package org.openrndr.extra.shapes.adjust

import org.openrndr.math.Vector2
import org.openrndr.shape.ShapeContour

data class ContourAdjusterEdge(val contourAdjuster: ContourAdjuster, val segmentIndex: () -> Int) {

    val startPosition
        get() = contourAdjuster.contour.segments[segmentIndex()].start

    val endPosition
        get() = contourAdjuster.contour.segments[segmentIndex()].end

    fun position(t: Double): Vector2 {
        return contourAdjuster.contour.segments[segmentIndex()].position(t)
    }

    fun normal(t: Double): Vector2 {
        return contourAdjuster.contour.segments[segmentIndex()].normal(t)
    }

    val length: Double
        get() {
            return contourAdjuster.contour.segments[segmentIndex()].length
        }


    /**
     * A [ContourAdjusterVertex] interface for the start-vertex of the edge
     */
    val start
        get() = ContourAdjusterVertex(contourAdjuster, segmentIndex)

    /**
     * A [ContourAdjusterVertex] interface for the end-vertex of the edge
     */
    val end
        get() = ContourAdjusterVertex(
            contourAdjuster,
            { (segmentIndex() + 1).mod(contourAdjuster.contour.segments.size) })

    /**
     * A link to the edge before this edge
     */
    val previous: ContourAdjusterEdge?
        get() = if (contourAdjuster.contour.closed) {
            this.copy(segmentIndex = { (segmentIndex() - 1).mod(contourAdjuster.contour.segments.size) })
        } else {
            if (segmentIndex() > 0) {
                this.copy(segmentIndex = { segmentIndex() - 1 })
            } else {
                null
            }
        }

    /**
     * A link to the edge after this edge
     */
    val next: ContourAdjusterEdge?
        get() = if (contourAdjuster.contour.closed) {
            this.copy(segmentIndex = { (segmentIndex() + 1).mod(contourAdjuster.contour.segments.size) })
        } else {
            if (segmentIndex() < contourAdjuster.contour.segments.size - 1) {
                this.copy(segmentIndex = { segmentIndex() + 1 })
            } else {
                null
            }
        }

    fun select() {
        contourAdjuster.selectEdge(segmentIndex())
    }

    private fun wrap(block: ContourEdge.() -> ContourEdge) {
        val newEdge = ContourEdge(contourAdjuster.contour, segmentIndex()).block()
        contourAdjuster.contour = newEdge.contour
        contourAdjuster.updateSelection(newEdge.adjustments)
    }

    fun toLinear() = wrap { toLinear() }

    fun toCubic() = wrap { toCubic() }
    fun splitAt(t: Double) = wrap { splitAt(t) }

    /**
     * split edge in [numberOfParts] parts of equal length
     */
    fun splitIn(numberOfParts: Int) = wrap { splitIn(numberOfParts) }

    fun moveBy(translation: Vector2, updateTangents: Boolean = true) = wrap { movedBy(translation, updateTangents) }
    fun rotate(rotationInDegrees: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
        wrap { rotatedBy(rotationInDegrees, anchorT, updateTangents) }

    fun scale(scaleFactor: Double, anchorT: Double = 0.5, updateTangents: Boolean = true) =
        wrap { scaledBy(scaleFactor, anchorT, updateTangents = true) }

    fun replaceWith(t: Double, updateTangents: Boolean = true) = wrap { replacedWith(t, updateTangents) }

    fun replaceWith(openContour: ShapeContour) = wrap { replacedWith(openContour) }


    fun sub(t0: Double, t1: Double, updateTangents: Boolean = true) {
        contourAdjuster.contour =
            ContourEdge(contourAdjuster.contour, segmentIndex())
                .subbed(t0, t1)
                .contour
    }
}
