package theorycrafter.fitting

import androidx.compose.runtime.Stable
import eve.data.*
import eve.data.utils.ValueByEnum
import eve.data.utils.valueByEnum


/**
 * The base class for [Module] and [DroneGroup], as they share quite a lot of properties.
 */
@Stable
sealed class ModuleOrDrone<P: ModuleOrDroneType>(


    /**
     * The fit this module or drone is part of.
     */
    override val fit: Fit,


    /**
     * The context [Attributes].
     */
    attributes: Attributes,


    /**
     * The item type.
     */
    type: P


): EveItem<P>(attributes, type), FitItem {


    /**
     * The property for the volley damage of each type done by this module or drone; `null` if this module or drone does
     * no damage.
     */
    abstract val volleyDamageByType: ValueByEnum<DamageType, AttributeProperty<Double>?>


    /**
     * The volley damage pattern of this module or drone; `null` if this module or drone does no damage.
     */
    val volleyPattern: DamagePattern?
        get() {
            val volleyDamageAttributes = this.volleyDamageByType
            if (volleyDamageAttributes.values.all { it == null })
                return null

            return DamagePattern {
                volleyDamageAttributes[it]?.doubleValue ?: 0.0
            }
        }


    /**
     * The total volley damage done by this module or drone; `null` if it does no damage.
     * The value will be `null` for weapons that do no damage when unloaded.
     */
    val volleyDamage: Double?
        get() = volleyDamageByType.values
            .sumOf { it?.doubleValue ?: 0.0 }
            .takeIf { it != 0.0 }


    /**
     * The proportion of each damage type in the total damage of this module or drone; `null` if this module or drone
     * does no damage of the type (but could also be non-null with a value of 0).
     */
    val damageProportionByType: ValueByEnum<DamageType, Double?>
        get() {
            val totalVolley = volleyDamage
            return valueByEnum { damageType ->
                if (totalVolley == null) return@valueByEnum null
                val volleyOfType = volleyDamageByType[damageType]?.doubleValue ?: return@valueByEnum null
                volleyOfType / totalVolley
            }
        }


    /**
     * The damage multiplier of this module or drone.
     */
    val damageMultiplier: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.damageMultiplier)


    /**
     * The duration of the module or drone's activation cycle, in milliseconds; `null` if this module or drone is either
     * not activable, or if it does not have an activation cycle (cloaking devices, for example).
     */
    open val activationDuration: AttributeProperty<Double>?
        get() = type.durationAttribute?.let { property(it) }


    /**
     * Returns the value of the given property divided by the module or drone's activation duration in seconds.
     * Note that this doesn't take reactivation delay or reload time into account.
     */
    protected fun Double.perSecond(): Double? {
        val duration = activationDuration?.doubleValue ?: return null
        return amountPerSecond(amountPerActivation = this, durationMs = duration)
    }


    /**
     * The module or drone's optimal range in meters; `null` if irrelevant.
     */
    open val optimalRange: AttributeProperty<Double>? = type.optimalRangeAttribute?.let { property(it) }


    /**
     * The module or drone's falloff range in meters; `null` if irrelevant.
     */
    val falloffRange: AttributeProperty<Double>? = type.falloffRangeAttribute?.let { property(it) }


    /**
     * The module or drone's tracking speed; `null` if irrelevant.
     */
    val trackingSpeed: AttributeProperty<Double>? = type.trackingSpeedAttribute?.let { property(it) }


    /**
     * The module or drone's signature resolution; `null` if irrelevant.
     */
    val signatureResolution: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.signatureResolution)


    /**
     * The amount of shield HP boosted by this (shield booster) module or (shield maintenance) drone in one activation.
     */
    val shieldHpBoosted: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.shieldBoost)


    /**
     * The amount of armor HP repaired by this (armor repairer) module or (armor maintenance) drone per activation.
     */
    abstract val armorHpRepairedValue: Double?


    /**
     * The amount of structure HP repaired by this (hull repairer) module or (hull maintenance) drone per activation.
     */
    val structureHpRepaired: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.structureDamageAmount)


    /**
     * The amount of shield HP boosted per second by this (shield booster) module (ignoring reload time for ancillary
     * boosters) or (shield maintenance) drone.
     */
    val shieldHpBoostedPerSecond: Double?
        get() = shieldHpBoosted?.doubleValue?.perSecond()


    /**
     * The amount of armor HP repaired per second by this (armor repairer) module (ignoring reload time for ancillary
     * boosters) or (armor maintenance) drone.
     */
    val armorHpRepairedPerSecond: Double?
        get() = armorHpRepairedValue?.perSecond()


    /**
     * The amount of armor HP repaired per second by this (hull repairer) module or (hull maintenance) drone.
     */
    val structureHpRepairedPerSecond: Double?
        get() = structureHpRepaired?.doubleValue?.perSecond()


    /**
     * The bonus to speed this module or drone grants, including negative bonus by webifying modules/drones.
     */
    val speedFactorBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.speedFactor)


    /**
     * The amount of energy this module or drone neutralizes from the target in one activation.
     */
    val energyNeutralized: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.energyNeutralizerAmount)


    /**
     * The amount of energy this module or drone neutralizes from the target per second.
     */
    val energyNeutralizedPerSecond: Double?
        get() = energyNeutralized?.doubleValue?.perSecond()


    /**
     * The ECM strength of this module or drone against each sensor type; `null` if this is not an ECM module.
     */
    val ecmStrength: ValueByEnum<SensorType, AttributeProperty<Double>?> by lazy {
        valueByEnum { sensorType ->
            propertyOrNull(attributes.ecmStrength[sensorType])
        }
    }


    /**
     * Returns the period of time for which a successful jam by this module or drone remains in effect.
     * A value of 0 indicates this module merely breaks the lock momentarily.
     */
    val ecmJamDuration: Double?
        get() {
            if (ecmStrength.values.all { it == null })  // Not an ECM module
                return null

            // Regular ECM modules don't have an ecmJamDuration attribute; it's implied that the jam duration
            // is their activation duration.
            return propertyOrNull(attributes.ecmJamDuration)?.doubleValue
                ?: (this as? Module)?.aoeDuration?.doubleValue  // ECM Jammer Burst Projector
                ?: activationDuration?.doubleValue
        }


    /**
     * The bonus to targeting range this module or drone grants, either remote or self, including negative bonus by
     * sensor dampening modules or drones.
     */
    val targetingRangeBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.targetingRangeBonus)


    /**
     * The bonus to scan resolution this module or drone grants, either remote or self, including negative bonus by
     * sensor dampening modules or drones.
     */
    val scanResolutionBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.scanResolutionBonus)


    /**
     * The (negative) bonus to target signature radius this (target painter) module or drone.
     */
    val signatureRadiusBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.signatureRadiusBonus)


    /**
     * The bonus (including negative) to optimal range.
     */
    val optimalRangeBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.optimalRangeBonus)


    /**
     * The bonus (including negative) to falloff.
     */
    val falloffBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.falloffBonus)


    /**
     * The bonus (including negative) to tracking speed.
     */
    val trackingSpeedBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.trackingSpeedBonus)


    /**
     * The amount mined per activation, in m^3.
     */
    val miningAmount: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.miningAmount)


    /**
     * The mining residue propbability, as a percentage.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val miningResidueProbability: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.miningResidueProbability)


    /**
     * The mining residue volume multiplier.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val miningResidueVolumeMultiplier: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.miningResidueVolumeMultiplier)


    /**
     * The mining amounts.
     */
    val miningInfo: MiningInfo?
        get() {
            val miningAmount = this.miningAmount?.doubleValue ?: return null
            val miningResidueProbability = this.miningResidueProbability?.doubleValue
            val miningResidueVolumeMultiplier = this.miningResidueVolumeMultiplier?.doubleValue
            val duration = this.activationDuration?.doubleValue ?: return null

            return if ((miningResidueProbability == null) || (miningResidueVolumeMultiplier == null)) {
                MiningInfo(
                    minedPerHour = (miningAmount / duration) * 1000 * 60 * 60,
                    residuePerHour = null
                )
            }
            else {
                val minedPerHour = (miningAmount / duration) * 1000 * 60 * 60
                MiningInfo(
                    minedPerHour = minedPerHour,
                    residuePerHour = minedPerHour * (miningResidueProbability / 100) * miningResidueVolumeMultiplier
                )
            }
        }


}


