package theorycrafter.fitting

import androidx.compose.runtime.*
import eve.data.DamageType
import eve.data.EveData
import eve.data.ModuleType
import eve.data.SensorType
import eve.data.utils.ValueByEnum
import eve.data.utils.valueByEnum


/**
 * An instance of a module.
 */
@Stable
class Module internal constructor(


    /**
     * The fit this module is part of.
     */
    fit: Fit,


    /**
     * The context [EveData].
     */
    private val eveData: EveData,


    /**
     * The module type.
     */
    type: ModuleType


):
    ModuleOrDrone<ModuleType>(fit, eveData.attributes, type),
    // FitItemWithEnabledState to allow rigs to be disabled, even though their Module.State is always ONLINE
    FitItemWithEnabledState
{


    override val enabledState = EnabledState()


    /**
     * Module states.
     */
    enum class State {

        OFFLINE,
        ONLINE,
        ACTIVE,
        OVERLOADED;


        /**
         * Returns whether this state implies the module is online.
         */
        fun isAtLeastOnline() = this >= ONLINE


        /**
         * Returns whether this state implies the module is active.
         */
        fun isAtLeastActive() = this >= ACTIVE


    }


    /**
     * This module's current state.
     */
    var state: State by mutableStateOf(State.OFFLINE)
        internal set


    /**
     * Whether this module is online.
     */
    val online: Boolean
        get() = state.isAtLeastOnline()


    /**
     * Whether this module is active.
     */
    val active: Boolean
        get() = state.isAtLeastActive()


    /**
     * Returns whether this module can load charges.
     */
    val canLoadCharges: Boolean
        get() = type.canLoadCharges


    /**
     * The charge loaded into this module, as a [MutableState]; `null` if this module can't load charges.
     */
    internal val loadedChargeState: MutableState<Charge?>? =
        if (canLoadCharges)
            mutableStateOf(null)
        else
            null


    /**
     * The charge currently loaded into this module; `null` if none.
     */
    val loadedCharge: Charge?
        get() = loadedChargeState?.value


    /**
     * The amount of modules of this module type's group ID that can be online at the same time.
     * A `null` property means there is no limit.
     */
    val maxGroupOnline: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.maxGroupOnline)


    /**
     * The amount of modules of this module type's group ID that can be active at the same time.
     * A `null` property means there is no limit.
     */
    val maxGroupActive: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.maxGroupActive)


    /**
     * The amount of power used by this module; `null` if this module uses no power (e.g. it's a rig).
     */
    val powerNeed: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.power)


    /**
     * The amount of CPU used by this module; `null` if this module uses no CPU (e.g. it's a rig)
     */
    val cpuNeed: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.cpu)


    /**
     * The calibration used by this rig; `null` if this is not a rig.
     */
    val calibrationNeed: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.calibrationNeed)


    /**
     * The amount of capacitor (in GJ) used by this module for one activation.
     */
    val capacitorNeed: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.capacitorNeed)


    /**
     * The volley damage of each type done by this module; `null` if this module does no damage.
     */
    override val volleyDamageByType: ValueByEnum<DamageType, AttributeProperty<Double>?> by lazy {
        valueByEnum { damageType ->
            propertyOrNull(attributes.weaponDamage[damageType])
        }
    }


    /**
     * The time to reload charges in this module, in milliseconds; `null` if irrelevant.
     */
    val reloadTime: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.reloadTime)


    /**
     * The time before a module can be activated again, after having been activated, in milliseconds.
     */
    val reactivationDelay: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.moduleReactivationDelay)


    /**
     * The damage per second done by this module; `null` if none.
     */
    val dps: Double?
        get() {
            val volleyDamage = this.volleyDamage ?: return null
            val duration = this.activationDuration?.value ?: return null
            val reactivationDelay = this.reactivationDelay?.value ?: 0.0

            return dps(volleyDamage, duration, reactivationDelay)
        }


    /**
     * The property that boosts shield resist of each type, for shield-hardening modules.
     */
    val shieldResonanceBonus: ValueByEnum<DamageType, AttributeProperty<Double>?> by lazy {
        valueByEnum { damageType ->
            propertyOrNull(attributes.shieldResonance[damageType])
        }
    }


    /**
     * The property that boosts armor resist of each type, for armor-hardening modules.
     */
    val armorResonanceBonus: ValueByEnum<DamageType, AttributeProperty<Double>?> by lazy {
        valueByEnum { damageType ->
            propertyOrNull(attributes.armorResonance[damageType])
        }
    }


    /**
     * The property that boosts structure resist of each type, for structure-hardening modules.
     */
    val structureResonanceBonus: ValueByEnum<DamageType, AttributeProperty<Double>?> by lazy {
        valueByEnum { damageType ->
            propertyOrNull(attributes.structureResonanceBonus[damageType])
        }
    }


    /**
     * The warp disruption strength for warp disruptors, scramblers etc., but also the stabilization strength (negative)
     * value for Warp Core Stabilizers.
     */
    val warpDisruptionStrength: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.warpDisruptionStrength)


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


    /**
     * The bonus (including negative) to missile flight time.
     */
    val missileFlightTimeBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.missileFlightTimeBonus)


    /**
     * The bonus (including negative) to missile explosion velocity.
     */
    val explosionVelocityBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.explosionVelocityBonus)


    /**
     * The bonus (including negative) to missile explosion radius.
     */
    val explosionRadiusBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.explosionRadiusBonus)


    /**
     * The explosion radius (for Vorton Projectors, Super Weapons).
     */
    val explosionRadius: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.aoeCloudSize)


    /**
     * The explosion velocity (for Vorton Projectors).
     */
    val explosionVelocity: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.aoeVelocity)


    /**
     * The damage reduction factor (for Vorton Projectors).
     */
    val damageReductionFactor: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.aoeDamageReductionFactor)


    /**
     * The speed of the tractor beam.
     */
    val maxTractorVelocity: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.maxTractorVelocity)


    /**
     * The gigajoules-per-second of capacitor given by this capacitor booster (must have charges loaded).
     */
    val capacitorBoostedPerSecond: Double?
        get() {
            if (!type.hasEffect(eveData.effects.powerBooster))
                return null

            val loadedCharge = this.loadedCharge ?: return null
            val capacitorBonus = loadedCharge.type.capacitorBonus ?: return null
            val chargeCount = maxLoadedChargeAmount(type, loadedCharge.type) ?: return null
            val duration = activationDuration?.value ?: return null
            val reloadTime = reloadTime?.value ?: 0.0

            return capBoostedPerSecond(
                capacitorBonus = capacitorBonus,
                chargeCount = chargeCount,
                durationMs = duration,
                reloadTimeMs = reloadTime
            )
        }


    /**
     * The property for the amount of armor HP repaired by this armor repairer per activation (before the charged
     * (ancillary) repairing bonus is applied to it).
     */
    val unchargedArmorHpRepaired: AttributeProperty<Double>? = propertyOrNull(attributes.armorRepairAmount)


    /**
     * The amount of armor HP repaired by this (local or remote) armor repairer per activation.
     */
    override val armorHpRepairedValue: Double?
        get() {
            val armorRepairAmount = propertyOrNull(attributes.armorRepairAmount)?.value ?: return null
            if (loadedCharge == null)
                return armorRepairAmount

            val chargedMultiplier =
                propertyOrNull(attributes.chargedArmorDamageMultiplier)?.value ?: return armorRepairAmount
            return armorRepairAmount * chargedMultiplier
        }


    /**
     * For modules with charges and a long reload cycle (ancillary repairers, rapid launchers etc.), their effect per
     * the entire clip before needing to reload, together with the length of the cycle and number of activations in it.
     */
    class ValuePerClip<T>(

        /**
         * The value itself.
         */
        val value: T,

        /**
         * The time until the module needs to be reloaded (not including reload time).
         */
        val clipTimeMillis: Double,

        /**
         * The number of activations until the module needs to be reloaded.
         */
        val activationCount: Int

    )


    /**
     * Returns a property of [ValuePerClip] whose value is the given property multiplied by the number of activations
     * per clip cycle.
     */
    fun Double.perClip(): ValuePerClip<Double>? {
        val loadedCharge = loadedCharge ?: return null
        val duration = activationDuration?.value ?: return null
        val chargesConsumedPerActivation = type.chargesConsumed ?: return null

        val chargeCount = maxLoadedChargeAmount(type, loadedCharge.type) ?: return null
        val activationCount = chargeCount / chargesConsumedPerActivation

        return ValuePerClip(
            value = this * activationCount,
            clipTimeMillis = activationCount * duration,
            activationCount = activationCount
        )
    }


    /**
     * The amount of shield HP boosted per clip by an ancillary (possibly remote) shield booster.
     */
    val shieldHpBoostedPerClip: ValuePerClip<Double>?
        get() = shieldHpBoosted?.value?.perClip()


    /**
     * The amount of armor HP repaired per clip by an ancillary (possibly remote) armor repairer.
     */
    val armorHpRepairedPerClip: ValuePerClip<Double>?
        get() = armorHpRepairedValue?.perClip()


    /**
     * The amount of energy this module transfers in one activation (nosferatus, remote capacitor transmitters).
     */
    val energyTransferred: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.energyTransferAmount)


    /**
     * The amount of energy this module transfers per second.
     */
    val energyTransferredPerSecond: Double?
        get() = energyTransferred?.value?.perSecond()


    /**
     * The bonus to sensor strength this module grants.
     */
    val sensorStrengthBonus: ValueByEnum<SensorType, AttributeProperty<Double>?> by lazy {
        valueByEnum { sensorType ->
            propertyOrNull(attributes.sensorStrengthBonus[sensorType])
        }
    }


    /**
     * The id of fuel type consumed by the activation.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val consumptionTypeId: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.consumptionTypeId)


    /**
     * The amount of fuel, e.g. Liquid Ozone, consumed by an activation of this module.
     */
    @Suppress("MemberVisibilityCanBePrivate")
    val consumptionQuantity: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.consumptionQuantity)


    /**
     * Combines the fuel consumption type and quantity.
     */
    data class FuelConsumption(
        val fuelTypeId: Int,
        val quantity: Double
    )


    /**
     * Combines the fuel type and the amount of it consumed into one property.
     */
    val fuelConsumption: FuelConsumption?
        get() {
            val fuelType = consumptionTypeId?.value ?: return null
            val fuelQuantity = consumptionQuantity?.value ?: return null

            return FuelConsumption(fuelType, fuelQuantity)
        }


    /**
     * The access difficulty (in salvager modules), or bonus to access difficulty (in salvage tackle rigs).
     * Also applies to data and relic analyzers.
     */
    val accessDifficultyBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.accessDifficultyBonus)


    /**
     * The virus coherence, for data and relic analyzers.
     */
    val virusCoherence: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.virusCoherence)


    /**
     * The virus utility element slots, for data and relic analyzers.
     */
    val virusElementSlots: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.virusElementSlots)


    /**
     * The virus strength, for data and relic analyzers.
     */
    val virusStrength: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.virusStrength)


    /**
     * The radius within which this Micro Jump Field Generator jumps other ships.
     */
    val mjfgRadius: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.mjfgRadius)


    /**
     * The cap on the amount of ships this Micro Jump Field Generator can jump.
     */
    val mjdShipJumpCap: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.mjdShipJumpCap)


    /**
     * The scan strength bonus of scan probe launchers.
     */
    val scanStrengthBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.scanStrengthBonus)


    /**
     * The literal "duration" attribute. Note that this does not always refer to the activation duration of the module;
     * for that use [Module.activationDuration].
     * This, for example, is used for the scan time of scan probe launchers.
     */
    val literalDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.duration)


    /**
     * The bonus this module gives to drone damage.
     */
    val droneDamageBonus: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.droneDamageBonus)


    /**
     * The targeting delay (in milliseconds) after decloaking induced by this cloaking device.
     */
    val decloakTargetingDelay: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.decloakTargetingDelay)


    /**
     * The number of spoolup cycles for this Triglavian module (disintegrator or mutadaptive remote armor repairer).
     *
     * This value can be set via [setSpoolupCycles].
     */
    val spoolupCycles: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.spoolupCycles)


    /**
     * Sets the number of spoolup cycles on this triglavian module.
     */
    context(FittingEngine.ModificationScope)
    fun setSpoolupCycles(cycleCount: Double) {
        setPinnedPropertyValue(attributes.spoolupCycles, cycleCount)
    }


    /**
     * The time it takes for the module to spool up to [spoolupCycles] number of cycles.
     */
    val spoolupTime: Double?
        get() {
            val spoolupCycles = spoolupCycles?.value ?: return null
            val activationDuration = activationDuration?.value ?: return null

            return spoolupCycles * activationDuration
        }


    /**
     * The maximum number of spoolup cycles for this Triglavian module; `null` if this isn't a spooling module.
     */
    val maxSpoolupCycles: Double?
        get() {
            val maxBonus = propertyOrNull(attributes.damageMultiplierBonusMax)
                ?: propertyOrNull(attributes.repairMultiplierBonusMax) ?: return null
            val cycleBonus = propertyOrNull(attributes.damageMultiplierBonusPerCycle)
                ?: propertyOrNull(attributes.repairMultiplierBonusPerCycle) ?: return null
            return maxBonus.value / cycleBonus.value
        }


    /**
     * The number of cycles this module (currently only Reactive Armor Hardener) has had to adapt.
     */
    val adaptationCycles: AttributeProperty<Int>?
        get() = propertyOrNull(attributes.adaptationCycles)


    /**
     * Sets the number of cycles this Reactive Armor Hardener has had to adapt.
     */
    context(FittingEngine.ModificationScope)
    fun setAdaptationCycles(cycleCount: Int) {
        setPinnedPropertyValue(attributes.adaptationCycles, cycleCount)
    }


    /**
     * The "stable" cloak duration of this cloaking device, in seconds.
     */
    val stabilizeCloakDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.stabilizeCloakDuration)


    /**
     * The duration of the command link burst effect, and PANIC module.
     */
    val buffDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.warfare.buffDuration)


    /**
     * The duration of an AOE effect.
     */
    val aoeDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.aoeDuration)


    /**
     * The range of an AOE effect.
     */
    val aoeRange: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.aoeRange)


    /**
     * The duration of a superweapon's (e.g. Lance) damage effect.
     */
    val superWeaponDamageDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.superWeaponDamageDuration)


    /**
     * The period between each damage dealing of a superweapon (e.g. Lance).
     */
    val superWeaponDamageCycleTime: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.superWeaponDamageCycleTime)


    /**
     * The delay before an effect from a superweapon (e.g. Lance) starts applying.
     */
    val superWeaponDelayDuration: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.superWeaponWarningDuration)


    /**
     * The signature radius of the weapon (for superweapons).
     */
    val signatureRadius: AttributeProperty<Double>?
        get() = propertyOrNull(attributes.signatureRadius)


}


/**
 * Returns the index of the module within its rack.
 */
fun Module.indexInRack() = this.fit.modules.slotsInRack(type.slotType).indexOf(this)