package theorycrafter.ui.fiteditor

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import eve.data.*
import eve.data.ordering.IMPLANT_NUMBERS_COMPARATOR
import eve.data.ordering.LEARNING_IMPLANTS_COMPARATOR
import eve.data.ordering.SAME_SLOT_PIRATE_IMPLANTS_COMPARATOR
import eve.data.typeid.*
import theorycrafter.TheorycrafterContext
import theorycrafter.fitting.Fit
import theorycrafter.fitting.canDroneTypeBeFitted
import theorycrafter.tournaments.TournamentRules
import theorycrafter.utils.MetaGroupOrder
import theorycrafter.utils.filterIf


/**
 * Specifies the prev/next item in the list of item variations when pressing left/right in a slot.
 */
class Carousel<T> (val items: List<T>) {


    /**
     * Returns the item following the given one, which should be displayed when pressing "right".
     */
    fun next(current: T): T {
        val currentIndex = items.indexOf(current)
        if ((currentIndex == -1) && (current != null))
            return current

        return items[(currentIndex+1).coerceAtMost(items.lastIndex)]
    }


    /**
     * Returns the item before the given one, which should be displayed when pressing "left".
     */
    fun prev(current: T): T {
        var currentIndex = items.indexOf(current)
        if (currentIndex == -1) {
            if (current != null)
                return current
            else
                currentIndex = items.size
        }

        return items[(currentIndex - 1).coerceAtLeast(0)]
    }


    /**
     * Returns whether the carousel contains the given item.
     */
    fun contains(item: T): Boolean = items.contains(item)


}


/**
 * Returns a remembered [Carousel] for the given module type to be fitted on the given ship.
 */
@Composable
fun rememberModuleCarousel(
    moduleType: ModuleType,
    shipType: ShipType,
    isFlagship: Boolean,
    tournamentRules: TournamentRules?
): Carousel<ModuleType> {
    if (moduleType.mutation != null)
        return Carousel(listOf(moduleType))

    val variationParentTypeId = moduleType.variationParentTypeId
    return remember(variationParentTypeId, shipType, tournamentRules) {
        val metaGroupOrder = MetaGroupOrder
        val eveData = TheorycrafterContext.eveData
        val moduleTypes = eveData.moduleTypesByVariationParentTypeId(variationParentTypeId)
            .filterIf(tournamentRules != null) {
                tournamentRules.isModuleLegal(it, shipType, isFlagship)
            }
            .sortedWith(
                compareBy(
                    { metaGroupOrder[it.metaGroupId] },
                    { it.metaLevel ?: 0 },
                    { it.powerNeed ?: 0 },
                    { it.cpuNeed ?: 0 },
                    { it.capacitorNeed?.unaryPlus() ?: 0 },
                    { it.itemId }
                )
            )

        Carousel(moduleTypes)
    }
}


/**
 * Returns a rememembered [Carousel] for charges loaded into the given module type.
 */
@Composable
fun rememberChargeCarousel(moduleType: ModuleType, tournamentRules: TournamentRules?): Carousel<ChargeType?> {
    return remember(moduleType, tournamentRules) {
        val chargeTypes = chargeGrouping(moduleType)
            .flatten()
            .filterIf(tournamentRules != null) { tournamentRules.isChargeLegal(it, moduleType) }
        Carousel(chargeTypes)
    }
}


/**
 * The way we sort most of the drones in the carousel.
 */
private val STD_DRONE_CAROUSEL_ORDER: Comparator<DroneType> = compareBy(
    { it.metaLevel ?: 0 },
    { it.name.contains("Integrated") },
    { it.name.contains("Augmented") },
    { it.primaryDamageType },
    { it.itemId }
)



/**
 * Returns the combat drone types for a carousel.
 */
@Composable
private fun rememberCarouselCombatDroneTypes(droneType: DroneType): List<DroneType> {
    // For combat drones, the carousel will contain drones from the same market group (light, medium, heavy, sentry),
    // so that it's easy to switch between different damage types.

    val marketGroup = droneType.marketGroup ?: return listOf(droneType)
    return remember(marketGroup){
        TheorycrafterContext.eveData.droneTypesByMarketGroup(marketGroup)
            .sortedWith(STD_DRONE_CAROUSEL_ORDER)
    }
}


/**
 * Returns the drone types that have the same [DroneType.variationParentTypeId] as the given type.
 * This is used for mining drones.
 */
@Composable
private fun rememberCarouselDroneTypesByVariationParentTypeId(droneType: DroneType): List<DroneType> {
    val variationParentTypeId = droneType.variationParentTypeId
    return remember(variationParentTypeId){
        TheorycrafterContext.eveData.droneTypesByVariationParentTypeId(variationParentTypeId)
            .sortedWith(STD_DRONE_CAROUSEL_ORDER)
    }
}


/**
 * Returns the drone types filtered by the given function, ordered by size and metalevel.
 * This is used for all EWAR and logistics drones so that the carousel shows drones of the same ewar type, ordered from
 * smallest t1 to largest t2.
 */
@Composable
private fun rememberCarouselDroneTypesByKind(filter: context(EveData) (DroneType) -> Boolean): List<DroneType> {
    return remember(filter) {
        TheorycrafterContext.eveData.droneTypes
            .filter { filter(TheorycrafterContext.eveData, it) }
            .sortedWith(
                compareBy(
                    { it.bandwidthUsed },
                    { it.metaLevel ?: 0 }
                )
            )
    }
}


/**
 * Returns a remembered list of [DroneType] for a drone carousel.
 */
@Composable
fun rememberCarouselDroneTypes(
    droneType: DroneType,
    fit: Fit,
    tournamentRules: TournamentRules?
): List<DroneType> {
    if (droneType.mutation != null)
        return listOf(droneType)

    val eveData = TheorycrafterContext.eveData
    with(eveData) {
        val droneTypes = when {
            droneType.isCombatDrone() -> rememberCarouselCombatDroneTypes(droneType)
            droneType.isMiningDrone() -> rememberCarouselDroneTypesByVariationParentTypeId(droneType)
            else -> {
                val droneKinds = listOf(
                    DroneType::isTargetPaintingDrone,
                    DroneType::isStasisWebifyingDrone,
                    DroneType::isSensorDampeningDrone,
                    DroneType::isTrackingDisruptingDrone,
                    DroneType::isEnergyNeutralizerDrone,
                    DroneType::isEcmDrone,
                    DroneType::isShieldMaintenanceBot,
                    DroneType::isArmorMaintenanceBot,
                    DroneType::isHullMaintenanceBot
                )
                val kind = droneKinds.firstOrNull { it(eveData, droneType) }
                if (kind == null)
                    listOf(droneType)
                else
                    rememberCarouselDroneTypesByKind(kind)
            }
        }

        // Filter out drone types that can't be fitted at all
        val fitBandwidth = fit.drones.bandwidth.total
        val fitCapacity = fit.drones.capacity.total
        val maxActiveDrones = fit.drones.activeCount.total

        return remember(droneType, droneTypes, fitBandwidth, fitCapacity, maxActiveDrones, tournamentRules) {
            droneTypes.filter {
                if (!tournamentRules.isDroneLegal(it))
                    return@filter false

                canDroneTypeBeFitted(
                    droneType = it,
                    fitBandwidth = fitBandwidth,
                    fitCapacity = fitCapacity,
                    maxActiveDrones = maxActiveDrones
                )
            }
        }
    }
}


/**
 * Returns a remembered [Carousel] for the given implant type.
 */
@Composable
fun rememberImplantCarousel(implantType: ImplantType, tournamentRules: TournamentRules?): Carousel<ImplantType> {
    val variationParentTypeId = implantType.variationParentTypeId
    return remember(variationParentTypeId, tournamentRules) {
        val implantTypes = TheorycrafterContext.eveData.implantTypes.byVariationParentTypeId(variationParentTypeId)
            .filterIf(tournamentRules != null) { tournamentRules.isImplantLegal(it) }
            .sortedWith(
                IMPLANT_NUMBERS_COMPARATOR
                    then LEARNING_IMPLANTS_COMPARATOR
                    then SAME_SLOT_PIRATE_IMPLANTS_COMPARATOR
            )
        Carousel(implantTypes)
    }
}


/**
 * Returns a remembered [Carousel] for the given booster type.
 */
@Composable
fun rememberBoosterCarousel(boosterType: BoosterType, tournamentRules: TournamentRules?): Carousel<BoosterType> {
    val variationParentTypeId = boosterType.variationParentTypeId
    return remember(variationParentTypeId, tournamentRules) {
        val attributesOfInterest = with(TheorycrafterContext.eveData.attributes) {
            // The boolean value is whether higher value is better
            listOf(
                shieldBoostMultiplier to true,
                armorDamageAmountBonus to true,
                capacitorCapacityBonus to true,
                signatureRadiusBonus to false,
                trackingSpeedBonus to true,
                rangeSkillBonus to true,
                falloffBonus to true,
                aoeCloudSizeBonus to false,
                velocityBonus to true,
                damageMultiplierBonus to true,
            )
        }

        val boosterTypes = TheorycrafterContext.eveData.boosterTypesByVariationParentTypeId(variationParentTypeId)
            .filterIf(tournamentRules != null) {
                tournamentRules.isBoosterLegal(it)
            }
        val commonAttribute = attributesOfInterest.find { (attribute, _) ->
            boosterTypes.all { it.hasAttribute(attribute) }
        }

        val sortedBoosterTypes =
            if (commonAttribute == null)
                boosterTypes.sortedBy(BoosterType::name)
            else {
                val (attribute, higherIsBetter) = commonAttribute
                val multiplier = if (higherIsBetter) 1 else -1
                boosterTypes.sortedWith(
                    compareBy(
                        { it.attributeValue(attribute) * multiplier },
                        BoosterType::name  // For consistency
                    )
                )
            }
        Carousel(sortedBoosterTypes)
    }
}


/**
 * Returns a sorting key for environments.
 */
private fun EnvironmentType.sortKey(): Comparable<*> = with(TheorycrafterContext.eveData) {
    when {
        isWormhole() -> name  // e.g. "Class N Pulsar"
        isMetaliminalStorm() -> name.startsWith("Strong")  // e.g. "Strong Metaliminal Electric Storm"
        isAbyssalStorm() -> name.substringAfterLast(' ')  // e.g. "Abyssal Electrical Storm N"
        else -> name
    }
}


/**
 * Returns a remembered [Carousel] of environments for the given environment.
 */
@Composable
fun rememberEnvironmentCarousel(
    environmentType: EnvironmentType,
    tournamentRules: TournamentRules?
): Carousel<EnvironmentType> {
    val variationParentTypeId = environmentType.variationParentTypeId
    return remember(variationParentTypeId, tournamentRules) {
        if (!tournamentRules.hasEnvironments())
            return@remember Carousel(listOf(environmentType))

        val envTypes = TheorycrafterContext.eveData.environmentTypesByVariationParentTypeId(variationParentTypeId)
            .sortedWith(
                compareBy(
                    { it.sortKey() },
                    { it.itemId }
                )
            )
        Carousel(envTypes)
    }
}


/**
 * Returns a remembered [Carousel] of tactical modes for the given ship type.
 */
@Composable
fun rememberTacticalModeCarousel(shipType: ShipType): Carousel<TacticalModeType> {
    return remember(shipType) {
        with(TheorycrafterContext.eveData) {
            val tacticalModes = tacticalModeTypes(shipType).values.sortedBy { it.kind }
            Carousel(tacticalModes)
        }
    }
}


/**
 * Returns a remembered [Carousel] of subsystems for the given ship and subsystem kind.
 */
@Composable
fun rememberSubsystemCarousel(shipType: ShipType, subsystemKind: SubsystemType.Kind): Carousel<SubsystemType> {
    return remember(shipType, subsystemKind) {
        with(TheorycrafterContext.eveData){
            val shipSubsystems = subsystemTypes(shipType)
            val subsystemsOfKind = shipSubsystems[subsystemKind].sortedBy { it.itemId }
            Carousel(subsystemsOfKind)
        }
    }
}
