/**
 * Implements grouping, sorting and default selection of charge types.
 */

package theorycrafter.ui.fiteditor

import eve.data.ChargeType
import eve.data.ModuleType
import eve.data.typeid.*
import theorycrafter.TheorycrafterContext


/**
 * A grouping of charges into sections.
 * This is used in the charge selection carousel and context menu.
 */
class ChargeGrouping(


    /**
     * The entries in the grouping.
     */
    val entries: List<Entry>


){


    /**
     * Creates a new grouping from the specified entries.
     */
    @Suppress("unused")
    constructor(vararg entries: Entry): this(entries.toList())


    /**
     * An entry in the grouping of charges.
     */
    sealed interface Entry


    /**
     * A titled section with a list of charges.
     */
    class Section(


        /**
         * The title of the section.
         */
        val title: String,


        /**
         * The charges in the section, in their correct order.
         */
        val charges: List<ChargeType>


    ): Entry {

        override fun toString(): String {
            return "Section(title='$title')"
        }

    }


    /**
     * A single charge entry.
     */
    class SingleCharge(


        /**
         * The charge.
         */
        val charge: ChargeType?


    ): Entry {

        override fun toString(): String {
            return "Charge(charge=$charge)"
        }

    }


    /**
     * Flattens this [ChargeGrouping] into a list of charge types.
     */
    fun flatten(): List<ChargeType?> = buildList {
        for (entry in entries) {
            when (entry) {
                is SingleCharge -> add(entry.charge)
                is Section -> addAll(entry.charges)
            }
        }
    }


    companion object{


        /**
         * Returns a [ChargeGrouping] consisting entirely of [SingleCharge] entries.
         */
        fun flat(charges: List<ChargeType?>) = ChargeGrouping(
            entries = charges.map(::SingleCharge)
        )


    }


}


/**
 * Returns the grouping for the charges of the module.
 */
fun chargeGrouping(moduleType: ModuleType): ChargeGrouping = with(TheorycrafterContext.eveData){
    val charges = chargesForModule(moduleType)
    return when {
        moduleType.isCapacitorBooster()
                || moduleType.isAncillaryShieldBooster()
                || moduleType.isAncillaryRemoteShieldBooster() ->
                    capBoosterGrouping(moduleType, charges)
        moduleType.isCommandBurst() -> commandBurstChargeGrouping(charges)
        moduleType.isBombLauncher() -> bombGrouping(charges)
        moduleType.isMissileLauncher() -> missileGrouping(charges)
        moduleType.isProjectileWeapon() -> projectileGrouping(charges)
        moduleType.isEnergyWeapon() -> crystalGrouping(charges)
        moduleType.isHybridWeapon() -> hybridChargeGrouping(charges)
        moduleType.isEntropicDisintegrator() -> exoticPlasmaChargeGrouping(charges)
        moduleType.isVortonProjector() -> condenserPackGrouping(charges)
        moduleType.isBreacherPodLauncher() -> breacherPodGrouping(charges)
        else -> defaultChargeGrouping(charges)
    }
}


/**
 * Groups the given charges into sections by their meta-group.
 */
fun metaGroupSections(
    charges: Collection<ChargeType>,
    nullMetaGroupName: String,
    sortAndFilterSection: (List<ChargeType>) -> List<ChargeType>
): List<ChargeGrouping.Section> {
    val metaGroups = TheorycrafterContext.eveData.metaGroups
    return charges
        .groupBy(ChargeType::metaGroupId)
        .entries
        .sortedBy { it.key }
        .map {
            val metaGroupName = it.key?.let { metaGroupId -> metaGroups[metaGroupId].name } ?: nullMetaGroupName
            ChargeGrouping.Section(
                title = metaGroupName,
                charges = sortAndFilterSection(it.value)
            )
        }
}


/**
 * Groups the given charges into sections by their main damage type.
 */
@Suppress("unused")
fun damageTypeSections(
    charges: Collection<ChargeType>,
    sortAndFilterSection: (List<ChargeType>) -> List<ChargeType>
): List<ChargeGrouping.Section> {
    return charges.groupBy(ChargeType::primaryDamageType)
        .entries
        .sortedBy { it.key }
        .map {
            ChargeGrouping.Section(
                title = it.key!!.displayName,
                charges = sortAndFilterSection(it.value)
            )
        }
}


/**
 * Groups the given capacitor boosters.
 */
private fun capBoosterGrouping(moduleType: ModuleType, charges: Collection<ChargeType>): ChargeGrouping {
    // Group by meta group, so that regular and "Navy" charges are grouped separately.
    // Within each section, sort by volume.
    val sections = metaGroupSections(
        charges = charges,
        nullMetaGroupName = "Regular",  // All cap boosters have a meta group, but just in case something changes...
        sortAndFilterSection = { it.sortedBy(ChargeType::volume) }
    )

    return with(TheorycrafterContext.eveData) {
        ChargeGrouping(
            buildList {
                if (moduleType.isAncillaryShieldBooster() || moduleType.isAncillaryRemoteShieldBooster())
                    add(ChargeGrouping.SingleCharge(null))
                addAll(sections)
            }
        )
    }
}


/**
 * Groups the given command burst charges.
 */
private fun commandBurstChargeGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    return ChargeGrouping.flat(
        charges.sortedWith(COMMAND_BURST_CHARGE_COMPARATOR)
    )
}


/**
 * Groups the given bombs.
 */
private fun bombGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    // This puts damage bombs first (they have the lowest skill requirement), sorted in standard damage type order,
    // then all the "special" bombs, sorted by required skill level.
    return ChargeGrouping.flat(
        charges.sortedWith(
            compareBy(
                { it.requiredSkillLevel(BOMB_DEPLOYMENT_SKILL_ID) },
                ChargeType::primaryDamageType,
                ChargeType::itemId
            )
        )
    )
}


/**
 * Groups the given missiles.
 */
private fun missileGrouping(charges: Collection<ChargeType>): ChargeGrouping {
    // This groups the missiles into sections:
    // 1. Regular tech 1
    // 2. Empire faction
    // 3. Rage or fury
    // 4. Javelin or precision
    // 5. Low-tier pirate (e.g. guristas)
    // 6. Elite pirate (e.g. dread guristas)
    // 7. Tech 1 auto-targeting
    // 8. Faction auto-targeting
    val eveData = TheorycrafterContext.eveData

    return weaponChargeGrouping(charges) { chargeType ->
        with(eveData) {
            when {
                chargeType.isJavelinMissile() -> SectionSpec("Javelin", 4)
                chargeType.isPrecisionMissile() -> SectionSpec("Precision", 4)
                chargeType.isRageMissile() -> SectionSpec("Rage", 3)
                chargeType.isFuryMissile() -> SectionSpec("Fury", 3)
                chargeType.isLowTierPirateFactionItem -> SectionSpec("Pirate", 5)
                chargeType.isElitePirateFactionItem -> SectionSpec("Elite Pirate", 6)
                chargeType.isAutoTargetingMissile() && chargeType.isEmpireNavyFactionItem ->
                    SectionSpec("Faction Auto-Targeting", 8)
                chargeType.isEmpireNavyFactionItem -> SectionSpec("Empire Faction", 2)
                chargeType.isAutoTargetingMissile() -> SectionSpec("Auto-Targeting", 7)
                else -> SectionSpec("Tech I", 1)
            }
        }
    }
}


/**
 * Specifies a grouping section's title and order.
 */
private data class SectionSpec(val title: String, val order: Int)


/**
 * Returns a [ChargeGrouping] of the given weapon charges by grouping them into sections according to the result of
 * applying [sectionOfCharge] to them. Within each section the charges are sorted by total damage and then by primary
 * damage type.
 */
private fun weaponChargeGrouping(
    charges: Collection<ChargeType>,
    sectionOfCharge: (ChargeType) -> SectionSpec
): ChargeGrouping {
    return ChargeGrouping(
        charges.groupBy(sectionOfCharge)
            .entries
            .sortedBy { it.key.order }
            .map {
                ChargeGrouping.Section(
                    title = it.key.title,
                    charges = it.value.sortedWith(
                        compareBy(
                            ChargeType::totalDamage,
                            ChargeType::primaryDamageType
                        )
                    )
                )
            }
    )
}


/**
 * Groups the given projectile charges.
 */
private fun projectileGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    // This groups the projectiles into sections:
    // 1. Tech 1
    // 2. Tech 2
    // 3. Republic Fleet
    // 4. Arch Angel
    // 5. Domination
    return with(TheorycrafterContext.eveData){
        weaponChargeGrouping(charges){ chargeType ->
            val name = chargeType.name
            when{
                chargeType.metaGroupId == metaGroups.tech2.id -> SectionSpec("Tech 2", 2)
                name.contains("Republic Fleet") -> SectionSpec("Republic Fleet", 3)
                name.contains("Arch Angel") -> SectionSpec("Arch Angel", 4)
                name.contains("Domination") -> SectionSpec("Domination", 5)
                else -> SectionSpec("Tech 1", 1)
            }
        }
    }
}


/**
 * Groups the given laser crystals.
 */
private fun crystalGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    // This groups the crystals into sections:
    // 1. Tech 1
    // 2. Tech 2
    // 3. Imperial Navy
    // 4. Blood
    // 5. Sanshas
    // 6. Dark Blood
    // 7. True Sanshas
    return with(TheorycrafterContext.eveData){
        weaponChargeGrouping(charges){ chargeType ->
            val name = chargeType.name
            when{
                chargeType.metaGroupId == metaGroups.tech2.id -> SectionSpec("Tech 2", 2)
                name.contains("Imperial Navy") -> SectionSpec("Imperial Navy", 3)
                name.contains("Dark Blood") -> SectionSpec("Dark Blood", 6)
                name.contains("True Sanshas") -> SectionSpec("True Sanshas", 7)
                name.contains("Blood") -> SectionSpec("Blood", 4)
                name.contains("Sanshas") -> SectionSpec("Sanshas", 5)
                else -> SectionSpec("Tech 1", 1)
            }
        }
    }
}


/**
 * Groups the given hybrid charges.
 */
private fun hybridChargeGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    // This groups the charges into sections:
    // 1. Tech 1
    // 2. Tech 2
    // 3. Federation Navy
    // 4. Caldari Navy
    // 5. Guristas
    // 6. Shadow
    // 7. Dread Guristas
    // 8. Guardian
    return with(TheorycrafterContext.eveData){
        weaponChargeGrouping(charges){ chargeType ->
            val name = chargeType.name
            when{
                chargeType.metaGroupId == metaGroups.tech2.id -> SectionSpec("Tech 2", 2)
                name.contains("Federation Navy") -> SectionSpec("Federation Navy", 3)
                name.contains("Caldari Navy") -> SectionSpec("Caldari Navy", 4)
                name.contains("Shadow") -> SectionSpec("Shadow", 5)
                name.contains("Dread Guristas") -> SectionSpec("Dread Guristas", 7)
                name.contains("Guardian") -> SectionSpec("Guardian", 8)
                name.contains("Guristas") -> SectionSpec("Dread Guristas", 6)
                else -> SectionSpec("Tech 1", 1)
            }
        }
    }
}


/**
 * Groups the given Exotic Plasma charges (for Entropic Disintegrator).
 */
private fun exoticPlasmaChargeGrouping(charges: Collection<ChargeType>) = singleGroupSortedByRange(charges)


/**
 * Groups the given Condenser packs (for Vorton Projector).
 */
private fun condenserPackGrouping(charges: Collection<ChargeType>) = singleGroupSortedByRange(charges)


/**
 * Groups the given breacher pods.
 */
private fun breacherPodGrouping(charges: Collection<ChargeType>) = singleGroupSortedByRange(charges)


/**
 * Returns a single group, sorted by the weapon range multiplier of the charges.
 */
private fun singleGroupSortedByRange(charges: Collection<ChargeType>): ChargeGrouping {
    return with(TheorycrafterContext.eveData){
        ChargeGrouping.flat(
            charges.sortedBy { it.attributeValueOrNull(attributes.weaponRangeMultiplier) ?: 0.0 }
        )
    }
}


/**
 * The default grouping of charges.
 */
private fun defaultChargeGrouping(charges: Collection<ChargeType>): ChargeGrouping{
    return ChargeGrouping.flat(
        listOf(null) +
                charges.sortedWith(
                    compareBy(
                        ChargeType::groupId,
                        ChargeType::metaGroupId,
                        ChargeType::itemId
                    )
                )
    )
}