package eve.data

import androidx.compose.runtime.Immutable
import eve.data.AttributeType.Companion.BOOLEAN
import eve.data.AttributeType.Companion.DOUBLE
import eve.data.AttributeType.Companion.INT
import eve.data.AttributeType.Companion.ITEM_SIZE
import eve.data.utils.ValueByEnum
import eve.data.utils.valueByEnum
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty


/**
 * The units of attribute values.
 */
enum class AttributeUnit(
    val suffix: String? = null,
    val normalSuffix: String? = suffix,
    val rawToNormalValue: (Double) -> Double = { it },
    val normalToRawValue: (Double) -> Double = { it },
    val normalizingInverts: Boolean = false,  // Whether a < b implies rawToNormalValue(a) > rawToNormalValue(b)
    val formatNormalValue: (Double) -> String = DefaultValueFormat,
    val formatRawValueWithUnits: (Double) -> String = DefaultValueFormatWithUnits(rawToNormalValue, normalSuffix)
) {

    RangeMeters(
        suffix = "m",
        normalSuffix = "km",
        rawToNormalValue = DivideByThousand,
        normalToRawValue = MultiplyByThousand,
        formatRawValueWithUnits = { it.asDistance(withUnits = true) }
    ),
    Mass(
        suffix = "kg",
        normalSuffix = "t",
        rawToNormalValue = DivideByThousand,
        normalToRawValue = MultiplyByThousand,
        formatRawValueWithUnits = { it.asShipMass(withUnits = true) }
    ),
    TimeSec(
        suffix = "sec",
        formatRawValueWithUnits = { (it*1000).millisAsTimeSec(withUnits = true) }
    ),
    VolumeM3("m³"),
    VelocityMetersPerSec(
        suffix = "m/s",
        formatRawValueWithUnits = { it.asSpeed(withUnits = true) }
    ),
    MaxVelocityMetersPerSec(
        suffix = "m/s",
        formatRawValueWithUnits = { it.asSpeed(withUnits = true) }
    ),
    DurationMs(
        suffix = "ms.",
        normalSuffix = "sec",
        rawToNormalValue = DivideByThousand,
        normalToRawValue = MultiplyByThousand,
        formatRawValueWithUnits = { it.millisAsTimeSec(withUnits = true) }
    ),
    ScanResolutionMm("mm"),
    Multiplier1(
        suffix = "x",
    ),
    BonusPercentAdditive1("%"),
    CpuTf("tf"),
    PowerMw("MW"),
    Resonance(
        suffix = null,
        normalSuffix = "%",
        rawToNormalValue = NormalizeInvertedPercentage,
        normalToRawValue = DenormalizeInvertedPercentage,
        normalizingInverts = true
    ),
    Multiplier2(
        suffix = "x",
        normalSuffix = "%",
        rawToNormalValue = FactorToAddedPercentage,
        normalToRawValue = AddedPercentageToFactor
    ),
    Multiplier3(  // speedMultiplier attribute (which is used as rate-of-fire bonus on e.g. Heat Sinks
        suffix = "x",
        normalSuffix = "%",
        rawToNormalValue = NormalizeInvertedPercentage,
        normalToRawValue = DenormalizeInvertedPercentage,
        normalizingInverts = true
    ),
    Hitpoints(
        suffix = "HP",
        formatRawValueWithUnits = { it.asHitPoints(withUnits = true) }
    ),
    CapacitorGj(
        suffix = "GJ",
        formatRawValueWithUnits = { it.asCapacitorEnergy(withUnits = true) }
    ),
    GroupId(null),
    TypeId(null),
    SizeCategory(
        suffix = null,
        formatRawValueWithUnits = {
            when (ItemSize.fromCode(it.toInt())) {
                ItemSize.CAPITAL -> "Capital"
                ItemSize.LARGE -> "Large"
                ItemSize.MEDIUM -> "Medium"
                ItemSize.SMALL -> "Small"
                else -> it.toInt().toString()
            }
        }
    ),
    AttributeId(null),
    Unitless(null),
    BonusPercentAdditive2("%"),
    SlotAmount(null),
    BonusPercentAdditive3("%"),
    ThrustNewtons(
        suffix = "N",
        normalSuffix = "MN",
        rawToNormalValue = DivideByMillion,
        normalToRawValue = MultiplyByMillion,
        formatRawValueWithUnits = { it.asThrust(withUnits = true) }
    ),
    JumpRangeLy("ly"),
    AdditiveFraction(
        suffix = "x",
        normalSuffix = "%",
        rawToNormalValue = ValueToPercentage,
        normalToRawValue = PercentageToValue,
    ),
    Bandwidth(
        suffix = "mbit/s",
        formatRawValueWithUnits = { it.asDroneBandwidth(withUnits = true) }
    ),
    Isk("ISK"),
    ScanRangeAu("AU"),
    SlotNumber(null),
    BooleanFlag(
        suffix = null,
        formatRawValueWithUnits = { if (it == 0.0) "no" else "yes" }
    ),
    FuelUnits("units"),
    BonusIntegerAdditive(
        suffix = null,
        formatRawValueWithUnits = { "+${it.toInt()}" }
    ),
    Level(null),
    HardpointAmount(null),
    Gender(null),
    DateTime(null),
    WarpSpeedAuPerSec(
        suffix = "AU/s",
        formatRawValueWithUnits = { it.asWarpSpeed(withUnits = true) }
    ),
    WasteMultiplier("x");

}


/**
 * Divides the value by 1000.
 *
 * Used for normalizing time (milliseconds to seconds), distance (meters to kilometers), speed (m/s to km/s).
 */
private val DivideByThousand: (Double) -> Double = { it / 1000 }


/**
 * Multiplies the value by 1000.
 *
 * Used for denormalizing time (seconds to milliseconds), distance (kilometers to meters), speed (km/s to m/s).
 */
private val MultiplyByThousand: (Double) -> Double = { it * 1000 }


/**
 * Divides the value by 1,000,000.
 *
 * Used for normalizing thrust (N to MN).
 */
private val DivideByMillion: (Double) -> Double = { it / 1_000_000 }


/**
 * Multiplies the value by 1,000,000.
 *
 * Used for denormalizing thrust (MN to N).
 */
private val MultiplyByMillion: (Double) -> Double = { it * 1_000_000 }


/**
 * Converts a value to a percentage.
 */
private val ValueToPercentage: (Double) -> Double = { it * 100 }


/**
 * Converts a percentage to a regular value.
 */
private val PercentageToValue: (Double) -> Double = { it / 100 }


/**
 * Converts a multiplicative factor to a percentage increase.
 *
 * Used for normalizing multiplicative factors, such as bonus to damage.
 */
private val FactorToAddedPercentage: (Double) -> Double = { (it - 1.0) * 100 }


/**
 * Converts a percentage increase to a multiplicative factor.
 *
 * Used for denormalizing multiplicative factors, such as bonus to damage.
 */
private val AddedPercentageToFactor: (Double) -> Double = { (1.0 + it / 100) }


/**
 * Normalizes inverted percentage attributes, such as resonance.
 */
private val NormalizeInvertedPercentage: (Double) -> Double = { (1.0 - it) * 100 }


/**
 * Denormalizes inverted percentage attributes, such as resonance.
 */
private val DenormalizeInvertedPercentage: (Double) -> Double = { (1.0 - it/100) }


/**
 * The default value-formatting function.
 */
private val DefaultValueFormat: (Double) -> String = { it.toDecimalWithSignificantDigitsAtMost(4) }


/**
 * Returns the default value-with-units formatting function.
 */
private fun DefaultValueFormatWithUnits(
    rawToNormalValue: (Double) -> Double,
    normalSuffix: String?
): (Double) -> String =
    {
        val normalValue = rawToNormalValue(it)
        DefaultValueFormat(normalValue) + (if (normalSuffix == null) "" else "$NNBSP$normalSuffix")
    }


/**
 * Formats a normal value of the given attribute as a string without units.
 */
fun Double.normalAttributeValueToString(attribute: Attribute<*>) =
    (attribute.unit?.formatNormalValue ?: DefaultValueFormat).invoke(this)


/**
 * Formats a raw value of the given attribute as a string with units.
 *
 * This is the most suitable format for presenting the value to the user.
 */
fun Double.rawAttributeValueToStringWithUnits(attribute: Attribute<*>) =
    (attribute.unit?.formatRawValueWithUnits ?: DefaultValueFormat).invoke(this)


/**
 * An attribute of some entity, e.g. "armorHP" (of a ship).
 */
@Immutable
class Attribute<T: Any> internal constructor(


    /**
     * The attribute's id.
     */
    val id: Int,


    /**
     * The attribute's name.
     */
    val name: String,


    /**
     * The attributes name that is suitable to be displayed to the user.
     */
    val displayName: String?,


    /**
     * The attribute's unit.
     */
    val unit: AttributeUnit?,


    /**
     * Whether this attribute is stacking-penalized.
     */
    val isStackingPenalized: Boolean,


    /**
     * Whether a higher attribute value (if applicable) is better; `null` if irrelevant.
     */
    val highIsGood: Boolean?,


    /**
     * An optional range to which the [Double] value of any property corresponding to this attribute is restricted.
     */
    val propertyRange: ClosedRange<Double>?,


    /**
     * A converter between the [Double] attribute values to the attribute's actual type.
     *
     * This is needed because the SDE specifies all attribute values as doubles, and does not appear to specify the
     * attribute's actual type. The only way to determine the type of the attribute is to "know" it (e.g. that
     * "techLevel" is an integer attribute). We therefore first create all the attributes as [Double] attributes, and
     * then convert the ones we "know" to their correct type (the named attribute properties in [Attributes]).
     */
    private val type: AttributeType<T>


) : ReadOnlyProperty<HasAttributeValues, T> {


    /**
     * Implementation of [ReadOnlyProperty] for obtaining the value of this attribute in the context of the given
     * [HasAttributeValues] object.
     * This allows using [Attribute]s as delegates for (Kotlin) properties of types implementing [HasAttributeValues].
     */
    override operator fun getValue(thisRef: HasAttributeValues, property: KProperty<*>): T {
        return thisRef.attributeValues.get(this)
    }


    /**
     * A [ReadOnlyProperty] for an attribute whose [AttributeValue] is not necessarily present in the
     * [HasAttributeValues] object. In such a case, it will return `null`.
     */
    val orNull: ReadOnlyProperty<HasAttributeValues, T?> by lazy {
        ReadOnlyProperty { it, _ ->
            it.attributeValues.getOrNull(this@Attribute)
        }
    }


    /**
     * Returns a [ReadOnlyProperty] for an attribute whose [AttributeValue] is not necessarily present in the
     * [HasAttributeValues] object. In such a case, it will return the given default value.
     */
    fun orDefault(defaultValue: T): ReadOnlyProperty<HasAttributeValues, T> =
        ReadOnlyProperty{ it, _ ->
            it.attributeValues.getOrDefault(this@Attribute, defaultValue)
        }


    /**
     * Converts the [Double] value to the type of this attribute.
     */
    fun valueFromDouble(value: Double) = type.fromDouble(value)


    /**
     * Converts the value of the type of this attribute to a [Double] value.
     */
    fun valueToDouble(value: T) = type.toDouble(value)


    override fun toString(): String {
        return "Attribute($name, id=$id)"
    }


    companion object{


        /**
         * Creates a [Double] attribute with the given id, name, display name and stacking-penalized flag.
         */
        fun double(
            id: Int,
            name: String,
            displayName: String?,
            unit: AttributeUnit?,
            isStackingPenalized: Boolean,
            highIsGood: Boolean?,
            propertyRange: ClosedRange<Double>?
        ): Attribute<Double> =
            Attribute(
                id = id,
                name = name,
                displayName = displayName,
                unit = unit,
                isStackingPenalized = isStackingPenalized,
                type = DOUBLE,
                highIsGood = highIsGood,
                propertyRange = propertyRange,
            )


    }


}


/**
 * The runtime representation of the type of an [Attribute], allowing to convert between the type and its underlying
 * [Double] value representation.
 */
internal interface AttributeType<T>{


    /**
     * Converts a [Double] value to the type.
     */
    fun fromDouble(value: Double): T


    /**
     * Convert a type value to [Double].
     */
    fun toDouble(value: T): Double


    /**
     * Whether a higher [Double] value corresponds to a lower [T] value; `null` if irrelevant.
     */
    val isInvertedRelativeToDouble: Boolean?


    companion object{


        /**
         * The [AttributeType] for floating-point attributes.
         */
        internal val DOUBLE = object: AttributeType<Double> {
            override fun fromDouble(value: Double) = value
            override fun toDouble(value: Double) = value
            override val isInvertedRelativeToDouble: Boolean
                get() = false
        }


        /**
         * The [AttributeType] for integer attributes.
         */
        internal val INT = object: AttributeType<Int> {

            override fun fromDouble(value: Double) =
                if (value % 1 != 0.0)
                    throw IllegalArgumentException("Value expected to be a whole number; actually: $value")
                else
                    value.toInt()

            override fun toDouble(value: Int) = value.toDouble()

            override val isInvertedRelativeToDouble: Boolean
                get() = false

        }


        /**
         * The [AttributeType] for boolean attributes.
         */
        internal val BOOLEAN = object: AttributeType<Boolean> {

            override fun fromDouble(value: Double): Boolean = when (value){
                0.0 -> false
                1.0 -> true
                else -> throw IllegalArgumentException("Value expected to be either 0 or 1; actually: $value")
            }

            override fun toDouble(value: Boolean): Double =
                if (value) 1.0 else 0.0

            override val isInvertedRelativeToDouble: Boolean?
                get() = null

        }


        /**
         * The [AttributeType] for [ItemSize] attributes.
         */
        internal val ITEM_SIZE = object: AttributeType<ItemSize> {

            override fun fromDouble(value: Double) =
                ItemSize.fromCode(INT.fromDouble(value)) ?: throw BadEveDataException("Unknown item size code: $value")

            override fun toDouble(value: ItemSize) = value.code.toDouble()

            override val isInvertedRelativeToDouble: Boolean?
                get() = null

        }


    }


}


/**
 * Groups all dogma attributes, and provides some of them via explicit properties.
 */
@Suppress("unused")
class Attributes internal constructor(
    attributes: Iterable<Attribute<Double>>
): Iterable<Attribute<Double>> by attributes{


    /**
     * Attributes mapped by their name.
     */
    private val byName: MutableMap<String, Attribute<*>> = attributes.associateByTo(mutableMapOf()) { it.name }


    /**
     * Attributes mapped by their id.
     */
    private val byId: MutableMap<Int, Attribute<*>> = attributes.associateByTo(hashMapOf()) { it.id }


    /**
     * Replaces the [DOUBLE] [Attribute] with the given name with an identical one of the given type and returns it.
     * The attribute must exist.
     */
    @Suppress("UNCHECKED_CAST")
    private fun <T: Any> updateTypeAndGet(name: String, type: AttributeType<T>): Attribute<T> {
        val attribute = byName[name] ?: error("No attribute named `$name` found")
        val doubleAttribute = attribute as Attribute<Double>
        if (type == DOUBLE)
            return doubleAttribute as Attribute<T>

        return Attribute(
            id = doubleAttribute.id,
            name = doubleAttribute.name,
            displayName = doubleAttribute.displayName,
            unit = doubleAttribute.unit,
            isStackingPenalized = doubleAttribute.isStackingPenalized,
            highIsGood = when(val highIsGood = doubleAttribute.highIsGood) {
                null -> null
                type.isInvertedRelativeToDouble -> !highIsGood
                else -> highIsGood
            },
            propertyRange = doubleAttribute.propertyRange,
            type = type,
        ).also {
            byName[name] = it
            byId[doubleAttribute.id] = it
        }
    }


    /**
     * Returns the attribute with the given name as an `Attribute<Double>`.
     */
    @Suppress("UNCHECKED_CAST")
    private fun getAsDouble(name: String): Attribute<Double> {
        val attribute = byName[name] ?: error("No attribute named `$name` found")
        return attribute as Attribute<Double>
    }


    /**
     * Like [updateTypeAndGet], but returns `null` if the attribute does not exist.
     */
    private fun <T: Any> updateTypeAndGetOrNull(name: String, type: AttributeType<T>): Attribute<T>? {
        return if (byName.containsKey(name)) updateTypeAndGet(name, type) else null
    }


    /**
     * Updates all attributes (like [updateTypeAndGet]) that have the name
     * ```
     * namePattern.simpleFormat(index)
     * ```
     * where `index` goes from 1 until there is no attribute with that name.
     * This allows batch-updating attributes like "chargeGroup1", "chargeGroup2" etc., and also makes the code
     * future-proof when new indices are added.
     */
    private fun <T: Any> updateIndexed(namePattern: String, type: AttributeType<T>): List<Attribute<T>> {
        return generateSequence(1){ it + 1 }
            .map { index -> namePattern.simpleFormat(index) }
            .map { name ->
                updateTypeAndGetOrNull(name, type)
            }
            .takeWhile { it != null }
            .filterNotNull()
            .toList()
    }



    /**
     * Updates the attributes that have the name
     * ```
     * nameFormat.format(damageType.id)
     * ```
     * where `damageType` iterates over the values of [DamageType].
     * This allows batch-updating per-damage-type attributes.
     */
    private fun <T: Any> updateWithDamageType(


        /**
         * The name pattern, in the sense of [String.simpleFormat].
         */
        nameFormat: String,


        /**
         * Whether to capitalize the first letter of the damage type name.
         */
        capitalizeFirstLetter: Boolean,


        /**
         * The updated attribute type.
         */
        type: AttributeType<T>


    ): ValueByEnum<DamageType, Attribute<T>> {
        return valueByEnum { damageType ->
            val id = damageType.id
            val name = if (capitalizeFirstLetter) id.replaceFirstChar(Char::uppercaseChar) else id
            val attributeName = nameFormat.simpleFormat(name)
            updateTypeAndGet(attributeName, type)
        }
    }


    /**
     * Updates the attributes that have the name
     * ```
     * namePattern.format(sensorType.id)
     * ```
     * where `sensorType` iterates over the values of [SensorType].
     * This allows batch-updating per-sensor-type attributes.
     */
    @Suppress("SameParameterValue")
    private fun <T: Any> updateWithSensorType(


        /**
         * The name pattern, in the sense of [String.simpleFormat]
         */
        namePattern: String,


        /**
         * Whether to capitalize the first letter of the sensor type name.
         */
        capitalizeFirstLetter: Boolean,


        /**
         * The updated attribute type.
         */
        type: AttributeType<T>


    ): ValueByEnum<SensorType, Attribute<T>>{
        return valueByEnum { sensorType ->
            val id = sensorType.id
            val name = if (capitalizeFirstLetter) id.replaceFirstChar(Char::uppercaseChar) else id
            val attributeName = namePattern.simpleFormat(name)
            updateTypeAndGet(attributeName, type)
        }
    }


    /**
     * Updates all subsystem bonus attributes (like [updateTypeAndGet]) that have the name
     * ```
     * namePrefix + index
     * ```
     * where `index` goes from 1 until there is no attribute with that name.
     */
    private fun updateSubsystemBonuses(namePrefix: String): List<Attribute<Double>>{
        return generateSequence(1){ it + 1 }
            .map { index -> namePrefix + (if (index == 1) "" else "$index") }
            .map { name ->
                updateTypeAndGetOrNull(name, DOUBLE)
            }
            .takeWhile { it != null }
            .filterNotNull()
            .toList()
    }


    /**
     * Returns an [AttributeType] for attributes whose values are other attributes.
     */
    private fun <T: Any> attribute(): AttributeType<Attribute<T>> = object : AttributeType<Attribute<T>> {

        @Suppress("UNCHECKED_CAST")
        override fun fromDouble(value: Double) = get(INT.fromDouble(value)) as Attribute<T>

        override fun toDouble(value: Attribute<T>) = INT.toDouble(value.id)

        override val isInvertedRelativeToDouble: Boolean?
            get() = null

    }


    /**
     * Returns the attribute with the given id, assuming it exists.
     */
    operator fun get(id: Int): Attribute<*> = byId[id]!!


    /**
     * Returns the attribute with the given id, or `null` if it doesn't exist.
     */
    fun getOrNull(id: Int): Attribute<*>? = byId[id]


    /**
     * Returns the attribute with the given name, or `null` if it doesn't exist.
     */
    operator fun get(name: String): Attribute<*>? = byName[name]


    val techLevel = updateTypeAndGet("techLevel", INT)
    val metaLevel = updateTypeAndGet("metaLevelOld", INT)
    val metaGroupId = updateTypeAndGet("metaGroupID", INT)

    val perceptionBonus = updateTypeAndGet("perceptionBonus", INT)
    val memoryBonus = updateTypeAndGet("memoryBonus", INT)
    val willpowerBonus = updateTypeAndGet("willpowerBonus", INT)
    val intelligenceBonus = updateTypeAndGet("intelligenceBonus", INT)
    val charismaBonus = updateTypeAndGet("charismaBonus", INT)

    val signatureRadius = getAsDouble("signatureRadius")  // Also for superweapons
    val radius = getAsDouble("radius")  // Ship ball radius
    val capacity = getAsDouble("capacity")  // Cargo or module capacity, in m^3
    val isCapitalSize = updateTypeAndGet("isCapitalSize", BOOLEAN)
    val highSlots = updateTypeAndGet("hiSlots", INT)
    val medSlots = updateTypeAndGet("medSlots", INT)
    val lowSlots = updateTypeAndGet("lowSlots", INT)
    val rigSlots = updateTypeAndGet("rigSlots", INT)
    val cpuOutput = getAsDouble("cpuOutput")
    val powerOutput = getAsDouble("powerOutput")
    val calibration = updateTypeAndGet("upgradeCapacity", INT)
    val turretHardpoints = updateTypeAndGet("turretSlotsLeft", INT)
    val launcherHardpoints = updateTypeAndGet("launcherSlotsLeft", INT)
    val rigSize = updateTypeAndGet("rigSize", ITEM_SIZE)
    val droneCapacity = updateTypeAndGet("droneCapacity", INT)
    val droneBandwidth = getAsDouble("droneBandwidth")  // This can be a fraction due to Warp Core Stabilizers
    val droneBandwidthUsed = updateTypeAndGet("droneBandwidthUsed", INT)  // Drone attribute
    val droneControlRange = getAsDouble("droneControlDistance")
    val warpCapacitorNeed = getAsDouble("warpCapacitorNeed")  // Ship attribute
    val warpSpeedMultiplier = getAsDouble("warpSpeedMultiplier")  // Ship attribute

    val structureHp = getAsDouble("hp")
    val structureResonance = updateWithDamageType("%sDamageResonance", capitalizeFirstLetter = false, DOUBLE)  // Ship attribute
    val structureResonanceBonus = updateWithDamageType("hull%sDamageResonance", capitalizeFirstLetter = true, DOUBLE)  // Module bonus attribute

    val armorHp = getAsDouble("armorHP")
    val armorResonance = updateWithDamageType("armor%sDamageResonance", capitalizeFirstLetter = true, DOUBLE)

    val shieldHp = getAsDouble("shieldCapacity")
    val shieldRechargeTime = getAsDouble("shieldRechargeRate")  // In milliseconds
    val shieldResonance = updateWithDamageType("shield%sDamageResonance", capitalizeFirstLetter = true, DOUBLE)

    val capacitorCapacity = getAsDouble("capacitorCapacity")
    val capacitorRechargeTime = getAsDouble("rechargeRate")  // In milliseconds

    val targetingRange = getAsDouble("maxTargetRange")
    val maxLockedTargets = updateTypeAndGet("maxLockedTargets", INT)
    val scanResolution = getAsDouble("scanResolution")

    val sensorStrength = updateWithSensorType("scan%sStrength", true, DOUBLE)
    val ecmStrength = updateWithSensorType("scan%sStrengthBonus", true, DOUBLE) // Jam strength of ECM modules/bombs
    val ecmJamDuration = getAsDouble("ecmJamDuration")  // The duration of a successful jam

    val mass = getAsDouble("mass")
    val inertiaModifier = getAsDouble("agility")
    val maxVelocity = getAsDouble("maxVelocity")  // Ship, drone and missile/bomb attribute
    val propulsionModuleSpeedFactor = getAsDouble("fix_propulsionModuleSpeedFactor")
    val propulsionModuleThrust = getAsDouble("fix_propulsionModuleThrust")
    val speedLimit = getAsDouble("speedLimit")  // Modifying module and the modified ship attr (by e.g. Entosis Links)
    val speedFactor = getAsDouble("speedFactor")  // Webifier and propulsion module attribute
    val heatDamage = getAsDouble("heatDamage")  // All overheatable modules

    val energyWarfareResistance = getAsDouble("energyWarfareResistance")
    val weaponDisruptionResistance = getAsDouble("weaponDisruptionResistance")
    val sensorDampenerResistance = getAsDouble("sensorDampenerResistance")
    val targetPainterResistance = getAsDouble("targetPainterResistance")
    val ecmResistance = getAsDouble("ECMResistance")
    val remoteRepairImpedance = getAsDouble("remoteRepairImpedance")  // Ship attribute; effectiveness of remote repair
    val remoteAssistanceImpedance = getAsDouble("remoteAssistanceImpedance")  // Ship attribute; effectiveness of RTCs and RSBs

    val power = getAsDouble("power")  // Module power usage
    val cpu = getAsDouble("cpu") // Module CPU usage
    val calibrationNeed = updateTypeAndGet("upgradeCost", INT)  // Rig calibration usage
    val capacitorNeed = getAsDouble("capacitorNeed")  // Module capacitor need per activation

    val damage = updateWithDamageType("%sDamage", capitalizeFirstLetter = false, DOUBLE)  // Damage done by ammo/drones/smartbombs
    val weaponDamage = updateWithDamageType("fix_%sWeaponDamage", capitalizeFirstLetter = false, DOUBLE)  // Damage done by weapon modules
    val droneDamage = updateWithDamageType("fix_%sDroneDamage", capitalizeFirstLetter = false, DOUBLE)  // Damage done by drones
    val damageMultiplier = getAsDouble("damageMultiplier")  // Weapon and drone attribute, and also weapon damage modules (e.g. Heat Sink)
    val droneDamageBonus = getAsDouble("droneDamageBonus")  // Drone damage amplifiers and Triage Module
    val missileDamageMultiplierBonus = getAsDouble("missileDamageMultiplierBonus")  // BCS damage bonus

    val speedMultiplier = getAsDouble("speedMultiplier")  // Rate of fire bonus

    val weaponRangeMultiplier = getAsDouble("weaponRangeMultiplier")  // Module attribute

    val durationAttribute = updateTypeAndGet("fix_durationAttributeId", attribute<Double>())  // Module/drone attribute specifying its duration
    val optimalRangeAttribute = updateTypeAndGet("fix_rangeAttributeId", attribute<Double>())  // Module/drone attribute specifying optimal range
    val falloffRangeAttribute = updateTypeAndGet("fix_falloffAttributeId", attribute<Double>())  // Module/drone attribute specifying falloff
    val trackingSpeedAttribute = updateTypeAndGet("fix_trackingSpeedAttributeId", attribute<Double>())  // Module/drone attribute specifying tracking speed
    val moduleReactivationDelay = getAsDouble("moduleReactivationDelay")  // Module attribute

    val reloadTime = getAsDouble("reloadTime")  // The time to reload, in milliseconds

    val speed = getAsDouble("speed")  // Typically used as the activation duration attribute
    val duration = getAsDouble("duration")  // Sometimes used as the activation duration attribute, but also as the scan time for scan probe launchers
    val maxRange = getAsDouble("maxRange")  // Typically used as the optimal range attribute
    val falloff = getAsDouble("falloff")  // Typically used as the falloff attribute
    val trackingSpeed = getAsDouble("trackingSpeed")  // Typically used as the tracking speed attribute
    val signatureResolution = getAsDouble("optimalSigRadius")  // The signature resolution of e.g. turrets
    val missileFlightTime = getAsDouble("explosionDelay")  // Missile and bomb (Charge) attribute
    val explosionRange = getAsDouble("explosionRange")  // Bomb (charge) attribute

    val optimalRangeBonus = getAsDouble("maxRangeBonus")  // Tracking computers/enhancers/disruptors
    val falloffBonus = getAsDouble("falloffBonus")  // Tracking computers/enhancers/disruptors
    val trackingSpeedBonus = getAsDouble("trackingSpeedBonus")  // Tracking computers/enhancers/disruptors
    val missileVelocityBonus = getAsDouble("missileVelocityBonus")  // Guidance computers/enhancers/disruptors
    val missileFlightTimeBonus = getAsDouble("explosionDelayBonus")  // Guidance computers/enhancers/disruptors
    val explosionVelocityBonus = getAsDouble("aoeVelocityBonus")  // Guidance computers/enhancers/disruptors
    val explosionRadiusBonus = getAsDouble("aoeCloudSizeBonus")  // Guidance computers/enhancers/disruptors
    val aoeCloudSize = getAsDouble("aoeCloudSize")  // Missile and bomb explosion radius
    val aoeVelocity = getAsDouble("aoeVelocity")  // Missile explosion velocity
    val aoeDamageReductionFactor = getAsDouble("aoeDamageReductionFactor")  // Missile drf
    val missileDamageMultiplier = getAsDouble("missileDamageMultiplier")  // Character attribute!

    val targetingRangeBonus = getAsDouble("maxTargetRangeBonus")  // Remote/self sensor booster and remote sensor dampener
    val scanResolutionBonus = getAsDouble("scanResolutionBonus")  // Remote/self sensor booster and remote sensor dampener
    val sensorStrengthBonus = updateWithSensorType("scan%sStrengthPercent", true, DOUBLE) // Remote/self sensor booster
    val signatureRadiusBonus = getAsDouble("signatureRadiusBonus")  // Target painter
    val warpDisruptionStrength = updateTypeAndGet("warpScrambleStrength", INT)  // Warp disruptor/scrambler and WCS

    val maxTractorVelocity = getAsDouble("maxTractorVelocity")  // Tractor beam

    val shieldBoost = getAsDouble("shieldBonus")  // Shield Booster attribute
    val armorRepairAmount = getAsDouble("armorDamageAmount")  // Armor repairer (including drones)
    val structureDamageAmount = getAsDouble("structureDamageAmount")  // Hull repairer
    val energyNeutralizerAmount = getAsDouble("energyNeutralizerAmount")  // Neuts, EV-drones and Void bomb
    val energyTransferAmount = getAsDouble("powerTransferAmount")  // Nosferatus, remote capacitor transmitters

    val chargedArmorDamageMultiplier = getAsDouble("chargedArmorDamageMultiplier")  // Ancillary armor repairers
    val chargeRate = updateTypeAndGet("chargeRate", INT)  // The amount of charges consumed per activation

    val armorHpMultiplier = getAsDouble("armorHPMultiplier")  // Bonus to armor HP
    val signatureRadiusAdd = getAsDouble("signatureRadiusAdd")  // Shield extenders

    val charisma = updateTypeAndGet("charisma", INT)
    val intelligence = updateTypeAndGet("intelligence", INT)
    val memory = updateTypeAndGet("memory", INT)
    val perception = updateTypeAndGet("perception", INT)
    val willpower = updateTypeAndGet("willpower", INT)

    val skillLevel = updateTypeAndGet("skillLevel", INT)
    val primaryAttribute = updateTypeAndGet("primaryAttribute", attribute<Int>())
    val secondaryAttribute = updateTypeAndGet("secondaryAttribute", attribute<Int>())
    val skillTimeConstant = updateTypeAndGet("skillTimeConstant", INT)

    val skillRequirements: List<SkillRequirementAttributes> = updateIndexed("requiredSkill%d", INT)
        .zip(updateIndexed("requiredSkill%dLevel", INT))
        .map { SkillRequirementAttributes(it.first, it.second) }

    // Charge groups are module attributes that specify the groupId of a charge that can be loaded into the module
    val chargeGroups: List<Attribute<Int>> = updateIndexed("chargeGroup%d", INT)

    val chargeSize = updateTypeAndGet("chargeSize", ITEM_SIZE)  // The size of a charge; an attribute of modules and charges
    val maxGroupFitted = updateTypeAndGet("maxGroupFitted", INT)  // Max. number of modules of the group that can be fitted onto the ship
    val maxGroupOnline = updateTypeAndGet("maxGroupOnline", INT)  // Max. number of modules of the group that can be online at the same time
    val maxGroupActive = updateTypeAndGet("maxGroupActive", INT)  // Max. number of modules of the group that can be active at the same time

    // Modules with these attributes limit the ships they can be fitted onto by the ship type's groupId
    val canFitShipGroups: List<Attribute<Int>> = updateIndexed("canFitShipGroup%02d", INT)

    // Modules with these attributes limit the ships they can be fitted onto by the ship type's id
    val canFitShipTypes: List<Attribute<Int>> = updateIndexed("canFitShipType%d", INT)

    // Marks modules that can only be fit on capital-class ships
    val canOnlyFitOnCapitals = updateTypeAndGet("fix_canOnlyFitOnCapitals", BOOLEAN)

    // The capacitor increase provided by Cap Booster charges
    val capacitorBonus = getAsDouble("capacitorBonus")

    val probeCanScanShips = updateTypeAndGet("probeCanScanShips", BOOLEAN)  // True for combat probes
    val scanStrengthBonus = getAsDouble("scanStrengthBonus")  // Probe launcher attribute
    val baseMaxScanDeviation = getAsDouble("baseMaxScanDeviation")  // Scan probe attribute
    val baseScanRange = getAsDouble("baseScanRange")  // Scan probe attribute
    val baseScanSensorStrength = getAsDouble("baseSensorStrength")  // Scan probe attribute
    val maxScanDeviationModifierModule = getAsDouble("maxScanDeviationModifierModule")  // Scan Pinpointing Array
    val scanStrengthBonusModule = getAsDouble("scanStrengthBonusModule")  // Scan Rangefinding Array

    // Warp disruption probes, mobile warp disruptors and warp disruption field generators
    val warpScrambleRange = getAsDouble("warpScrambleRange")

    val maxActiveDrones = updateTypeAndGet("maxActiveDrones", INT)  // Character attribute
    val entityCruiseSpeed = getAsDouble("entityCruiseSpeed")  // Drone orbit speed
    val entityFlyRange = getAsDouble("entityFlyRange")  // Drone orbit range

    val consumptionQuantity = getAsDouble("consumptionQuantity")  // Amount of fuel (e.g. liquid ozone) consumed
    val consumptionTypeId = updateTypeAndGet("consumptionType", INT)  // The id of the type of fuel consumed
    val jumpDriveConsumptionAmount = getAsDouble("jumpDriveConsumptionAmount")  // Ship attribute

    val miningAmount = getAsDouble("miningAmount")
    val miningResidueProbability = getAsDouble("miningWasteProbability")  // Percentage
    val miningResidueVolumeMultiplier = getAsDouble("miningWastedVolumeMultiplier")

    val accessDifficultyBonus = getAsDouble("accessDifficultyBonus")  // Salvager, relic/data analyzer, salvage tackle rigs
    val virusCoherence = getAsDouble("virusCoherence")  // Relic/data analyzer
    val virusElementSlots = updateTypeAndGet("virusElementSlots", INT)  // Relic/data analyzer
    val virusStrength = getAsDouble("virusStrength")  // Relic/data analyzer

    val mjfgRadius = getAsDouble("mjfgRadius")  // Micro Jump Field Generator radius
    val mjdShipJumpCap = updateTypeAndGet("mjdShipJumpCap", INT)  // Micro Jump Field Generator cap on ships jumped

    val decloakTargetingDelay = getAsDouble("cloakingTargetingDelay")  // Cloaking Devices
    val specializationAsteroidYieldMultiplier = getAsDouble("specializationAsteroidYieldMultiplier")  // Mercoxit crystals

    // Reactive armor hardener
    val resistanceShiftAmount = getAsDouble("resistanceShiftAmount")
    val adaptationCycles = updateTypeAndGet("fix_adaptationCycles", INT)
    val adaptiveDirty = getAsDouble("fix_adaptiveDirty")

    // Triglavian modules
    val spoolupCycles = getAsDouble("fix_spoolupCycles")
    val damageMultiplierBonusPerCycle = getAsDouble("damageMultiplierBonusPerCycle")  // bonus per cycle
    val repairMultiplierBonusPerCycle = getAsDouble("repairMultiplierBonusPerCycle")  // bonus per cycle
    val damageMultiplierBonusMax = getAsDouble("damageMultiplierBonusMax")  // multiplier bonus at max spools
    val repairMultiplierBonusMax = getAsDouble("repairMultiplierBonusMax")  // multiplier bonus at max spools

    // Breacher pod and SCARAB (dot = damage over time)
    val dotMaxDamagePerTick = getAsDouble("dotMaxDamagePerTick")
    val dotMaxHpPctDamagePerTick = getAsDouble("dotMaxHPPercentagePerTick")
    val dotDuration = getAsDouble("dotDuration")

    // Cloaks
    val stabilizeCloakDuration = getAsDouble("stabilizeCloakDuration")

    // Armor plate
    val massAddition = getAsDouble("massAddition")

    // Boosters (not exclusively)
    val shieldBoostMultiplier = getAsDouble("shieldBoostMultiplier")
    val armorDamageAmountBonus = getAsDouble("armorDamageAmountBonus")
    val capacitorCapacityBonus = getAsDouble("capacitorCapacityBonus")
    val rangeSkillBonus = getAsDouble("rangeSkillBonus")
    val aoeCloudSizeBonus = getAsDouble("aoeCloudSizeBonus")
    val velocityBonus = getAsDouble("velocityBonus")
    val damageMultiplierBonus = getAsDouble("damageMultiplierBonus")

    val tacticalDestroyerShipId = updateTypeAndGet("fix_tacticalDestroyerShipId", INT)
    val hasTacticalModes = updateTypeAndGet("fix_hasTacticalModes", BOOLEAN)
    val tacticalModeTypeKindId = updateTypeAndGet("fix_tacticalModeTypeKindId", INT)

    // Tech3 cruiser subsystems
    val highSlotModifier = updateTypeAndGet("hiSlotModifier", INT)
    val medSlotModifier = updateTypeAndGet("medSlotModifier", INT)
    val lowSlotModifier = updateTypeAndGet("lowSlotModifier", INT)
    val turretHardPointModifier = updateTypeAndGet("turretHardPointModifier", INT)
    val launcherHardPointModifier = updateTypeAndGet("launcherHardPointModifier", INT)

    val fitsToShipTypeId = updateTypeAndGet("fitsToShipType", INT)
    val subSystemSlot = updateTypeAndGet("subSystemSlot", INT)
    val maxSubSystems = updateTypeAndGet("maxSubSystems", INT)
    val powerEngineeringOutputBonus = getAsDouble("powerEngineeringOutputBonus")
    val cpuOutputBonus2 = getAsDouble("cpuOutputBonus2")
    val maxLockedTargetsBonus = updateTypeAndGet("maxLockedTargetsBonus", INT)
    val armorHpBonusAdd = getAsDouble("armorHPBonusAdd")
    val structureHpBonusAdd = getAsDouble("structureHPBonusAdd")
    val subsystemBonusMassAddition = getAsDouble("subsystemBonusMassAddition")
    val subsystemEnergyNeutFittingReduction = getAsDouble("subsystemEnergyNeutFittingReduction")
    val subsystemMedMissileFittingReduction = getAsDouble("subsystemMMissileFittingReduction")
    val subsystemMedEnergyTurretFittingReduction = getAsDouble("subsystemMETFittingReduction")
    val subsystemMedHybridTurretFittingReduction = getAsDouble("subsystemMHTFittingReduction")
    val subsystemMedProjectibeTurretFittingReduction = getAsDouble("subsystemMPTFittingReduction")
    val subsystemMedRemoteShieldBoosterFittingReduction = getAsDouble("subsystemMRSBFittingReduction")
    val subsystemCommandBurstFittingReduction = getAsDouble("subsystemCommandBurstFittingReduction")
    val subsystemMedRemoteArmorRepairerFittingReduction = getAsDouble("subsystemMRARFittingReduction")
    val cloakingCpuNeedBonus = getAsDouble("cloakingCpuNeedBonus")
    val covertOpsAndReconOpsCloakModuleDelay = getAsDouble("covertOpsAndReconOpsCloakModuleDelay")
    val cargoCapacityAdd = getAsDouble("cargoCapacityAdd")
    val remoteArmorRepairerOptimalBonus = getAsDouble("remoteArmorRepairerOptimalBonus")
    val remoteArmorRepairerFalloffBonus = getAsDouble("remoteArmorRepairerFalloffBonus")
    val remoteShieldBoosterFalloffBonus = getAsDouble("remoteShieldBoosterFalloffBonus")
    val roleBonusCommandBurstAoERange = getAsDouble("roleBonusCommandBurstAoERange")
    val agilityBonusAdd = getAsDouble("agilityBonusAdd")
    val shipBonusRole1 = getAsDouble("shipBonusRole1")
    val shipBonusRole2 = getAsDouble("shipBonusRole2")

    // AOE effects
    val aoeDuration = getAsDouble("doomsdayAOEDuration")  // Target Illumination Burst Projector
    val aoeRange = getAsDouble("doomsdayAOERange")  // Webification probes

    /**
     * Groups the attributes affected by subsystem skills.
     */
    inner class Subsystems {
        val bonusAmarrCore: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusAmarrCore")
        val bonusCaldariCore: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusCaldariCore")
        val bonusMinmatarCore: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusMinmatarCore")
        val bonusGallenteCore: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusGallenteCore")
        val bonusAmarrDefensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusAmarrDefensive")
        val bonusCaldariDefensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusCaldariDefensive")
        val bonusMinmatarDefensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusMinmatarDefensive")
        val bonusGallenteDefensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusGallenteDefensive")
        val bonusAmarrOffensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusAmarrOffensive")
        val bonusCaldariOffensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusCaldariOffensive")
        val bonusMinmatarOffensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusMinmatarOffensive")
        val bonusGallenteOffensive: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusGallenteOffensive")
        val bonusAmarrPropulsion: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusAmarrPropulsion")
        val bonusCaldariPropulsion: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusCaldariPropulsion")
        val bonusMinmatarPropulsion: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusMinmatarPropulsion")
        val bonusGallentePropulsion: List<Attribute<Double>> = updateSubsystemBonuses("subsystemBonusGallentePropulsion")
    }

    /**
     * The attributes related to subsystems.
     */
    val subsystems = Subsystems()

    // CONCORD ships (Marshal, Enforcer, Pacifier)
    val pilotSecurityStatus = getAsDouble("pilotSecurityStatus")

    // Scripts
    val maxRangeBonusBonus = getAsDouble("maxRangeBonusBonus")
    val missileVelocityBonusBonus = getAsDouble("missileVelocityBonusBonus")
    val maxTargetRangeBonusBonus = getAsDouble("maxTargetRangeBonusBonus")


    /**
     * Groups the warfare buffs attributes.
     */
    inner class Warfare {
        val shieldResonanceBonus = getAsDouble("fix_warfareShieldResonanceBonus")
        val shieldBoostersBonus = getAsDouble("fix_warfareShieldBoostersBonus")
        val shieldHitpointsBonus = getAsDouble("fix_warfareShieldHitpointsBonus")
        val armorResonanceBonus = getAsDouble("fix_warfareArmorResonanceBonus")
        val armorRepairersBonus = getAsDouble("fix_warfareArmorRepairersBonus")
        val armorHitpointsBonus = getAsDouble("fix_warfareArmorHitpointsBonus")
        val scanResolutionBonus = getAsDouble("fix_warfareScanResolutionBonus")
        val electronicSuperiorityBonus = getAsDouble("fix_warfareElectronicSuperiorityBonus")
        val sensorStrengthBonus = getAsDouble("fix_warfareSensorStrengthBonus")
        val ewarResistance = getAsDouble("fix_warfareEwarResistance")
        val signatureRadiusBonus = getAsDouble("fix_warfareSignatureRadiusBonus")
        val tackleRangeBonus = getAsDouble("fix_warfareTackleRangeBonus")
        val speedFactorBonus = getAsDouble("fix_warfareSpeedFactorBonus")
        val targetingRangeBonus = getAsDouble("fix_warfareTargetingRangeBonus")
        val agilityBonus = getAsDouble("fix_warfareAgilityBonus")

        @Suppress("unused")
        val mindlinkBonus = getAsDouble("mindlinkBonus")
        val buffDuration = getAsDouble("buffDuration")
    }


    /**
     *  Warfare buffs attributes.
     */
    val warfare = Warfare()


    inner class TacticalMode {
        val resonanceBonusDiv = updateWithDamageType("mode%sResistancePostDiv", capitalizeFirstLetter = true, DOUBLE)
        val sensorStrengthBonusDiv = updateWithSensorType("mode%sStrengthPostDiv", capitalizeFirstLetter = true, DOUBLE)
        val signatureRadiusBonusDiv = getAsDouble("modeSignatureRadiusPostDiv")
        val agilityBonusDiv = getAsDouble("modeAgilityPostDiv")
        val velocityBonusDiv = getAsDouble("modeVelocityPostDiv")
        val mwdVelocityPostDiv = getAsDouble("modeMWDVelocityPostDiv")  // Hecate only
        val maxRangeBonusDiv = getAsDouble("modeMaxRangePostDiv")
        val targetRangeBonusDiv = getAsDouble("modeMaxTargetRangePostDiv")
        val damageBonusDiv = getAsDouble("modeDamageBonusPostDiv")
        val ewarResistanceBonusDiv = getAsDouble("modeEwarResistancePostDiv")
        val mwdSigPenaltyBonusDiv = getAsDouble("modeMWDSigPenaltyPostDiv")
        val mwdCapBonusDiv = getAsDouble("modeMWDCapPostDiv")
        val trackingBonusDiv = getAsDouble("modeTrackingPostDiv")
        val armorRepDurationBonusDiv = getAsDouble("modeArmorRepDurationPostDiv")
        val remoteRepairCapacitorCostPostDiv = getAsDouble("modeRemoteRepairCapacitorCostPostDiv")
        val remoteRepairBonusPostDiv = getAsDouble("modeRemoteRepairBonusPostDiv")
        val shieldRechargePostDiv = getAsDouble("modeShieldRechargePostDiv")
        val rookieLightMissileVelocity = getAsDouble("rookieLightMissileVelocity")
        val rookieRocketVelocity = getAsDouble("rookieRocketVelocity")
        val shipBonus2CB = getAsDouble("shipBonus2CB")  // Anhinga bonus to missile RoF
        val shipBonusRole7 = getAsDouble("shipBonusRole7")  // Anhinga primary mode reduction in missile velocity
        val shipBonusRole8 = getAsDouble("shipBonusRole8")  // Anhinga primary mode bonus to missile flight time
        val shipBonusCB3 = getAsDouble("shipBonusCB3")  // Anhinga tertiary mode bonus to missile flight time (why is it not `shipBonusRole7`??)
        val roleBonusMarauder = getAsDouble("roleBonusMarauder")  // Anhinga tertiary mode bonus to MJD activation delay
        val shipBonusCI2 = getAsDouble("shipBonusCI2")  // Anhinga tertiary mode bonus to inertia
    }


    /**
     * Tactical mode effect attributes.
     */
    val tacticalMode = TacticalMode()

    // (Laser) Crystal attributes
    val crystalVolatilityChance = getAsDouble("crystalVolatilityChance")
    val crystalVolatilityDamage = getAsDouble("crystalVolatilityDamage")
    val crystalsGetDamaged = updateTypeAndGet("crystalsGetDamaged", BOOLEAN)

    val boosterDuration = getAsDouble("boosterDuration")
    val boosterSideEffectChance = updateIndexed("boosterEffectChance%d", DOUBLE)
    val boosterLastInjectionDatetime = getAsDouble("boosterLastInjectionDatetime")  // In days since epoch

    // Environmental effect attributes (but probably not just)
    val shieldHitpointsMultiplier = getAsDouble("shieldCapacityMultiplier")
    val signatureRadiusMultiplier = getAsDouble("signatureRadiusMultiplier")
    val shieldResistanceBonus = updateWithDamageType("shield%sDamageResistanceBonus", capitalizeFirstLetter = true, DOUBLE)
    val armorResistanceBonus = updateWithDamageType("armor%sDamageResistanceBonus", capitalizeFirstLetter = true, DOUBLE)
    val rechargeRateMultiplier = getAsDouble("rechargeRateMultiplier")
    val energyWarfareStrengthMultiplier = getAsDouble("energyWarfareStrengthMultiplier")
    val agilityMultiplier = getAsDouble("agilityMultiplier")
    val maxTargetRangeMultiplier = getAsDouble("maxTargetRangeMultiplier")
    val missileVelocityMultiplier = getAsDouble("missileVelocityMultiplier")
    val maxVelocityMultiplier = getAsDouble("maxVelocityMultiplier")
    val explosionVelocityMultiplier = getAsDouble("aoeVelocityMultiplier")
    val explosionRadiusMultiplier = getAsDouble("aoeCloudSizeMultiplier")
    val stasisWebStrengthMultiplier = getAsDouble("stasisWebStrengthMultiplier")
    val armorRepairAmountMultiplier = getAsDouble("armorDamageAmountMultiplier")
    val armorRemoteRepairAmountMultiplier = getAsDouble("armorDamageAmountMultiplierRemote")
    val shieldBoostAmountMultiplier = getAsDouble("shieldBonusMultiplier")
    val shieldRemoteBoostAmountMultiplier = getAsDouble("shieldBonusMultiplierRemote")
    val capacitorCapacityMultiplier = getAsDouble("capacitorCapacityMultiplierSystem")
    val energyTransferAmountMultiplier = getAsDouble("energyTransferAmountBonus")
    val trackingSpeedMultiplier = getAsDouble("trackingSpeedMultiplier")
    val damageMultiplierMultiplier = getAsDouble("damageMultiplierMultiplier")
    val targetPainterEffectivenessMultiplier = getAsDouble("targetPainterStrengthMultiplier")
    val heatDamageMultiplier = getAsDouble("heatDamageMultiplier")
    val overloadBonusMultiplier = getAsDouble("overloadBonusMultiplier")
    val smartbombRangeMultiplier = getAsDouble("empFieldRangeMultiplier")
    val bombEffectivenessMultiplier = getAsDouble("smartbombDamageMultiplier")
    val smallWeaponDamageMultiplier = getAsDouble("smallWeaponDamageMultiplier")
    val virusCoherenceBonus = getAsDouble("virusCoherenceBonus")
    val scanProbeStrengthBonus = getAsDouble("scanProbeStrengthBonus")
    val disallowCloaking = updateTypeAndGet("disallowCloaking", BOOLEAN)
    val miningDurationMultiplier = getAsDouble("miningDurationMultiplier")
    val warpSpeedBonus = getAsDouble("warpSpeedBonus")
    val shieldBoosterDurationBonus = getAsDouble("shieldBoosterDurationBonus")
    val armorRepairerDurationBonus = getAsDouble("armorRepairDurationBonus")
    val maxVelocityBonus = getAsDouble("implantBonusVelocity")
    val capRechargeBonus = getAsDouble("capRechargeBonus")
    val armorHpBonus = getAsDouble("armorHpBonus")
    val shieldHpBonus = getAsDouble("shieldHpBonus")
    val superWeaponDamageDuration = getAsDouble("doomsdayDamageDuration")  // Lances, Reapers
    val superWeaponDamageCycleTime = getAsDouble("doomsdayDamageCycleTime")  // Lances, Reapers
    val superWeaponWarningDuration = getAsDouble("doomsdayWarningDuration")
}


/**
 * The value of some attribute.
 */
class AttributeValue(
    val attributeId: Int,
    val value: Double,
) {

    override fun toString() = "AttributeValue($attributeId=$value)"

}


/**
 * Groups the [AttributeValue]s for some entity and provides convenient access to them.
 */
class AttributeValues private constructor(


    /**
     * Maps [AttributeValue]s by their attribute ids.
     */
    private val valuesByAttributeId: MutableMap<Int, AttributeValue>


): Collection<AttributeValue> by valuesByAttributeId.values {


    /**
     * Creates an [AttributeValues] instance from the given list of [AttributeValue]s
     */
    constructor(attributeValues: Iterable<AttributeValue>):
            this(attributeValues.associateByTo(mutableMapOf()) { it.attributeId } )


    /**
     * Returns the value of the given attribute.
     * The value must be present.
     */
    fun <T: Any> get(attribute: Attribute<T>): T {
        val attributeValue = valuesByAttributeId[attribute.id]
            ?: throw BadEveDataException("No attribute value for attribute ${attribute.id} found")
        return attribute.valueFromDouble(attributeValue.value)
    }


    /**
     * Returns the value of the given attribute, or `null` if it's not present.
     */
    fun <T: Any> getOrNull(attribute: Attribute<T>): T? {
        return valuesByAttributeId[attribute.id]?.let { attribute.valueFromDouble(it.value) }
    }


    /**
     * Returns the value of the given attribute, or the given default value if it's not present.
     */
    fun <T: Any> getOrDefault(attribute: Attribute<T>, defaultValue: T): T {
        return valuesByAttributeId[attribute.id]?.let { attribute.valueFromDouble(it.value) } ?: defaultValue
    }


    /**
     * Sets the value of the given attribute.
     */
    fun <T: Any> set(attribute: Attribute<T>, value: T) {
        valuesByAttributeId[attribute.id] = AttributeValue(attribute.id, attribute.valueToDouble(value))
    }


    /**
     * Returns the underlying [Double] value of the attribute with the given id.
     */
    fun getDoubleValue(attributeId: Int): Double {
        val attributeValue = valuesByAttributeId[attributeId]
            ?: throw BadEveDataException("No attribute value for attribute $attributeId found")
        return attributeValue.value
    }


    /**
     * Returns the underlying [Double] value of the given attribute.
     */
    fun getDoubleValue(attribute: Attribute<*>): Double {
        val attributeValue = valuesByAttributeId[attribute.id]
            ?: throw BadEveDataException("No attribute value for attribute $attribute found")
        return attributeValue.value
    }


    /**
     * Sets the underlying [Double] value of the attribute with the given id.
     */
    fun setDoubleValue(attributeId: Int, value: Double) {
        valuesByAttributeId[attributeId] = AttributeValue(attributeId, value)
    }


    /**
     * Returns whether the given attribute is present.
     */
    fun has(attribute: Attribute<*>): Boolean {
        return valuesByAttributeId.containsKey(attribute.id)
    }


    override fun toString(): String {
        return valuesByAttributeId.entries.joinToString(separator = "\n") {
            "${it.key} = ${it.value.value}"
        }
    }


    fun toString(eveData: EveData): String {
        return valuesByAttributeId.entries.joinToString(separator = "\n") {
            "${eveData.attributes[it.key].name}(${it.key}) = ${it.value.value}"
        }
    }


}


/**
 * The interface for types that have [AttributeValues].
 * Together with [Attribute.getValue], this allows [Attribute]s to be used as delegates of (Kotlin) properties in them:
 * ```
 * class MyEntity(override val attributeValues: AttributeValues){
 *     val techLevel: Int by attributes.techLevel
 * }
 * ```
 */
interface HasAttributeValues {


    /**
     * The attribute values.
     */
    val attributeValues: AttributeValues


}


/**
 * Encapsulates a "requiredSkill" and a "requiredSkillLevel" attribute.
 * Together they represent a requirement for a certain skill to be trained to a certain level.
 */
class SkillRequirementAttributes(


    /**
     * The id of the skill that must be trained.
     */
    val skillId: Attribute<Int>,


    /**
     * The level of the skill that must be trained.
     */
    val level: Attribute<Int>


)


/**
 * A function like `String.format`, but only with the patterns we actually need.
 */
private fun String.simpleFormat(arg: Any): String {
    return when {
        "%s" in this -> replace("%s", arg.toString())
        "%d" in this -> replace("%d", (arg as Int).toString())
        "%02d" in this -> replace("%02d", (arg as Int).toString().padStart(2, '0'))
        else -> error("Unsupported pattern: $this")
    }
}