package theorycrafter.ui.graphs

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import compose.widgets.GraphLine
import eve.data.asPercentageWithTenthsIfBelow
import eve.data.millisAsTime
import theorycrafter.FitHandle
import theorycrafter.fitting.CapDeltaForBreakingMinimum
import theorycrafter.fitting.CapacitorSimulation
import theorycrafter.fitting.TimeWithoutBreakingMinimumCapForStabilityMillis
import kotlin.math.roundToInt


/**
 * The graphs window pane for exploring the level of capacitor over time.
 */
@Composable
fun CapacitorLevelGraphPane(initialFitHandle: FitHandle?) {
    FitsGraphPane(
        initialFitHandle = initialFitHandle,
        graph = { fits, modifier ->
            CapacitorLevelGraph(
                fits = fits,
                modifier = modifier
            )
        }
    )
}


/**
 * The graph of the percentage of capacitor over time of the given list of fits.
 */
@Composable
private fun CapacitorLevelGraph(
    fits: List<GraphFit>,
    modifier: Modifier
) {
    val lines = fits.map { graphFit ->
        val capByTime = graphFit.fit.capacitor.simulation().capByTime(maxTimeSec = 3600)
        val maxCap = graphFit.fit.capacitor.capacity.value
        GraphLine(
            name = graphFit.name,
            function = { time ->
                val index = time.roundToInt().coerceIn(0, capByTime.lastIndex)
                100 * capByTime[index] / maxCap
            },
            samplingStepPx = DefaultLineSamplingStep,
            samplingRange = 0.0..capByTime.lastIndex.toDouble(),
            lineStyleAtPoint = graphFit.lineStyle
        )
    }
    val maxDisplayedTime = lines.maxOfOrNull {
        it.samplingRange!!.endInclusive
    }?.plus(3) ?: 60.0
    val maxDisplayedCapPct = 105.0
    BasicGraph(
        modifier = modifier,
        xRange = 0.0 .. maxDisplayedTime,
        yRange = 0.0 .. maxDisplayedCapPct,
        xValueFormatter = { (it*1000).millisAsTime(showZeroRemainder = true) },
        yValueFormatter = Double::asPercentageWithTenthsIfBelow,
        lines = lines
    )
}


/**
 * Returns the amount of cap the ship will have each second until [maxTimeSec] or it runs out of cap.
 */
private fun CapacitorSimulation.capByTime(
    maxTimeSec: Int
): List<Double> {
    val totalCapacity = capacity
    if (isStableAtFull) {
        return object: AbstractList<Double>() {
            override val size get() = maxTimeSec
            override fun get(index: Int) = totalCapacity
        }
    }

    var minCapacity = totalCapacity  // The lowest capacity we've seen so far
    var minimumBrokenTime = 0  // The last time we broke the minimum.
    return buildList {
        while (currentTimeMillis <= maxTimeSec * 1000) {
            if (capacity < 0) { // Out of cap
                add(0.0)  // Add a zero just to visually clarify that it ran out
                break
            }

            add(capacity)

            advanceClock(1000, defaultItemActivationStrategy(totalCapacity))

            if (capacity < minCapacity - CapDeltaForBreakingMinimum) {
                minimumBrokenTime = currentTimeMillis
                minCapacity = capacity
            }

            // If we haven't broken the minimum for a while, we're probably stable
            if (currentTimeMillis - minimumBrokenTime > TimeWithoutBreakingMinimumCapForStabilityMillis)
                break
        }
    }
}