package theorycrafter.fitting

import androidx.compose.runtime.*
import eve.data.*
import eve.data.typeid.isDefenderMissileLauncher
import eve.data.utils.ValueByEnum
import eve.data.utils.mapValues
import eve.data.utils.valueByEnum
import theorycrafter.fitting.utils.mapEach
import theorycrafter.fitting.utils.sumOfNullable
import java.util.Collections
import kotlin.math.min


/**
 * A ship's fit.
 */
@Stable
class Fit internal constructor(


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


    /**
     * The character piloting the ship.
     */
    val character: Character,


    /**
     * The ship being fitted.
     */
    val ship: Ship,


    /**
     * The warfare buffs item, through which command bursts are applied to this fit.
     */
    val warfareBuffs: WarfareBuffs,


    /**
     * The fit for which this fit is an auxiliary fit; `null` if this is a primary fit.
     */
    val auxiliaryFitOf: Fit?


) {


    /**
     * Returns whether this is an auxiliary fit.
     */
    val isAuxiliary: Boolean
        get() = auxiliaryFitOf != null


    /**
     * The context attributes.
     */
    private val attributes = eveData.attributes


    /**
     * Fitting properties.
     */
    val fitting = Fitting()


    /**
     * Defensive ship properties (shield, armor, etc.)
     */
    val defenses = ItemDefenses(
        item = ship,
        attributes = attributes,
        damagePattern = { incomingDamage.patternOrUniform },
        localModules = { modules.active },
        friendlyEffects = { remoteEffects.friendly.activeIncludingAuxiliary },
    )


    /**
     * Capacitor properties.
     */
    val capacitor = Capacitor()


    /**
     * Targeting properties (range, sensor strength, etc.)
     */
    val targeting = Targeting()


    /**
     * Propulsion properties (maximum velocity etc.)
     */
    val propulsion = Propulsion()


    /**
     * Drone properties (capacity, bandwidth, etc.)
     */
    val drones = Drones()


    /**
     * Electronic warfare properties.
     */
    val electronicWarfare = ElectronicWarfare()


    /**
     * The fitted modules.
     */
    val modules = Modules()


    /**
     * The cargohold.
     */
    val cargohold = Cargohold()


    /**
     * The fitted implants.
     */
    val implants = Implants()


    /**
     * The fitted boosters.
     */
    val boosters = Boosters()


    /**
     * Firepower properties (volley, dps etc.)
     */
    val firepower = Firepower()


    /**
     * The remote repair capabilities.
     */
    val remoteRepairs = RemoteRepairs()


    /**
     * Whether the ship of this fit has tactical modes.
     */
    val hasTacticalModes: Boolean
        get() = ship.type.hasTacticalModes


    /**
     * The fit's tactical mode, as a [MutableState]; `null` if the ship doesn't have tactical modes.
     */
    internal var tacticalModeState: MutableState<TacticalMode?>? =
        if (hasTacticalModes) mutableStateOf(null) else null


    /**
     * The fit's tactical mode; `null` if none.
     */
    val tacticalMode: TacticalMode?
        get() = tacticalModeState?.value


    /**
     * The fit's fitted subsystem state, by kind; `null` if the ship doesn't use subsystems.
     *
     * The subsystem values themselves can be `null` only when the fit is being created because we immediately set
     * the initial or stored subsystems.
     */
    internal val subsystemStateByKind: ValueByEnum<SubsystemType.Kind, MutableState<Subsystem?>>? =
        if (ship.type.usesSubsystems)
            valueByEnum { mutableStateOf(null) }
        else
            null


    /**
     * The fit's fitted subsystem, by kind; `null` if the ship doesn't use subsystems.
     */
    val subsystemByKind: ValueByEnum<SubsystemType.Kind, Subsystem?>?
        get() = subsystemStateByKind?.mapValues { it.value }


    /**
     * The remote effects applied to this fit.
     */
    val remoteEffects = RemoteEffects()


    /**
     * The incoming damage applied to this fit.
     */
    val incomingDamage = IncomingDamage()


    /**
     * The environments applied to this fit.
     */
    var environments: List<Environment> by mutableStateOf(emptyList())
        internal set


    /**
     * Extra attributes that may be needed by other libraries.
     */
    val extras: MutableMap<String, Any> = mutableStateMapOf()


    /**
     * Snapshot (Compose) state that changes whenever anything about the fit changes.
     */
    private val changeKeyState = mutableIntStateOf(0)


    /**
     * Snapshot (Compose) state that changes whenever anything about the fit changes, for internal use.
     * This can be used as a key to any computations that depend on this fit.
     */
    val changeKey: Any by changeKeyState


    /**
     * Called by the [FittingEngine] whenever anything about this fit changes.
     */
    internal fun onChanged() {
        changeKeyState.value += 1
    }


    /**
     * Some kind of numeric resource the ship has (PG, CPU etc.)
     */
    class Resource<T: Number> internal constructor(
        val totalProperty: AttributeProperty<T>,
        private val usedGetter: () -> T,
        val minus: (T, T) -> T
    ) {


        /**
         * The total amount of the resource the ship has.
         */
        val total: T
            get() = totalProperty.value


        /**
         * The amount of the resource used by the fit.
         */
        val used: T
            get() = usedGetter()


        /**
         * The amount of available/remaining resource.
         */
        val available: T
            get() = minus(total, used)


    }


    /**
     * Returns an [Int] resource.
     */
    internal fun IntResource(totalProperty: AttributeProperty<Int>, usedGetter: () -> Int)
        = Resource(totalProperty, usedGetter, Int::minus)


    /**
     * Returns a [Double] resource.
     */
    internal fun DoubleResource(totalProperty: AttributeProperty<Double>, usedGetter: () -> Double)
            = Resource(totalProperty, usedGetter, Double::minus)


    /**
     * Ship fitting properties.
     */
    inner class Fitting {


        /**
         * Slot layout.
         */
        val slots = Slots()


        /**
         * CPU output of the ship.
         */
        val cpu: Resource<Double> = DoubleResource(
            totalProperty = ship.cpuOutput,
            usedGetter = { modules.online.sumOf { it.cpuNeed?.doubleValue ?: 0.0 } }
        )


        /**
         * Power grid of the ship.
         */
        val power: Resource<Double> = DoubleResource(
            totalProperty = ship.powerOutput,
            usedGetter = { modules.online.sumOf { it.powerNeed?.doubleValue ?: 0.0 } }
        )


        /**
         * Calibration of the ship.
         */
        val calibration: Resource<Int> = IntResource(
            totalProperty = ship.calibration,
            usedGetter = { modules.rigs.sumOf { it.calibrationNeed?.value ?: 0 } }
        )


        /**
         * Number of turret hardpoints on the ship.
         */
        val turretHardpoints: Resource<Int> = IntResource(
            totalProperty = ship.turretHardpoints,
            usedGetter = { modules.high.count { it.type.usesTurretHardpoint } }
        )


        /**
         * Number of launcher hardpoints on the ship.
         */
        val launcherHardpoints: Resource<Int> = IntResource(
            totalProperty = ship.launcherHardpoints,
            usedGetter = { modules.high.count { it.type.usesLauncherHardpoint } }
        )


        /**
         * The slot layout.
         */
        inner class Slots {


            /**
             * Number of high slots on the ship.
             */
            val high: AttributeProperty<Int>
                get() = ship.property(attributes.highSlots)


            /**
             * Number of medium slots on the ship.
             */
            val medium: AttributeProperty<Int>
                get() = ship.property(attributes.medSlots)


            /**
             * Number of low slots on the ship.
             */
            val low: AttributeProperty<Int>
                get() = ship.property(attributes.lowSlots)


            /**
             * Number of rig slots on the ship.
             */
            val rig: AttributeProperty<Int>
                get() = ship.property(attributes.rigSlots)


            /**
             * Returns the number of slots of the given type.
             */
            operator fun get(moduleSlotType: ModuleSlotType): Int = when(moduleSlotType){
                ModuleSlotType.HIGH -> high.value
                ModuleSlotType.MEDIUM -> medium.value
                ModuleSlotType.LOW -> low.value
                ModuleSlotType.RIG -> rig.value
            }


        }


    }


    /**
     * Firepower.
     */
    inner class Firepower {


        /**
         * Whether the given module's damage should be ignored in dps calculations.
         */
        private fun Module.ignoreDamage(): Boolean = with(eveData) { type.isDefenderMissileLauncher() }


        /**
         * The weapon volley damage.
         */
        val weaponVolleyDamage: Double
            get() = modules.all.sumOfNullable {
                if (it.ignoreDamage()) 0.0 else it.volleyDamage
            }


        /**
         * The weapon damage per second, for each damage type.
         */
        val weaponDpsPattern: DamagePattern
            get() = modules.all.sumOfDamagePatterns {
                if (it.ignoreDamage()) null else it.dpsPattern
            }


        /**
         * The weapon damage per second.
         */
        val weaponDps: Double
            get() = modules.all.sumOfNullable {
                if (it.ignoreDamage()) 0.0 else it.dps
            }


        /**
         * The drone volley damage.
         */
        val droneVolleyDamage: Double
            get() = drones.all.sumOfNullable { it.totalVolleyDamage }


        /**
         * The drone damage per second, for each damage type.
         */
        val droneDpsPattern: DamagePattern
            get() = drones.all.sumOfDamagePatterns { it.totalDpsPattern }


        /**
         * The drone damage per second.
         */
        val droneDps: Double
            get() = drones.all.sumOfNullable { it.totalDps }


        /**
         * The combined (weapon + drone) DPS per second.
         */
        val totalDps: Double
            get() = droneDps + weaponDps


        /**
         * The combined (weapon + drone) damage per second, for each damage type.
         */
        val dpsPattern: DamagePattern
            get() = weaponDpsPattern + droneDpsPattern


    }


    /**
     * A ship's capacitor properties.
     */
    inner class Capacitor {


        /**
         * Total capacitor capacity, in GJ.
         */
        val capacity: AttributeProperty<Double>
            get() = ship.property(attributes.capacitorCapacity)


        /**
         * Recharge time, in milliseconds.
         */
        val rechargeTime: AttributeProperty<Double>
            get() = ship.property(attributes.capacitorRechargeTime)


        /**
         * Capacitor peak (natural) recharge rate, in GJ/sec.
         */
        val peakRechargeRate: Double
            get() = maxRechargeRate(maxValue = capacity.doubleValue, rechargeTime = rechargeTime.doubleValue)


        /**
         * The state for the modules relevant to capacitor stability.
         */
        private val localModulesRelevantForCapacitorStability = derivedStateOf {
            modules.active.filter(Module::isRelevantForCapacitorStabilityAsLocalModule)
        }


        /**
         * The state for remote hostile items relevant to capacitor stability.
         */
        private val hostileItemsRelevantForCapacitorStability = derivedStateOf {
            remoteEffects.hostile.activeIncludingAuxiliary
                .flatMap {
                    it.affectingModules + it.affectingDrones
                }
                .filter(ModuleOrDrone<*>::isRelevantForCapacitorStabilityAsHostileItem)
        }


        /**
         * The state for remote friendly modules relevant to capacitor stability.
         */
        private val friendlyModulesRelevantForCapacitorStability = derivedStateOf {
            remoteEffects.friendly.activeIncludingAuxiliary
                .flatMap { it.affectingModules }
                .filter(Module::isRelevantForCapacitorStabilityAsFriendlyModule)
        }


        /**
         * Capacitor stability, given the active fitted modules and remote effects.
         */
        val stability: CapacitorStability by derivedStateOf {
            capacitorStability(
                totalCapacity = capacity.doubleValue,
                rechargeTime = rechargeTime.doubleValue,
                energyNeutralizationResistance = electronicWarfare.energyNeutralizationResistance.doubleValue,
                localModules = localModulesRelevantForCapacitorStability.value,
                hostileItems = hostileItemsRelevantForCapacitorStability.value,
                friendlyModules = friendlyModulesRelevantForCapacitorStability.value
            )
        }


        /**
         * Returns a new [CapacitorSimulation] for this fit.
         */
        fun simulation(): CapacitorSimulation = capacitorSimulation(
            totalCapacity = capacity.doubleValue,
            rechargeTime = rechargeTime.doubleValue,
            energyNeutralizationResistance = electronicWarfare.energyNeutralizationResistance.doubleValue,
            localModules = localModulesRelevantForCapacitorStability.value,
            hostileItems = hostileItemsRelevantForCapacitorStability.value,
            friendlyModules = friendlyModulesRelevantForCapacitorStability.value
        )


    }



    /**
     * A ship's targeting properties.
     */
    inner class Targeting {


        /**
         * Maximum targeting range, in meters.
         */
        val targetingRange: AttributeProperty<Double>
            get() = ship.property(attributes.targetingRange)


        /**
         * Scan resolution, in millimeters.
         */
        val scanResolution: AttributeProperty<Double>
            get() = ship.property(attributes.scanResolution)


        /**
         * Maximum number of locked targets.
         * This is the minimum of the number of targets that can be locked by the ship and the number of targets that
         * the character's skill allow.
         */
        val maxLockedTargets: Int
            get() = min(ship.maxLockedTargets.value, character.maxLockedTargets.value)


        /**
         * Sensors.
         */
        val sensors: Sensors = Sensors()


        /**
         * Ship sensors.
         */
        inner class Sensors{


            /**
             * The sensor type of this ship.
             */
            val type: SensorType
                get() = ship.type.targeting.sensors.type


            /**
             * The sensor strength of this ship.
             */
            val strength: AttributeProperty<Double>
                get() = ship.property(attributes.sensorStrength[type])


        }


    }


    /**
     * The ship's propulsion properties.
     */
    inner class Propulsion {


        /**
         * Mass, in kg.
         */
        val mass: AttributeProperty<Double>
            get() = ship.mass


        /**
         * Inertia modifier (agility).
         */
        val inertiaModifier: AttributeProperty<Double>
            get() = ship.inertiaModifier


        /**
         * Maximum velocity without propulsion modules, in m/s.
         */
        val baseMaxVelocity: AttributeProperty<Double>
            get() = ship.property(attributes.maxVelocity)


        /**
         * The speed boost factor provided by a propulsion module.
         */
        val propulsionModuleSpeedFactor: AttributeProperty<Double>
            get() = ship.property(attributes.propulsionModuleSpeedFactor)


        /**
         * The thrust provided by a propulsion module.
         */
        val propulsionModuleThrust: AttributeProperty<Double>
            get() = ship.property(attributes.propulsionModuleThrust)


        /**
         * The speed limit (imposed by e.g. entosis links). A negative value indicates no limit.
         */
        val speedLimit: AttributeProperty<Double>
            get() = ship.property(attributes.speedLimit)


        /**
         * Maximum velocity, including propulsion modules, in m/s.
         */
        val maxVelocity: Double
            get() {
                val maxVelocity = maxVelocity(
                    baseMaxVelocity = baseMaxVelocity.doubleValue,
                    mass = mass.doubleValue,
                    propulsionModuleSpeedFactor = propulsionModuleSpeedFactor.doubleValue,
                    propulsionModuleThrust = propulsionModuleThrust.doubleValue,
                )

                val limit = speedLimit.doubleValue
                return if (limit >= 0)
                    maxVelocity.coerceAtMost(limit)
                else
                    maxVelocity
            }


        /**
         * Time it takes to align before entering warp, in milliseconds.
         */
        val alignTime: Double
            get() = alignTime(inertiaModifier = inertiaModifier.doubleValue, mass = mass.doubleValue)


        /**
         * The warp speed, in AU/s.
         */
        val warpSpeed: Double
            get() = ship.warpSpeed.doubleValue


    }


    /**
     * The ship's remote repair capabilities.
     */
    inner class RemoteRepairs {


        /**
         * The remote repairs by active modules.
         */
        val modules = Source { this@Fit.modules.active.filter { it.type.isProjected } }


        /**
         * The remote repairs by active drones.
         */
        val drones = Source { this@Fit.drones.active }


        /**
         * The total amount of repairs, by both modules and drones, to shields, armor and structure.
         */
        val total: Double
            get() =
                this@Fit.modules.active.sumOf {
                    if (it.type.isProjected)
                        (it.shieldHpBoostedPerSecond ?: 0.0) + (it.armorHpRepairedPerSecond ?: 0.0) + (it.structureHpRepairedPerSecond ?: 0.0)
                    else
                        0.0
                } +
                this@Fit.drones.active.sumOf {
                    (it.shieldHpBoostedPerSecond ?: 0.0) + (it.armorHpRepairedPerSecond ?: 0.0) + (it.structureHpRepairedPerSecond ?: 0.0)
                }


        /**
         * Provides the amount of remote repairs of shields/armor/structure by a given list of modules and/or drones.
         */
        inner class Source(private val sources: () -> Iterable<ModuleOrDrone<*>>) {


            /**
             * The amount of shield hitpoints repaired per second.
             */
            val shieldHpBoostedPerSecond: Double
                get() = sources().sumOfProperty(ModuleOrDrone<*>::shieldHpBoostedPerSecond)


            /**
             * The amount of armor hitpoints repaired per second.
             */
            val armorHpRepairedPerSecond: Double
                get() = sources().sumOfProperty(ModuleOrDrone<*>::armorHpRepairedPerSecond)


            /**
             * The amount of structure hitpoints repaired per second.
             */
            val structureHpRepairedPerSecond: Double
                get() = sources().sumOfProperty(ModuleOrDrone<*>::structureHpRepairedPerSecond)


            /**
             * Returns the sum of the given property for each module and drone in the list.
             */
            private inline fun Iterable<ModuleOrDrone<*>>.sumOfProperty(selector : (ModuleOrDrone<*>) -> Double?) = sumOf {
                val value = selector(it)
                when {
                    value == null -> 0.0
                    it is DroneGroup -> value * it.size
                    else -> value
                }
            }


        }


    }


    /**
     * The ship's drone properties.
     */
    inner class Drones {


        /**
         * The fitted drones, as a [MutableState], for internal use.
         */
        internal val allDronesState: MutableState<List<DroneGroup>> = mutableStateOf(emptyList())


        /**
         * All the fitted drones.
         */
        val all: List<DroneGroup> by allDronesState


        /**
         * The fitted active drones.
         */
        val active: List<DroneGroup>
            get() = all.filter { it.active }


        /**
        * The number of active drones.
         */
        val activeCount: Resource<Int> = IntResource(
            totalProperty = character.maxActiveDrones,
            usedGetter = {
                all.sumOf { droneGroup ->
                    if (droneGroup.active) droneGroup.size else 0
                }
            }
        )


        /**
         * The drone bay capacity, in m^3.
         */
        val capacity: Resource<Int> = IntResource(
            totalProperty = ship.droneCapacity,
            usedGetter = { all.sumOf { it.totalVolume } }
        )


        /**
         * The drone bandwidth, in megabit/s.
         */
        val bandwidth: Resource<Double> = DoubleResource(
            totalProperty = ship.droneBandwidth,
            usedGetter = { all.sumOf { it.totalUsedBandwidth }.toDouble() }
        )


        /**
         * The maximum range at which this character can control drones.
         */
        val controlRange: AttributeProperty<Double>
            get() = character.property(attributes.droneControlRange)


        /**
         * Whether the fit can use drones (both capacity and bandwidth greater than 0).
         */
        val canFitDrones: Boolean
            get() = (capacity.total > 0) && (bandwidth.total > 0)


    }


    /**
     * The ship's resistance to electronic warfare.
     *
     * The resistance factors here are actually susceptibility factors. The effect of the corresponding EWAR is
     * multiplied by this factor, meaning a value of 1.0 implies the EWAR has full effect, while a value of 0.0 implies
     * the fit is immune to the corresponding EWAR.
     */
    inner class ElectronicWarfare {


        /**
         * The resistance factor to energy neutralization.
         */
        val energyNeutralizationResistance: AttributeProperty<Double>
            get() = ship.property(attributes.energyWarfareResistance)


        /**
         * The resistance factor to weapon disruption.
         */
        val weaponDisruptionResistance: AttributeProperty<Double>
            get() = ship.property(attributes.weaponDisruptionResistance)


        /**
         * The resistance to factor sensor dampener.
         */
        val sensorDampenerResistance: AttributeProperty<Double>
            get() = ship.property(attributes.sensorDampenerResistance)


        /**
         * The resistance to target painters.
         */
        val targetPainterResistance: AttributeProperty<Double>
            get() = ship.property(attributes.targetPainterResistance)


        /**
         * The resistance to ECM.
         */
        val ecmResistance: AttributeProperty<Double>
            get() = ship.property(attributes.ecmResistance)


    }


    /**
     * Modules fitted to the ship.
     * Note that the size of the slot lists is the maximum possible number of the corresponding slots
     * (i.e. [ModuleSlotType.maxSlotCount]), because we want to allow "extra" modules when fitting strategic cruisers.
     * It's up to the caller to avoid fitting more modules than possible by checking [Fit.Fitting.slots].
     */
    inner class Modules {


        /**
        * The slots, as a list of [MutableState]s; for internal use.
         */
        internal val slotsByTypeStates: ValueByEnum<ModuleSlotType, List<MutableState<Module?>>> =
            valueByEnum { slotType ->
                val slotCount =
                    if (isAuxiliary && ((slotType == ModuleSlotType.MEDIUM) || slotType == ModuleSlotType.HIGH))
                        20  // Allocate more slots for auxiliary fits
                    else
                        slotType.maxSlotCount

                Collections.unmodifiableList(
                    Array(slotCount) {
                        mutableStateOf<Module?>(null)
                    }.asList()
                )
            }


        /**
         * The slots of the given slot type, not limited by the rack size.
         */
        fun slotsInRack(slotType: ModuleSlotType): Iterable<Module?> = slotsByTypeStates[slotType].currentValues()


        /**
         * The amount of slots of the given slot type, not limited by the rack size.
         */
        fun slotsInRackCount(slotType: ModuleSlotType): Int = slotsByTypeStates[slotType].size


        /**
        * Wrap this calculation in derivedStateOf, to avoid having the fitted modules themselves affect recomposition.
         */
        private val relevantSlotCountStateBySlotType: ValueByEnum<ModuleSlotType, State<Int>> =
            valueByEnum { slotType ->
                derivedStateOf(structuralEqualityPolicy()) {
                    // Do this carefully to avoid reading the valid slots, in order to minimize the changes that
                    // cause this computation to be re-run.
                    val fittingSlots = fitting.slots[slotType]
                    val extraSlots = slotsByTypeStates[slotType].let {
                        it.subList(fittingSlots, it.size)
                    }
                    val lastFittedIndexInExtraSlots = extraSlots.indexOfLast { it.value != null }
                    fittingSlots + lastFittedIndexInExtraSlots + 1  // It just works out if it's -1
                }
            }


        /**
         * The number of "relevant" slots of the given type: the maximum between the (legal) size of the rack and the
         * slot of the actual last fitted module (which can exceed the rack size).
         *
         * This is used to determine the number of slots to display to the user. This can exceed the rack size in case
         * of strategic cruisers that had their subsystem changed, or simply a fit that was stored to disk and then
         * loaded after its slot layout was changed.
         */
        fun relevantSlotCount(slotType: ModuleSlotType): Int {
            return relevantSlotCountStateBySlotType[slotType].value
        }


        /**
         * The current slots of the given type, limited by the slot count in the rack.
         */
        fun slotsLimitedByRackSize(slotType: ModuleSlotType): Iterable<Module?> {
            val slotCount = fitting.slots[slotType]
            return slotsByTypeStates[slotType].subList(0, slotCount).currentValues()
        }


        /**
         * The module currently fitted at the given slot type and index.
         */
        fun inSlot(slotType: ModuleSlotType, slotIndex: Int): Module? =
            slotsByTypeStates[slotType][slotIndex].value


        /**
         * The module currently fitted at the given slot.
         */
        fun inSlot(slot: ModuleSlot): Module? = inSlot(slot.type, slot.index)


        /**
         * Returns all the fitted modules that pass the given filter.
         */
        fun filtered(filter: (Module) -> Boolean): List<Module> = buildList {
            for (slotType in ModuleSlotType.entries) {
                for (moduleState in slotsByTypeStates[slotType]) {
                    val module = moduleState.value ?: continue
                    if (filter(module))
                        add(module)
                }
            }
        }



        /**
        * All the fitted modules.
         */
        val all: List<Module>
            get() = filtered { true }


        /**
         * All the online modules.
         */
        val online: Collection<Module>
            get() = filtered { it.online }


        /**
         * All the active modules.
         */
        val active: Collection<Module>
            get() = filtered { it.active }


        /**
         * Returns the modules in the given rack.
         */
        fun inRack(slotType: ModuleSlotType): Collection<Module> {
            return slotsByTypeStates[slotType].mapNotNull { it.value }
        }


        /**
         * All the fitted high-slot modules.
         */
        val high: Collection<Module>
            get() = inRack(ModuleSlotType.HIGH)


        /**
         * All the fitted medium-slot modules.
         */
        val medium: Collection<Module>
            get() = inRack(ModuleSlotType.MEDIUM)


        /**
         * All the fitted low-slot modules.
         */
        val low: Collection<Module>
            get() = inRack(ModuleSlotType.LOW)


        /**
         * All the fitted rigs.
         */
        val rigs: Collection<Module>
            get() = inRack(ModuleSlotType.RIG)


    }


    /**
     * Cargohold.
     */
    inner class Cargohold {


        /**
         * The cargohold contents.
         */
        var contents: List<CargoItem> by mutableStateOf(emptyList())
            internal set


        /**
         * Cargo capacity, in m^3.
         */
        val capacity: Resource<Double> = DoubleResource(
            totalProperty = ship.cargoCapacity,
            usedGetter = { contents.sumOfNullable(CargoItem::volume) }
        )


    }



    /**
     * Fitted implants.
     */
    inner class Implants {


        /**
         * The implant slots, as a list of [MutableState]s; for internal use.
         */
        internal val mutableSlots: List<MutableState<Implant?>> =
            List(eveData.implantTypes.slotCount) { mutableStateOf(null) }


        /**
         * The current implant slots.
         */
        val slots: Iterable<Implant?>
            get() = mutableSlots.currentValues()


        /**
         * The implant at the given slot.
         */
        fun inSlot(slotIndex: Int): Implant? =
            mutableSlots[slotIndex].value


        /**
         * All the fitted implants.
         */
        val fitted: List<Implant>
            get() = mutableSlots.mapNotNull { it.value }


    }


    /**
     * Remote effects applied to this fit.
     */
    inner class RemoteEffects {


        /**
         * The auxiliary fit, used for remote effects directly by modules/drones, without a fit.
         */
        internal var auxiliaryFit: Fit? by mutableStateOf(null)


        /**
         * The command effects on this fit.
         */
        val command = EffectSet(
            moduleEffectsFilter = { false },
            droneEffectsFilter = { false },
        )


        /**
         * The hostile effects on this fit.
         */
        val hostile = EffectSet(
            moduleEffectsFilter = { eveData.isOffensive(it.module.type) },
            droneEffectsFilter = { eveData.isOffensive(it.droneGroup.type) }
        )


        /**
         * The friendly effects on this fit.
         */
        val friendly = EffectSet(
            moduleEffectsFilter = { eveData.isAssistive(it.module.type) },
            droneEffectsFilter = { eveData.isAssistive(it.droneGroup.type) }
        )


        /**
         * All the module effects on this fit.
         */
        var allModule: List<ModuleEffect> by mutableStateOf(emptyList())
            internal set


        /**
         * All the drone effects on this fit.
         */
        var allDrone: List<DroneEffect> by mutableStateOf(emptyList())
            internal set


        /**
         * Bundles together effects of the same kind (command, friendly, hostile).
         */
        inner class EffectSet(
            private val moduleEffectsFilter: (ModuleEffect) -> Boolean,
            private val droneEffectsFilter: (DroneEffect) -> Boolean,
        ) {


            /**
             * All the [RemoteEffect]s, except for the effect by the auxiliar fit.
             */
            var allExcludingAuxiliary: List<RemoteEffect> by mutableStateOf(emptyList())
                internal set


            /**
             * The [RemoteEffect] by the auxiliary fit.
             */
            var auxiliaryEffect: RemoteEffect? by mutableStateOf(null)
                internal set


            /**
             * All the active [RemoteEffect]s, including the auxiliary one.
             */
            val activeIncludingAuxiliary: List<RemoteEffect> by derivedStateOf {
                val active = allExcludingAuxiliary.filter { it.enabled }
                val aux = auxiliaryEffect
                if (aux == null) active else active + aux
            }


            /**
             * The module effects.
             */
            val module: List<ModuleEffect> by derivedStateOf {
                allModule.filter(moduleEffectsFilter)
            }


            /**
             * The drone effects.
             */
            val drone: List<DroneEffect> by derivedStateOf {
                allDrone.filter(droneEffectsFilter)
            }


        }


    }


    /**
     * Fitted boosters.
     */
    inner class Boosters {


        /**
         * The booster slots, as a list of [MutableState]s; for internal use.
         */
        internal val mutableSlots: List<MutableState<Booster?>> =
            List(eveData.boosterSlotCount){ mutableStateOf(null) }


        /**
         * The booster slots.
         */
        val slots: Iterable<Booster?>
            get() = mutableSlots.currentValues()


        /**
         * The booster at the given slot.
         */
        fun inSlot(slotIndex: Int): Booster? =
            mutableSlots[slotIndex].value


        /**
         * All the fitted boosters.
         */
        val fitted: List<Booster>
            get() = mutableSlots.mapNotNull { it.value }


    }


    class IncomingDamage {


        var effects: List<DamageEffect> by mutableStateOf(emptyList())
            internal set


        val pattern: DamagePattern
            get() = effects.sumOfDamagePatterns {
                if (it.enabled) it.damagePattern else null
            }


        val patternOrUniform: DamagePattern
            get() = pattern.uniformIfNullOrNone()


    }



    override fun toString(): String {
        val auxFitOf = auxiliaryFitOf
        return if (auxFitOf != null)
            "Auxiliary fit of ${auxFitOf.ship}"
        else
            "Fit of $ship"
    }


}


/**
 * Returns the current values of the given list of states.
 */
private fun <T> List<State<T>>.currentValues(): Iterable<T> = this.mapEach { it.value }
