package theorycrafter.ui.graphs

import androidx.compose.foundation.layout.*
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.utils.VSpacer
import compose.widgets.GridScope
import compose.widgets.RaisedButtonWithText
import compose.widgets.SingleLineText
import compose.widgets.rememberAppendSuffixTransformation
import eve.data.NNBSP
import eve.data.SensorType
import eve.data.asSensorStrength
import eve.data.fractionAsPercentageWithPrecisionAtMost
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.hostOs
import theorycrafter.fits.FitHandle
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.*
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.tooltip
import theorycrafter.ui.widgets.CheckboxedText
import theorycrafter.ui.widgets.DoubleTextField
import theorycrafter.ui.widgets.RadioButtonWithText
import theorycrafter.ui.widgets.maxPrecisionFormatter
import theorycrafter.utils.TextDropdownField


/**
 * The graphs window pane for exploring the effectiveness of ECM.
 */
@Composable
fun EcmEffectivenessGraphPane(initialFitHandle: FitHandle?) {
    val jammers: MutableList<GraphFit> = rememberSaveableListOfGraphSources(initialFitHandle)
    val targets: MutableList<EcmTarget> = rememberSaveableListOfGraphSources()
    val settings = TheorycrafterContext.settings.graphs.ecm
    val showChanceToJam = settings.showChanceToJam
    val includeDrones = settings.includeDrones

    // Whenever initialFitHandle changes (to a non-null value), make sure we have a target
    val firstColor = nextGraphSourceColor(emptyList())!!
    LaunchedEffect(initialFitHandle) {
        if (targets.isEmpty()) {
            targets.add(
                EcmTarget.Virtual(
                    color = firstColor,
                    sensorType = settings.virtualTargetSensorType.value,
                    sensorStrength = settings.virtualTargetSensorStrength.value,
                )
            )
        }
    }

    GraphPaneScaffold(
        graph = { modifier ->
            EcmEffectivenessGraph(
                jammers = jammers,
                targets = targets,
                includeDrones = includeDrones.value,
                showChanceToJam = showChanceToJam.value,
                modifier = modifier,
            )
        },
        paramsEditor = { modifier ->
            EcmEffectivenessGraphParams(
                jammers = jammers,
                targets = targets,
                showChanceToJam = showChanceToJam,
                includeDrones = includeDrones,
                virtualTargetSensorType = settings.virtualTargetSensorType,
                virtualTargetSensorStrength = settings.virtualTargetSensorStrength,
                modifier = modifier,
            )
        }
    )
}


/**
 * The interface for objects the user can specify as the targets of ECM.
 */
@Stable
interface EcmTarget: GraphSource {


    /**
     * The sensor type of the target.
     */
    val sensorType: SensorType


    /**
    * The sensor strength of the target.
     */
    val sensorStrength: Double


    /**
     * The resistance (or rather susceptibility) to ECM of the target.
     *
     * A value of 1.0 indicates ECM has full effect; 0.0 indicates no effect.
     */
    val ecmResistance: Double


    /**
     * A virtual [EcmTarget] that simply has the specified properties.
     */
    class Virtual(
        override val color: Color,
        override val sensorType: SensorType,
        override val sensorStrength: Double,
    ): EcmTarget {

        override val ecmResistance: Double
            get() = 1.0

        override val name: String
            get() = "Virtual"

    }


}


/**
 * The graph displaying the chances to jam a target.
 */
@Composable
private fun EcmEffectivenessGraph(
    jammers: List<GraphFit>,
    targets: List<EcmTarget>,
    includeDrones: Boolean,
    showChanceToJam: Boolean,
    modifier: Modifier
) {
    fun ModuleOrDrone<*>.ecmStrengthAtDistance(distance: Double, sensorType: SensorType): Double {
        return effectAtDistance(
            distance = distance,
            effectAtOptimal = { ecmStrength[sensorType]?.value ?: 0.0 }
        )
    }

    @Suppress("UNUSED_PARAMETER")
    fun chanceToJam(
        modules: List<Module>,
        droneGroups: List<DroneGroup>,
        attacker: GraphFit,
        target: EcmTarget,
        distance: Double
    ): Double {
        return jamChance(
            sensorType = target.sensorType,
            sensorStrength = target.sensorStrength,
            ecmResistance = target.ecmResistance,
            ecmModules = modules,
            appliedModuleEcmStrength = { module, sensorType ->
                module.ecmStrengthAtDistance(distance, sensorType)
            },
            ecmDrones = droneGroups,
            appliedDroneEcmStrength = { droneGroup, sensorType ->
                droneGroup.ecmStrengthAtDistance(distance = 0.0, sensorType)
            }
        )
    }

    @Suppress("UNUSED_PARAMETER")
    fun timeSpentJammed(
        modules: List<Module>,
        droneGroups: List<DroneGroup>,
        attacker: GraphFit,
        target: EcmTarget,
        distance: Double
    ): Double {
        return timeFractionJammed(
            sensorType = target.sensorType,
            sensorStrength = target.sensorStrength,
            ecmResistance = target.ecmResistance,
            ecmModules = modules,
            appliedModuleEcmStrength = { module, sensorType ->
                module.ecmStrengthAtDistance(distance, sensorType)
            },
            ecmDrones = droneGroups,
            appliedDroneEcmStrength = { droneGroup, sensorType ->
                droneGroup.ecmStrengthAtDistance(distance = 0.0, sensorType)
            }
        )
    }

    CumulativeRemoteEffectsGraph(
        fits = jammers,
        targets = targets,
        moduleFilter = { module -> module.ecmStrength.values.any { it != null } },
        droneFilter = { drone -> drone.ecmStrength.values.any { it != null } },
        includeDrones = includeDrones,
        computationScope = remember(showChanceToJam){
            { _, _, attacker, target ->
                CumulativeEffectComputation { modules, drones, distance ->
                    if (showChanceToJam) {
                        chanceToJam(modules, drones, attacker, target, distance)
                    } else {
                        timeSpentJammed(modules, drones, attacker, target, distance)
                    }
                }
            }
        },
        effectValueFormatter = { it.fractionAsPercentageWithPrecisionAtMost(precision = 1) },
        defaultMaxDisplayedValue = 1.05,
        modifier = modifier
    )
}


/**
 * The UI for the ECM effectiveness graph parameters.
 */
@Composable
private fun EcmEffectivenessGraphParams(
    jammers: MutableList<GraphFit>,
    targets: MutableList<EcmTarget>,
    showChanceToJam: MutableState<Boolean>,
    includeDrones: MutableState<Boolean>,
    virtualTargetSensorType: MutableState<SensorType>,
    virtualTargetSensorStrength: MutableState<Double>,
    modifier: Modifier
) {
    Row(
        horizontalArrangement = Arrangement.spacedBy(
            space = TheorycrafterTheme.spacing.larger,
            alignment = Alignment.Start,
        ),
        modifier = modifier
    ) {
        val nextJammerColor = nextGraphSourceColor(jammers)
        GraphSourceList(
            title = "Jamming Fits",
            sources = jammers,
            mutableSources = jammers,
            infoColumnWidths = emptyList(),
            infoCells = { _, _ -> },
            nextColor = nextJammerColor.takeIf { jammers.isEmpty() || (targets.size <= 1) },
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f, fill = false)
        )

        val nextTargetColor = nextGraphSourceColor(targets)
        GraphSourceList(
            title = "ECM Targets",
            sources = targets,
            mutableSources = targets,
            infoColumnWidths = listOf(60.dp, 50.dp),
            infoCells = { source, firstInfoColumnIndex ->
                EcmTargetRowInfoCells(source as EcmTarget, firstInfoColumnIndex)
            },
            nextColor = nextTargetColor.takeIf { targets.isEmpty() || (jammers.size <= 1) },
            modifier = Modifier
                .fillMaxHeight()
                .weight(1.2f, fill = false)
        )

        AddVirtualEcmTarget(
            ecmTargets = targets,
            nextTargetColor = nextTargetColor,
            virtualTargetSensorType = virtualTargetSensorType,
            virtualTargetSensorStrength = virtualTargetSensorStrength,
        )

        Column(
            modifier = Modifier
                .width(IntrinsicSize.Max)
                .fillMaxHeight(),
            verticalArrangement = Arrangement.Bottom
        ) {
            SingleLineText(text = "Show:")
            VSpacer(TheorycrafterTheme.spacing.xxsmall)
            RadioButtonWithText(
                text = "Chance of immediate jam",
                selected = showChanceToJam.value,
                onClick = { showChanceToJam.value = true },
                modifier = Modifier.fillMaxWidth()
            )
            RadioButtonWithText(
                text = "Percentage of time jammed",
                selected = !showChanceToJam.value,
                onClick = { showChanceToJam.value = false },
                modifier = Modifier.fillMaxWidth()
            )
            VSpacer(TheorycrafterTheme.spacing.large)
            CheckboxedText(
                modifier = Modifier.fillMaxWidth(),
                text = "Include ECM drones",
                state = includeDrones,
            )
        }
    }
}


/**
 * The cells of the info columns of the ECM target row, when not edited.
 */
@Composable
private fun GridScope.GridRowScope.EcmTargetRowInfoCells(
    source: EcmTarget,
    firstColumnIndex: Int
) {
    cell(firstColumnIndex, contentAlignment = Alignment.CenterStart) {
        SingleLineText(
            text = source.sensorType.shortDisplayName,
            modifier = Modifier.tooltip("Sensor Type")
        )
    }
    cell(firstColumnIndex+1, contentAlignment = Alignment.CenterEnd) {
        SingleLineText(
            text = source.sensorStrength.asSensorStrength(),
            modifier = Modifier.tooltip("Sensor Strength")
        )
    }
}


/**
 * The UI for adding a virtual ECM target.
 */
@Composable
private fun AddVirtualEcmTarget(
    ecmTargets: MutableList<EcmTarget>,
    nextTargetColor: Color?,
    virtualTargetSensorType: MutableState<SensorType>,
    virtualTargetSensorStrength: MutableState<Double>
) {
    Column(
        modifier = Modifier
            .height(  // Needed due to https://github.com/JetBrains/compose-multiplatform/issues/4760
                if (hostOs == OS.Windows) 166.dp else 160.dp
            )
            .width(230.dp),
    ) {
        Text("Virtual ECM Target", style = TheorycrafterTheme.textStyles.mediumHeading)
        VSpacer(TheorycrafterTheme.spacing.small)

        var sensorType by rememberSaveable { mutableStateOf(virtualTargetSensorType.value) }
        TextDropdownField(
            label = "Sensor Type",
            items = SensorType.entries,
            selectedItem = sensorType,
            onItemSelected = { _, value -> sensorType = value },
        )
        VSpacer(TheorycrafterTheme.spacing.small)

        var sensorStrength: Double? by rememberSaveable { mutableStateOf(virtualTargetSensorStrength.value) }
        DoubleTextField(
            value = sensorStrength,
            onValueChange = { sensorStrength = it },
            formatter = maxPrecisionFormatter(precision = 1),
            constraint = { it > 0 },
            label = { Text("Sensor Strength") },
            visualTransformation = rememberAppendSuffixTransformation { "${NNBSP}pts" },
            modifier = Modifier
                .fillMaxWidth()
        )

        Spacer(Modifier.height(TheorycrafterTheme.spacing.medium).weight(1f))

        RaisedButtonWithText(
            text = "Add Virtual ECM Target",
            enabled = (nextTargetColor != null) && (sensorStrength != null),
            onClick = {
                val definiteSensorStrength = sensorStrength!!
                ecmTargets.add(
                    EcmTarget.Virtual(
                        color = nextTargetColor!!,
                        sensorType = sensorType,
                        sensorStrength = definiteSensorStrength
                    )
                )
                virtualTargetSensorType.value = sensorType
                virtualTargetSensorStrength.value = definiteSensorStrength
            },
            modifier = Modifier
                .fillMaxWidth()
                .then(
                    when {
                        nextTargetColor == null -> Modifier.tooltip("Max. targets reached")
                        sensorStrength == null -> Modifier.tooltip("Illegal sensor strength value")
                        else -> Modifier
                    }
                )
        )
    }
}