@file:Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")

package org.openrndr.extra.envelopes

import org.openrndr.Clock
import org.openrndr.extra.parameters.DoubleParameter

class Trigger(val id: Int, val on: Double, var off: Double, val envelope: Envelope)

class TrackerValue(
    val time: Double,
    val value: Double,
    val position: Double,
    val envelope: Envelope
) {
    operator fun invoke() {
        draw()
    }

    fun draw() {
        envelope.objectFunction(time, value, position)
    }
}

abstract class Tracker<T : Envelope>(val clock: Clock) {
    val triggers = mutableListOf<Trigger>()

    protected abstract fun createEnvelope(objectFunction: (time: Double, value: Double, position: Double) -> Unit): T

    fun triggerOn(
        triggerId: Int = 0,
        objectFunction: (time: Double, value: Double, position: Double) -> Unit = { _, _, _ -> }
    ) {
        mppSynchronized(triggers) {
            val t = clock.seconds
            triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) }
            triggers.add(Trigger(triggerId, clock.seconds, 1E30, createEnvelope(objectFunction)))
        }
    }

    fun triggerOff(triggerId: Int = 0) {
        mppSynchronized(triggers) {
            val t = clock.seconds
            triggers.removeAll { !it.envelope.isActive(t - it.on, it.off - it.on) }
            triggers.findLast { it.id == triggerId }?.let {
                it.off = clock.seconds
            }
        }
    }

    fun values(): List<TrackerValue> {
        val t = clock.seconds
        return mppSynchronized(triggers) {
            triggers.mapNotNull {
                val tOn = t - it.on
                val tOff = it.off - it.on

                if (it.envelope.isActive(tOn, tOff)) {
                    val v = it.envelope.value(tOn, tOff)
                    TrackerValue(t, v, it.envelope.position(tOn, tOff), it.envelope)
                } else {
                    null
                }
            }
        }
    }

    fun value(): Double {
        return values().sumOf { it.value }
    }
}


class ADSRTracker(clock: Clock) : Tracker<ADSR>(clock) {

    @DoubleParameter("attack", 0.0, 20.0, order = 1)
    var attack: Double = 0.1

    @DoubleParameter("decay", 0.0, 20.0, order = 2)
    var decay: Double = 0.1

    @DoubleParameter("sustain", 0.0, 1.0, order = 3)
    var sustain: Double = 0.9

    @DoubleParameter("release", 0.0, 20.0, order = 4)
    var release: Double = 0.9

    override fun createEnvelope(objectFunction: (time: Double, value: Double, position: Double) -> Unit): ADSR {
        return ADSR(attack, decay, sustain, release).apply {
            this.objectFunction = objectFunction
        }
    }
}

