/**
 * The UI showing a fit's defense stats: HP, resists etc.
 */

package theorycrafter.ui.fitstats

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import compose.utils.EasyTooltipPlacement
import compose.widgets.GridScope
import compose.widgets.SimpleGrid
import compose.widgets.SingleLineText
import eve.data.*
import theorycrafter.fitting.Fit
import theorycrafter.fitting.ItemDefense
import theorycrafter.fitting.resistFactor
import theorycrafter.ui.TheorycrafterTheme
import theorycrafter.ui.fiteditor.ValueWithDescription
import theorycrafter.ui.fiteditor.ValueWithDescriptionTable
import theorycrafter.ui.tooltip
import theorycrafter.ui.widgets.PercentageView
import theorycrafter.utils.AttractAttentionOnValueChange
import theorycrafter.utils.DpOffsetY
import kotlin.math.absoluteValue
import kotlin.math.roundToInt


/**
 * Displays a single resist (resonance) value.
 */
@Composable
private fun ResistView(
    damageType: DamageType,
    resonance: Double,
    hitpoints: Double,
    averageResistFactor: Double,
    modifier: Modifier = Modifier
) {
    val resistFactor = resistFactor(resonance)
    val ehp = hitpoints * resistFactor
    val ratio = resistFactor / averageResistFactor

    PercentageView(
        value = 1f - resonance.toFloat(),
        decimals = 1,
        colors = TheorycrafterTheme.colors.resistanceIndicatorColors(damageType),
        modifier = modifier
            .fillMaxWidth()
            .tooltip(
                placement = EasyTooltipPlacement.ElementBottomCenter(
                    offset = DpOffsetY(TheorycrafterTheme.spacing.xxxsmall)
                )
            ) {
                ValueWithDescriptionTable(
                    ValueWithDescription(resistFactor.asResistFactor(), "damage reduction"),
                    ValueWithDescription(ehp.asHitPoints(ehp = true), "against ${damageType.displayNameLowercase} damage"),
                    ValueWithDescription(
                        value = when {
                            (resistFactor - averageResistFactor).absoluteValue < 0.0001 -> "same"
                            resistFactor > averageResistFactor -> ratio.asMultiplicationFactor()
                            resistFactor < averageResistFactor -> (1/ratio).asMultiplicationFactor()
                            else -> "same"
                        },
                        description = when {
                            (resistFactor - averageResistFactor).absoluteValue < 0.0001 -> "as average"
                            resistFactor > averageResistFactor -> "better than average"
                            resistFactor < averageResistFactor -> "worse than average"
                            else -> "as average"
                        }
                    )
                )
            }
    )
}


/**
 * The cell with the caption in a resists row.
 */
@Composable
private fun GridScope.GridRowScope.resistRowCaptionCell(text: String) {
    cell(0, modifier = Modifier.padding(end = 6.dp), contentAlignment = Alignment.CenterEnd) {
        SingleLineText(
            text = text,
            style = TheorycrafterTheme.textStyles.caption,
        )
    }
}


/**
 * The summary cell in a resists row.
 */
@Composable
private fun GridScope.GridRowScope.resistRowSummaryCell(
    text: String,
    tooltipContent: (@Composable () -> Unit)? = null
) {
    cell(5) {
        AttractAttentionOnValueChange(
            value = text,
            modifier = Modifier
                .tooltip(
                    placement = EasyTooltipPlacement.ElementCenterStart,
                    content = tooltipContent
                )
        ) {
            SingleLineText(
                text = text,
                modifier = Modifier
                    .padding(start = TheorycrafterTheme.spacing.xsmall)
                    .align(Alignment.CenterStart),
            )
        }
    }
}


/**
 * A row of resists for a single defense type.
 */
@Composable
private fun GridScope.GridRowScope.ResistsRow(
    name: String,
    defense: ItemDefense,
) {
    val resonance = defense.resonances
    val resistFactor = defense.resistFactor
    val hp = defense.hp.value

    resistRowCaptionCell(text = name)

    for (damageType in DamageType.entries) {
        cell(1 + damageType.ordinal) {
            ResistView(
                damageType = damageType,
                resonance = resonance[damageType].value,
                hitpoints = defense.hp.value,
                averageResistFactor = resistFactor,
                modifier = Modifier.fillMaxWidth(),
            )
        }
    }

    val ehp = defense.ehp
    resistRowSummaryCell(
        text = ehp.asHitPoints(ehp = true, withUnits = false),
        tooltipContent = {
            ValueWithDescriptionTable(
                ValueWithDescription(resistFactor.asResistFactor(), "${defense.name.lowercase()} damage reduction"),
                ValueWithDescription(hp.asHitPoints(withUnits = false), "raw hitpoints")
            )
        }
    )
}


/**
 * The last row in the table, showing the incoming damage.
 */
@Composable
private fun GridScope.GridRowScope.IncomingDamageRow(fit: Fit) {
    resistRowCaptionCell("Damage")

    val damagePattern = fit.incomingDamage.pattern
    val normalizedPattern = damagePattern.uniformIfNullOrNone().normalize()
    for (damageType in DamageType.entries) {
        cell(1 + damageType.ordinal) {
            val colors = TheorycrafterTheme.colors.resistanceIndicatorColors(damageType)
            PercentageView(
                value = normalizedPattern[damageType].toFloat(),
                colors = colors,
                decimals = 0,
                modifier = Modifier
                    .fillMaxWidth()
                    .tooltip(damagePattern[damageType].asDps(withUnits = true) + " incoming ${damageType.displayNameLowercase} damage"),
            )
        }
    }

    val totalIncomingDamage = damagePattern.totalDamage
    resistRowSummaryCell(
        text = if (totalIncomingDamage == 0.0) "N/A" else totalIncomingDamage.asDps(withUnits = false),
        tooltipContent = {
            ValueWithDescriptionTable(
                ValueWithDescription(
                    value = totalIncomingDamage.asDps(withUnits = true),
                    description = "total incoming damage"
                ),
            )
        }
    )
}


/**
 * UI showing the hitpoints stats.
 */
@Composable
private fun HpStats(fit: Fit) {
    with (fit.defenses) {
        val hp = hp
        val ehp = ehp

        StatsTable(
            columns = 3,
            Stat(
                label = "Hitpoints",
                value = ehp.asHitPoints(ehp = true, withUnits = true),
                tooltipValues = listOf(
                    ValueWithDescription(value = ehp.roundToInt().toString(), description = "effective hitpoints"),
                    ValueWithDescription(value = hp.roundToInt().toString(), description = "raw hitpoints")
                )
            ),
            ResistFactorStat(fit),
            RepairStat(fit)
        )
    }
}


/**
 * Returns the [Stat] for resist factor.
 */
@Composable
private fun ResistFactorStat(fit: Fit): Stat = with (fit.defenses) {
    val defenses = listOf(shield, armor, structure)
    val (maxRepairedDefense, maxRepairedEhp) = defenses
        .map { it to (it.ehpRepairedLocally + it.ehpRepairedRemotely) }
        .maxBy { (_, ehpRepaired) -> ehpRepaired }
    val maxEhpDefense = defenses.maxBy { it.ehp }
    val mainDefense = if (maxRepairedEhp > 0) maxRepairedDefense else maxEhpDefense
    val resistFactorByDefense = defenses.associateWith { it.resistFactor }
    Stat(
        label = "Resist Factor",
        value = "${resistFactorByDefense[mainDefense]!!.asResistFactor()} (${mainDefense.name.lowercase()})",
        tooltipValues = defenses.map {
            ValueWithDescription(resistFactorByDefense[it]!!.asResistFactor(), "${it.name.lowercase()} damage reduction")
        }
    )
}


/**
 * Returns the [Stat] for repairs.
 */
private fun RepairStat(fit: Fit): Stat {
    return with(fit.defenses) {
        data class HpRepair(
            val ehpAmount: Double,
            val hpAmount: Double,
            val defense: ItemDefense,
            val descriptionSuffix: String
        )

        val defenses = listOf(shield, armor, structure)
        val repairs = mutableListOf<HpRepair>()
        for (defense in defenses) {
            repairs.add(
                HpRepair(
                    ehpAmount = defense.ehpRepairedLocally,
                    hpAmount = defense.hpRepairedLocally,
                    defense = defense,
                    descriptionSuffix = "repaired locally"
                )
            )
            repairs.add(
                HpRepair(
                    ehpAmount = defense.ehpRepairedRemotely,
                    hpAmount = defense.hpRepairedRemotely,
                    defense = defense,
                    descriptionSuffix = "repaired remotely"
                )
            )
        }

        val hasShieldRepair = repairs.any { (it.ehpAmount > 0) && (it.defense == shield) }
        val hasArmorRepair = repairs.any { (it.ehpAmount > 0) && (it.defense == armor) }
        val hasStructureRepair = repairs.any { (it.ehpAmount > 0) && (it.defense == structure) }
        val includeShieldRegenInTotal = hasShieldRepair || (!hasArmorRepair && !hasStructureRepair)

        val shieldRegen = HpRepair(
            ehpAmount = shield.peakRegenEhpPerSecond,
            hpAmount = shield.peakRegenHpPerSecond,
            defense = shield,
            descriptionSuffix = "peak regen" + (if (!includeShieldRegenInTotal) "*" else "")
        )
        val totalEhpRepairedPerSecond = repairs.sumOf { it.ehpAmount } +
                if (includeShieldRegenInTotal) shieldRegen.ehpAmount else 0.0

        // Put at the end of the shield section if included, otherwise put at the end
        if (includeShieldRegenInTotal)
            repairs.add(repairs.indexOfLast { it.defense == shield } + 1, shieldRegen)
        else
            repairs.add(shieldRegen)

        Stat(
            label = "Repairs",
            value = totalEhpRepairedPerSecond.asHitpointsPerSecond(ehp = true, withUnits = true),
            tooltipValues = buildList {
                repairs.forEach {
                    if (it.ehpAmount == 0.0)
                        return@forEach
                    add(ValueWithDescription(
                        value = it.ehpAmount.roundToInt().toString(),
                        description = "${it.defense.name.lowercase()} EHP/s ${it.descriptionSuffix}"
                    ))
                    add(ValueWithDescription(
                        value = it.hpAmount.roundToInt().toString(),
                        description = "${it.defense.name.lowercase()} HP/s ${it.descriptionSuffix}"
                    ))
                }
            },
            extraTooltipContent = if (includeShieldRegenInTotal) null else { ->
                SingleLineText(
                    text = "*Not included in total",
                    style = TheorycrafterTheme.textStyles.footnote,
                )
            }
        )
    }
}


/**
 * UI showing the resist stats.
 */
@Composable
fun ResistStats(fit: Fit) {
    val defenses = fit.defenses
    SimpleGrid(
        columnWidths = listOf(TheorycrafterTheme.sizes.fitStatsCaptionColumnWidth) +
            List(4) { TheorycrafterTheme.sizes.fitStatsPercentageViewWidth } +  // One column per resist
            Dp.Unspecified,
        defaultRowModifier = Modifier.padding(bottom = 6.dp),
        defaultRowAlignment = Alignment.CenterVertically,
        defaultCellModifier = Modifier.padding(end = 4.dp).fillMaxWidth(),
    ) {
        row(0) {
            ResistsRow("Shield", defenses.shield)
        }
        row(1) {
            ResistsRow("Armor", defenses.armor)
        }
        row(2) {
            ResistsRow("Hull", defenses.structure)
        }
        row(3, modifier = Modifier.padding(top = TheorycrafterTheme.spacing.xsmall, bottom = 0.dp)) {  // Remove bottom padding from last row)
            IncomingDamageRow(fit)
        }
    }
}


/**
 * UI showing a fit's resistances, EHP etc.
 */
@Composable
fun DefenseStats(fit: Fit){
    Column(
        verticalArrangement = Arrangement.spacedBy(TheorycrafterTheme.spacing.small)
    ) {
        HpStats(fit)
        ResistStats(fit)
    }
}
