package eve.data

import androidx.compose.runtime.Immutable
import java.math.BigDecimal


/**
 * Encapsulates a single mutaplasmid.
 */
@Immutable
data class Mutaplasmid(


    /**
     * The id of the mutaplasmid.
     */
    val id: Int,


    /**
     * The name of the mutaplasmid.
     */
    val name: String,


    /**
     * The ids of types this mutaplasmid can mutate.
     */
    val targetTypeIds: List<Int>,


    /**
     * The mutations performed by this mutaplasmid, by the id of the attribute.
     */
    val attributeMutations: Map<Int, AttributeMutation>


): Comparable<Mutaplasmid> {


    /**
     * The type of this mutaplasmid.
     */
    val type = MutaplasmidNameSubstringsToType.firstOrNull { (matcher, _) ->
        matcher.matches(name)
    }?.second ?: error("Unknown mutaplasmid name: $name")


    /**
     * The types of mutaplasmids.
     */
    enum class Type {
        Decayed,
        GlorifiedDecayed,
        Gravid,
        GlorifiedGravid,
        Unstable,
        GlorifiedUnstable,
        Radical,
        GlorifiedRadical,
        ExigentNavigation,
        GlorifiedExigentNavigation,
        ExigentFirepower,
        GlorifiedExigentFirepower,
        ExigentDurability,
        GlorifiedExigentDurability,
        ExigentProjection,
        GlorifiedExigentProjection,
        ExigentPrecision,
        GlorifiedExigentPrecision
    }


    override fun compareTo(other: Mutaplasmid): Int {
        return type.compareTo(other.type)
    }


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


}


/**
 * The mutaplasmid matchers.
 */
private val MutaplasmidNameSubstringsToType = listOf(
    MutaplasmidMatcher(prefix = "Decayed") to Mutaplasmid.Type.Decayed,
    MutaplasmidMatcher(prefix = "Gravid") to Mutaplasmid.Type.Gravid,
    MutaplasmidMatcher(prefix = "Unstable") to Mutaplasmid.Type.Unstable,
    MutaplasmidMatcher(prefix = "Radical") to Mutaplasmid.Type.Radical,
    MutaplasmidMatcher(prefix = "Glorified Decayed") to Mutaplasmid.Type.GlorifiedDecayed,
    MutaplasmidMatcher(prefix = "Glorified Gravid") to Mutaplasmid.Type.GlorifiedGravid,
    MutaplasmidMatcher(prefix = "Glorified Unstable") to Mutaplasmid.Type.GlorifiedUnstable,
    MutaplasmidMatcher(prefix = "Glorified Radical") to Mutaplasmid.Type.GlorifiedRadical,
    MutaplasmidMatcher(prefix = "Exigent", substring = "Navigation") to Mutaplasmid.Type.ExigentNavigation,
    MutaplasmidMatcher(prefix = "Exigent", substring = "Firepower") to Mutaplasmid.Type.ExigentFirepower,
    MutaplasmidMatcher(prefix = "Exigent", substring = "Durability") to Mutaplasmid.Type.ExigentDurability,
    MutaplasmidMatcher(prefix = "Exigent", substring = "Projection") to Mutaplasmid.Type.ExigentProjection,
    MutaplasmidMatcher(prefix = "Exigent", substring = "Precision") to Mutaplasmid.Type.ExigentPrecision,
    MutaplasmidMatcher(prefix = "Glorified Exigent", substring = "Navigation") to Mutaplasmid.Type.GlorifiedExigentNavigation,
    MutaplasmidMatcher(prefix = "Glorified Exigent", substring = "Firepower") to Mutaplasmid.Type.GlorifiedExigentFirepower,
    MutaplasmidMatcher(prefix = "Glorified Exigent", substring = "Durability") to Mutaplasmid.Type.GlorifiedExigentDurability,
    MutaplasmidMatcher(prefix = "Glorified Exigent", substring = "Projection") to Mutaplasmid.Type.GlorifiedExigentProjection,
    MutaplasmidMatcher(prefix = "Glorified Exigent", substring = "Precision") to Mutaplasmid.Type.GlorifiedExigentPrecision,
)


/**
 * Encapsules the info to categorize a mutaplasmid by its name.
 */
private class MutaplasmidMatcher(
    val prefix: String? = null,
    val substring: String? = null
) {

    /**
     * Returns whether the given name matches.
     */
    fun matches(name: String): Boolean {
        if ((prefix != null) && !name.startsWith(prefix))
            return false
        if ((substring != null) && substring !in name)
            return false
        return true
    }

}


/**
 * Encapsulates how the mutaplasmid can modify a single attribute.
 */
data class AttributeMutation(


    /**
     * The id of the modified attribute.
     */
    val attributeId: Int,


    /**
     * The range of values for the factor by which the attribute is multiplied.
     */
    val factorRange: ClosedFloatingPointRange<Double>,


    /**
     * Whether a higher value in the mutated attribute is "better" in the context of this mutation.
     * This property, if not `null`, overrides the attribute's own [Attribute.highIsGood] property.
     */
    val highIsGood: Boolean?


) {


    /**
     * The start of the range, as a [BigDecimal].
     */
    private val factorRangeStartDecimal by lazy { BigDecimal.valueOf(factorRange.start) }


    /**
     * The end of the range, as a [BigDecimal].
     */
    private val factorRangeEndInclusiveDecimal by lazy { BigDecimal.valueOf(factorRange.endInclusive) }


    /**
     * Returns the range of the mutated attribute value, given its base value.
     */
    fun attributeValueRange(baseValue: Double): ClosedFloatingPointRange<Double> {
        // Use BigDecimal because floating point multiplication can give inexact
        // results, and then fail a range check when importing mutated attribute
        // values from other programs.
        // For example, we could get an upper bound of 1.29999999999997 which
        // will then fail when importing a fit with a mutated attribute value of 1.3
        val baseValueDecimal = BigDecimal.valueOf(baseValue)
        val v2 = (baseValueDecimal * factorRangeEndInclusiveDecimal).toDouble()
        val v1 = (baseValueDecimal * factorRangeStartDecimal).toDouble()
        return if (baseValue >= 0)
            v1 .. v2
        else
            v2 .. v1
    }


    /**
     * Returns whether a higher value is good in this mutation.
     */
    fun highIsGood(attribute: Attribute<*>): Boolean {
        if (attribute.id != attributeId)
            throw IllegalArgumentException("Wrong attribute given")
        return this.highIsGood ?: attribute.highIsGood ?:
            error("Unknown whether attribute $attribute high is good")
    }


}

