/**
 * Determines the value to be displayed in the "Effect" column for drones.
 */

package theorycrafter.ui.fiteditor.effectcolumn

import androidx.compose.runtime.Composable
import eve.data.*
import eve.data.typeid.*
import eve.data.utils.ValueByEnum
import eve.data.utils.valueByEnum
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.*
import theorycrafter.ui.widgets.TextAndTooltip
import theorycrafter.utils.with
import kotlin.math.pow


/**
 * All the information we need to display a [DroneGroup]'s effect.
 */
class DroneGroupInfo(
    override val fit: Fit,
    override val appliedEffects: Map<AttributeProperty<*>, Collection<AppliedEffect>>,
    val droneGroup: DroneGroup,
): AffectingItemInfo


/**
 * Returns the [DroneGroupInfo] for the given module.
 */
@Composable
private fun droneGroupInfo(
    fit: Fit,
    droneGroup: DroneGroup,
    remoteEffect: RemoteEffect?,
): DroneGroupInfo {
    val appliedEffects =
        if (remoteEffect == null)
            emptyMap()
        else
            droneGroup.appliedEffectsByTargetProperty(contextEffect = remoteEffect)
    return DroneGroupInfo(fit, appliedEffects, droneGroup)
}


/**
 * Returns the displayed effect for the given drone group.
 */
@Composable
fun displayedDroneEffect(droneGroup: DroneGroup): TextAndTooltip? {
    if (!droneGroup.active)
        return null

    val droneType = droneGroup.type
    // Drones never have local effects, so we don't need applied effects here
    val droneGroupInfo = droneGroupInfo(droneGroup.fit, droneGroup, remoteEffect = null)
    return with(TheorycrafterContext.eveData, DroneProperties, droneGroupInfo) {
        when {
            droneType.isTargetPaintingDrone() ->
                withMergedMainSource(mainSource = SIGNATURE_RADIUS_BONUS)
            droneType.isStasisWebifyingDrone() ->
                withMergedMainSource(mainSource = SPEED_FACTOR_BONUS)
            droneType.isSensorDampeningDrone() ->
                withMergedMainSource(
                    mainSource = TARGETING_RANGE_BONUS,
                    SCAN_RESOLUTION_BONUS
                )
            droneType.isTrackingDisruptingDrone() ->
                withMergedMainSource(
                    mainSource = OPTIMAL_RANGE_BONUS,
                    FALLOFF_BONUS,
                    TRACKING_SPEED_BONUS,
                )
            droneType.isEnergyNeutralizerDrone() ->
                withMergedMainSource(
                    mainSource = ENERGY_NEUTRALIZED_PER_SECOND,
                    ENERGY_NEUTRALIZED,
                    DURATION
                )
            droneType.isEcmDrone() ->
                withMergedMainSource(
                    mainSource = ECM_STRENGTH,
                    ecmChance(targetSensorStrength = 10.0),
                    ecmChance(targetSensorStrength = 15.0),
                    ecmChance(targetSensorStrength = 20.0),
                    ecmChance(targetSensorStrength = 25.0),
                    ecmChance(targetSensorStrength = 30.0),
                    ecmChance(targetSensorStrength = 40.0),
                    ecmChance(targetSensorStrength = 50.0),
                )
            droneType.isShieldMaintenanceBot() ->
                withMergedMainSource(
                    mainSource = SHIELD_HP_BOOST_RATE,
                    DURATION
                )
            droneType.isArmorMaintenanceBot() ->
                withMergedMainSource(
                    mainSource = ARMOR_HP_REPAIR_RATE,
                    DURATION
                )
            droneType.isHullMaintenanceBot() ->
                withMergedMainSource(
                    mainSource = STRUCTURE_HP_REPAIR_RATE,
                    DURATION
                )
            droneType.isMiningDrone() ->
                withMergedMainSource(
                    mainSource = MINING_INFO,
                    MINING_RESIDUE
                )
            droneType.isCombatDrone() ->
                withMergedMainSource(
                    mainSource = DPS,
                    tooltipSources = buildList {
                        add(VOLLEY)
                        addAll(DAMAGE_TYPE_PROPORTIONS.values)
                    }
                )
            else -> null
        }
    }
}


/**
 * Returns the displayed effect of the given drone group on the given fit.
 */
@Composable
fun displayedRemoteDroneEffect(remoteEffect: RemoteEffect, droneGroup: DroneGroup): TextAndTooltip? {
    if (!droneGroup.active)
        return null

    val targetFit = remoteEffect.target
    val droneType = droneGroup.type
    val droneGroupInfo = droneGroupInfo(targetFit, droneGroup, remoteEffect = remoteEffect)
    return with(TheorycrafterContext.eveData, CommonEffectSources, DroneProperties, droneGroupInfo) {
        when {
            droneType.isTargetPaintingDrone() ->
                withMergedMainSource(mainSource = SIGNATURE_RADIUS)
            droneType.isStasisWebifyingDrone() ->
                withMergedMainSource(mainSource = MAX_VELOCITY)
            droneType.isSensorDampeningDrone() ->
                withMergedMainSource(
                    mainSource = TARGETING_RANGE,
                    SCAN_RESOLUTION
                )
            droneType.isTrackingDisruptingDrone() ->
                withMergedMainSource(
                    mainSource = TURRET_OPTIMAL_RANGE,
                    TURRET_FALLOFF,
                    TURRET_TRACKING_SPEED,
                )
            droneType.isEnergyNeutralizerDrone() ->
                withMergedMainSource(
                    mainSource = PROJECTED_ENERGY_NEUTRALIZED_PER_SECOND,
                    PROJECTED_ENERGY_NEUTRALIZED,
                    DURATION
                )
                droneType.isEcmDrone() ->
                    withMergedMainSource(
                        mainSource = JAM_CHANCE
                    )
                droneType.isShieldMaintenanceBot() ->
                    withMergedMainSource(
                        mainSource = SHIELD_EHP_BOOST_RATE,
                        SHIELD_HP_BOOST_RATE,
                        DURATION
                    )
                droneType.isArmorMaintenanceBot() ->
                    withMergedMainSource(
                        mainSource = ARMOR_EHP_REPAIR_RATE,
                        ARMOR_HP_REPAIR_RATE,
                        DURATION
                    )
                droneType.isHullMaintenanceBot() ->
                    withMergedMainSource(
                        mainSource = STRUCTURE_EHP_REPAIR_RATE,
                        STRUCTURE_HP_REPAIR_RATE,
                        DURATION
                    )
            else -> null
        }
    }
}


/**
 * Specifies the property and how to display it.
 */
private class DronePropertyEffectSource<T: Any>(


    /**
     * Given the drone group and the context fit, returns the value to display.
     * Returns `null` if nothing should be displayed.
     */
    val value: (Fit, DroneGroup) -> T?,


    /**
     * The property's description. This will be shown to the right of the value in the tooltip.
     */
    description: String,


    /**
     * Specifies the value to display given the property's value and the number of drones in the group.
     */
    val cumulativeEffect: CumulativeEffect<T>


): DisplayedEffectSource<DroneGroupInfo>(description = description) {


    /**
     * A constructor for effect sources which don't need the fit itself.
     */
    constructor(
        value: (DroneGroup) -> T?,
        description: String,
        cumulativeEffect: CumulativeEffect<T>
    ) : this(
        value = { _, droneGroup -> value(droneGroup) },
        description = description,
        cumulativeEffect = cumulativeEffect
    )


    @Composable
    override fun valueOrMissingText(
        fit: Fit,
        affectingItemInfo: DroneGroupInfo,
        isCellDisplay: Boolean
    ): ValueOrMissing? {
        val droneGroup = affectingItemInfo.droneGroup

        val singleDroneValue = value(fit, droneGroup) ?: return null
        return cumulativeEffect.effectText(
            singleDroneValue = singleDroneValue,
            droneCount = droneGroup.size,
            isCellDisplay = isCellDisplay
        ).valueOr(null)
    }


    companion object {


        /**
         * Constructs a [DronePropertyEffectSource] from an [AttributeProperty] instead of a value.
         */
        fun <T: Any> fromProperty(
            property: (DroneGroup) -> AttributeProperty<T>?,
            description: String,
            cumulativeEffect: CumulativeEffect<T>
        ) = DronePropertyEffectSource(
            value = { _, droneGroup -> property(droneGroup)?.value },
            description = description,
            cumulativeEffect = cumulativeEffect
        )


    }


}


/**
 * Given the value of a property for a single drone, returns the text to display for the given number of drones.
 */
private sealed interface CumulativeEffect<in T> {

    fun effectText(singleDroneValue: T, droneCount: Int, isCellDisplay: Boolean): String?

}


/**
 * Treats the value as a percentage that is added to the target, e.g. the target painter's "bonus" to signature radius.
 */
private class MultiplicativePercent(

    /**
     * Returns the attribute affected by the drone's effect.
     * If it is stacking penalized, the cumulative value will be stacking penalized.
     */
    val modifiedAttribute: (Attributes) -> Attribute<*>

): CumulativeEffect<Double> {

    override fun effectText(singleDroneValue: Double, droneCount: Int, isCellDisplay: Boolean): String {
        val eveData = TheorycrafterContext.eveData
        val attribute = modifiedAttribute(eveData.attributes)
        val cumulativeEffect = if (attribute.isStackingPenalized){
            var stackingPenalizedEffect = 1.0
            for (n in (0 until droneCount))
                stackingPenalizedEffect *= (1 + singleDroneValue * stackingPenaltyFactor(n) / 100.0)
            stackingPenalizedEffect
        }
        else
            (1 + singleDroneValue / 100).pow(droneCount)

        return (cumulativeEffect-1).fractionAsPercentage(if (isCellDisplay) 0 else 1, withSign = true)
    }

}


/**
 * An additive effect, where the value is simply multiplied by the number of drones, e.g. the neutralizer drone's
 * amount of energy neutralized.
 */
private class Additive(
    val valueFormatter: (Double, isCell: Boolean) -> String
): CumulativeEffect<Double> {

    override fun effectText(singleDroneValue: Double, droneCount: Int, isCellDisplay: Boolean): String {
        val cumulativeEffect = singleDroneValue * droneCount
        return valueFormatter(cumulativeEffect, isCellDisplay)
    }

}


/**
 * A non-cumulative value, where the number of drones does not affect the value, e.g. the duration of the drones
 * activation cycle.
 */
private class NonCumulative<T>(
    val valueFormatter: (T, isCell: Boolean) -> String?
): CumulativeEffect<T> {

    override fun effectText(singleDroneValue: T, droneCount: Int, isCellDisplay: Boolean): String? {
        return valueFormatter(singleDroneValue, isCellDisplay)
    }

}


/**
 * A special effect for [MiningInfo].
 */
private class MiningInfoEffect(
    val valueFormatter: (MiningInfo, isCell: Boolean) -> String?
): CumulativeEffect<MiningInfo>{

    override fun effectText(singleDroneValue: MiningInfo, droneCount: Int, isCellDisplay: Boolean): String? {
        return valueFormatter(singleDroneValue * droneCount, isCellDisplay)
    }

}


/**
 * The cumulative effect for the chance of a successful jam.
 */
private val JamChance = NonCumulative<Double> { value, _ -> value.fractionAsPercentageWithPrecisionAtMost(1) }


/**
 * The drone's EHP.
 */
val DRONE_EHP_PROPERTY: DisplayedEffectSource<DroneGroupInfo> = DronePropertyEffectSource(
    value = { it.defenses.ehp },
    description = "ehp",
    cumulativeEffect = NonCumulative { value, isCell -> value.asHitPoints(ehp = true, withUnits = isCell) },
)


/**
 * Groups the drone properties we can display.
 */
private object DroneProperties{


    /**
     * The increase in target signature radius by target painting drones.
     */
    val SIGNATURE_RADIUS_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::signatureRadiusBonus,
        description = "target signature radius",
        cumulativeEffect = MultiplicativePercent(Attributes::signatureRadius)
    )


    /**
     * The speed reduction factor by webifying drones.
     */
    val SPEED_FACTOR_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::speedFactorBonus,
        description = "target speed",
        cumulativeEffect = MultiplicativePercent(Attributes::maxVelocity)
    )


    /**
     * The targeting range reduction by sensor dampening drones.
     */
    val TARGETING_RANGE_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::targetingRangeBonus,
        description = "target targeting range",
        cumulativeEffect = MultiplicativePercent(Attributes::targetingRange)
    )


    /**
     * The scan resolution reduction by sensor dampening drones.
     */
    val SCAN_RESOLUTION_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::scanResolutionBonus,
        description = "target scan resolution",
        cumulativeEffect = MultiplicativePercent(Attributes::scanResolution)
    )


    /**
     * The turret optimal range reduction by tracking disruption drones.
     */
    val OPTIMAL_RANGE_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::optimalRangeBonus,
        description = "target optimal range",
        cumulativeEffect = MultiplicativePercent(Attributes::maxRange)
    )


    /**
     * The turret falloff reduction by tracking disruption drones.
     */
    val FALLOFF_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::falloffBonus,
        description = "target falloff",
        cumulativeEffect = MultiplicativePercent(Attributes::falloff)
    )


    /**
     * The turret tracking speed reduction by tracking disruption drones.
     */
    val TRACKING_SPEED_BONUS = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::trackingSpeedBonus,
        description = "target tracking speed",
        cumulativeEffect = MultiplicativePercent(Attributes::trackingSpeed)
    )


    /**
     * The amount of energy neutralized by energy neutralizing drones.
     */
    val ENERGY_NEUTRALIZED = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::energyNeutralized,
        description = "energy neutralized per activation",
        cumulativeEffect = Additive { value, _ -> value.asCapacitorEnergy(withSign = false) }
    )


    /**
     * The amount of energy neutralized by energy neutralizing drones from a remote fit.
     */
    val PROJECTED_ENERGY_NEUTRALIZED = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            droneGroup.energyNeutralized?.doubleValue?.let {
                it * fit.electronicWarfare.energyNeutralizationResistance.doubleValue
            }
        },
        description = "energy neutralized per activation",
        cumulativeEffect = Additive { value, _ -> value.asCapacitorEnergy(withSign = false) }
    )


    /**
     * The amount of energy neutralized per second by energy neutralizing drones.
     */
    val ENERGY_NEUTRALIZED_PER_SECOND = DronePropertyEffectSource(
        value = DroneGroup::energyNeutralizedPerSecond,
        description = "energy neutralized",
        cumulativeEffect = Additive { value, _ -> value.asCapacitorEnergyPerSecond(withSign = false) }
    )


    /**
     * The amount of energy neutralized per second by energy neutralizing drones from a remote fit.
     */
    val PROJECTED_ENERGY_NEUTRALIZED_PER_SECOND = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            droneGroup.energyNeutralizedPerSecond?.let {
                it * fit.electronicWarfare.energyNeutralizationResistance.doubleValue
            }
        },
        description = "energy neutralized",
        cumulativeEffect = Additive { value, _ -> value.asCapacitorEnergyPerSecond(withSign = false) }
    )


    /**
     * The duration of the drone's activation cycle.
     */
    val DURATION = DronePropertyEffectSource.fromProperty(
        property = DroneGroup::activationDuration,
        description = "duration",
        cumulativeEffect = NonCumulative { value, _ -> value.millisAsTimeSec() }
    )


    /**
     * The amount of shield hitpoints boosted per second by shield maintenance bots.
     */
    val SHIELD_HP_BOOST_RATE = DronePropertyEffectSource(
        value = DroneGroup::shieldHpBoostedPerSecond,
        description = "shield boosted",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = false, rounding = isCell)
        }
    )


    /**
     * The amount of shield EHP boosted per second by shield maintenance bots.
     */
    val SHIELD_EHP_BOOST_RATE = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            with(fit.defenses.shield) {
                droneGroup.shieldHpBoostedPerSecond?.toEhp()
            }
        },
        description = "shield boosted",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = true, rounding = isCell)
        }
    )


    /**
     * The amount of armor hitpoints repaired per second by armor maintenance bots.
     */
    val ARMOR_HP_REPAIR_RATE = DronePropertyEffectSource(
        value = DroneGroup::armorHpRepairedPerSecond,
        description = "armor repaired",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = false, rounding = isCell)
        }
    )


    /**
     * The amount of armor EHP repaired per second by armor maintenance bots.
     */
    val ARMOR_EHP_REPAIR_RATE = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            with(fit.defenses.armor) {
                droneGroup.armorHpRepairedPerSecond?.toEhp()
            }
        },
        description = "armor repaired",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = true, rounding = isCell)
        }
    )


    /**
     * The amount of structure hitpoints repaired per second by hull maintenance bots.
     */
    val STRUCTURE_HP_REPAIR_RATE = DronePropertyEffectSource(
        value = DroneGroup::structureHpRepairedPerSecond,
        description = "structure repaired",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = false, rounding = isCell)
        }
    )


    /**
     * The amount of structure EHP repaired by hull maintenance bots.
     */
    val STRUCTURE_EHP_REPAIR_RATE = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            with(fit.defenses.structure) {
                droneGroup.structureHpRepairedPerSecond?.toEhp()
            }
        },
        description = "structure repaired",
        cumulativeEffect = Additive{ value, isCell ->
            value.asHitpointsPerSecond(ehp = true, rounding = isCell)
        }
    )


    /**
     * The mining info of mining drones.
     */
    val MINING_INFO = DronePropertyEffectSource(
        value = DroneGroup::miningInfo,
        description = "mining yield",
        cumulativeEffect = MiningInfoEffect{ value, isCell ->
            if (isCell)
                value.asMinedAndWastedAmounts()
            else{
                // In the tooltip, the residue is shown via MINING_RESIDUE
                value.minedPerHour.asVolumePerHour(withSign = false, withUnits = true)
            }
        }
    )


    /**
     * The mining residue of mining drones.
     */
    val MINING_RESIDUE = DronePropertyEffectSource(
        value = DroneGroup::miningInfo,
        description = "mining residue",
        cumulativeEffect = MiningInfoEffect { value, _ ->
            val residuePerHour = value.residuePerHour ?: return@MiningInfoEffect null
            residuePerHour.asVolumePerHour(withSign = false, withUnits = true)
        }
    )



    /**
     * The dps of combat drones.
     */
    val DPS = DronePropertyEffectSource(
        value = DroneGroup::totalDps,
        description = "damage per second",
        cumulativeEffect = NonCumulative { value, isCell -> value.asDps(withUnits = isCell) }
    )


    /**
     * The volley damage by combat drones.
     */
    val VOLLEY = DronePropertyEffectSource(
        value = DroneGroup::totalVolleyDamage,
        description = "volley damage",
        cumulativeEffect = NonCumulative { value, isCell -> value.asDamage(withUnits = isCell) }
    )


    /**
     * For each damage type, its proportion in the total damage.
     */
    val DAMAGE_TYPE_PROPORTIONS: ValueByEnum<DamageType, DronePropertyEffectSource<Double>> = valueByEnum { damageType ->
        DronePropertyEffectSource(
            value = { _, drone -> drone.damageProportionByType[damageType] },
            description = "${damageType.displayName.lowercase()} damage",
            cumulativeEffect = NonCumulative { value, _ ->
                if (value == 0.0) null else (100 * value).asPercentage(precision = 0)
            },
        )
    }


    /**
     * The ECM strength by ECM drones.
     */
    val ECM_STRENGTH = DronePropertyEffectSource.fromProperty(
        property = { it.ecmStrength[SensorType.entries.first()] },
        description = "ECM strength",
        cumulativeEffect = NonCumulative{ value, _ -> value.asSensorStrength() }
    )


    /**
     * Returns a [DronePropertyEffectSource] for the chance to jam a target with the given sensor strength.
     */
    fun ecmChance(targetSensorStrength: Double) = DronePropertyEffectSource(
        value = { _, droneGroup ->
            jamChance(
                sensorType = SensorType.entries.first(),
                sensorStrength = targetSensorStrength,
                ecmDrones = listOf(droneGroup)
            )
        },
        description = "chance vs. sensor strength $targetSensorStrength",
        cumulativeEffect = JamChance
    )


    /**
     * The chance to jam the target fit.
     */
    val JAM_CHANCE = DronePropertyEffectSource(
        value = { fit, droneGroup ->
            jamChance(targetFit = fit, ecmDrones = listOf(droneGroup))
        },
        description = "chance of a successful jam",
        cumulativeEffect = JamChance
    )


}
