package theorycrafter.ui.graphs

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.width
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import compose.input.KeyShortcut
import compose.input.onKeyShortcut
import compose.widgets.*
import eve.data.NNBSP
import eve.data.asDistance
import eve.data.toDecimalWithSignificantDigitsAtMost
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.effectivenessAtDistance
import theorycrafter.ui.OutlinedTextField
import theorycrafter.ui.TheorycrafterTheme


/**
 * The graphs window pane for exploring the effectiveness of a generic effect.
 */
@Composable
fun GenericEffectGraphPane() {
    val settings = TheorycrafterContext.settings.graphs.genericEffect
    var effect by rememberSaveable {
        mutableStateOf(
            GenericEffect(
                strength = settings.effectStrength.value,
                optimalRange = settings.optimalRange.value,
                falloffRange = settings.falloffRange.value,
            )
        )
    }
    GraphPaneScaffold(
        graph = { modifier ->
            GenericEffectGraph(
                effect = effect,
                modifier = modifier
            )
        },
        paramsEditor = { modifier ->
            GenericEffectEditor(
                effect = effect,
                onSetEffect = {
                    effect = it
                    settings.apply {
                        effectStrength.value = it.strength
                        optimalRange.value = it.optimalRange
                        falloffRange.value = it.falloffRange
                    }
                },
                modifier = modifier
            )
        }
    )
}


/**
 * The width of the effect strength text field.
 */
private val EffectStrengthTextFieldWidth = 160.dp


/**
 * The widths of the effect optimal and falloff text fields.
 */
private val EffectDistanceTextFieldWidth = 160.dp


/**
 * The UI for specifying and editing the parameters of a generic effect.
 */
@Composable
private fun GenericEffectEditor(
    effect: GenericEffect,
    onSetEffect: (GenericEffect) -> Unit,
    modifier: Modifier
) {

    fun String.asEffectStrength() = toDoubleOrNull()
    fun String.asDistance() = toDoubleOrNull()?.takeIf { it >= 0 }

    Row(
        modifier = modifier,
        horizontalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.large),
        verticalAlignment = Alignment.Bottom
    ) {
        var effectStrengthText by remember { mutableStateOf(effect.strength.toDecimalWithSignificantDigitsAtMost(4)) }
        var optimalRangeText by remember { mutableStateOf(effect.optimalRange.toDecimalWithSignificantDigitsAtMost(4)) }
        var falloffRangeText by remember { mutableStateOf(effect.falloffRange.toDecimalWithSignificantDigitsAtMost(4)) }

        fun applyValues() {
            val strength = effectStrengthText.asEffectStrength() ?: return
            val optimal = optimalRangeText.asDistance() ?: return
            val falloff = falloffRangeText.asDistance() ?: return

            onSetEffect(
                GenericEffect(
                    strength = strength,
                    optimalRange = optimal,
                    falloffRange = falloff
                )
            )
        }

        TheorycrafterTheme.OutlinedTextField(
            value = effectStrengthText,
            onValueChange = { effectStrengthText = it },
            label = { Text("Effect Strength") },
            singleLine = true,
            isError = effectStrengthText.asEffectStrength() == null,
            modifier = Modifier
                .width(EffectStrengthTextFieldWidth)
                .onKeyShortcut(KeyShortcut.anyEnter(), onPreview = true) {
                    applyValues()
                }
        )

        val distanceKmSuffix = rememberAppendSuffixTransformation { "${NNBSP}km" }

        TheorycrafterTheme.OutlinedTextField(
            value = optimalRangeText,
            onValueChange = { optimalRangeText = it },
            label = { Text("Optimal Range") },
            singleLine = true,
            visualTransformation = distanceKmSuffix,
            isError = optimalRangeText.asDistance() == null,
            modifier = Modifier
                .width(EffectDistanceTextFieldWidth)
                .onKeyShortcut(KeyShortcut.anyEnter(), onPreview = true) {
                    applyValues()
                }
        )

        TheorycrafterTheme.OutlinedTextField(
            value = falloffRangeText,
            onValueChange = { falloffRangeText = it },
            singleLine = true,
            label = { Text("Falloff Range") },
            visualTransformation = distanceKmSuffix,
            isError = falloffRangeText.asDistance() == null,
            modifier = Modifier
                .width(EffectDistanceTextFieldWidth)
                .onKeyShortcut(KeyShortcut.anyEnter(), onPreview = true) {
                    applyValues()
                }
        )

        RaisedButtonWithText(
            text = "Apply Changes",
            onClick = { applyValues() },
            enabled = (effectStrengthText.asEffectStrength() != null)
                    && (optimalRangeText.asDistance() != null)
                    && (falloffRangeText.asDistance() != null),
        )
    }
}


/**
 * A graph displaying the effectiveness of a generic effect.
 */
@Composable
private fun GenericEffectGraph(
    effect: GenericEffect,
    modifier: Modifier
) {
    val formatDistanceForDisplay: (Double) -> String = { (it*1000).asDistance(withUnits = true) }
    val formatEffectivenessForDisplay: (Double) -> String = { it.toDecimalWithSignificantDigitsAtMost(2) }

    val lineColor = nextGraphSourceColor(emptyList())!!
    FunctionLineGraph(
        modifier = modifier,
        xRange = 0.0 .. effect.optimalRange + 3 * effect.falloffRange,
        yRange = effect.strength.let {
            if (it >= 0)
                -0.05 * it .. 1.1 * it
            else
                1.1 * it .. -0.05 * it
        },
        properties = DefaultGraphProperties.copy(
            lineColor = LocalContentColor.current.copy(alpha = 0.15f),
            labelColor = LocalContentColor.current,
            horizontalLinesMinDistance = 80.dp,
            verticalLinesMinDistance = 100.dp,
            xLabelFormatter = formatDistanceForDisplay,
            yLabelFormatter = formatEffectivenessForDisplay,
            hoverLineProperties = DefaultGraphProperties.hoverLineProperties.copy(
                color = LocalContentColor.current,
                xFormatter = formatDistanceForDisplay,
                yFormatter = { _, _, value -> formatEffectivenessForDisplay(value) },
                textShadowColor = if (TheorycrafterTheme.colors.base().isLight) Color.Black else Color.White
            )
        ),
        lines = listOf(
            GraphLine(
                name = "Effectiveness",
                function = { x ->
                    effect.strength * effectivenessAtDistance(
                        distance = x,
                        optimal = effect.optimalRange,
                        falloff = effect.falloffRange
                    )
                },
                samplingStepPx = DefaultLineSamplingStep,
                lineStyleAtPoint = FixedLineStyle { defaultLineStyle(lineColor) }
            )
        )
    )
}


/**
 * The parameters of a generic effect.
 */
private data class GenericEffect(


    /**
     * The strength of the effect, in whatever units.
     */
    val strength: Double,


    /**
     * The optimal range, in kilometers.
     */
    val optimalRange: Double,


    /**
     * The falloff range, in kilometers.
     */
    val falloffRange: Double


)