package theorycrafter.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import eve.data.*
import eve.data.ordering.*
import eve.data.typeid.isEmpireNavyFactionItem
import eve.data.typeid.isLowTierPirateFactionItem
import eve.data.utils.ValueByEnum
import eve.data.utils.valueByEnum
import kotlinx.coroutines.Deferred
import theorycrafter.fitting.Fit
import theorycrafter.fitting.Ship
import theorycrafter.fitting.canDroneTypeBeFitted
import theorycrafter.utils.*

/**
 * Auto-suggest functions.
 */
class EveAutoSuggest(private val eveData: EveData) {


    /**
     * The usual sorting of eve item types according to likely use.
     */
    private val stdAutoSuggestComparator =
        compareBy<EveItemType> {
            val metaLevel = it.metaLevel ?: 20

            // Tech2 is most popular, then going down by metalevel
            if (metaLevel <= 5)
                5 - metaLevel
            else  // Storyline and upwards, the higher metalevel the rarer the item
                metaLevel
        }.then(compareBy { it.name } )  // For consistency


    /**
     * The auto-suggest for ship types by name.
     */
    val shipTypes: AutoSuggest<ShipType> by onBackgroundThread {
        autoSuggest(
            items = eveData.shipTypes,
            itemToSearchString = ShipType::name,
            minCharacters = 2
        )
    }


    /**
     * The auto-suggest of module names for each slot type (and rig size, in case of rig slots).
     */
    private val moduleNamesBySlot: Map<Pair<ModuleSlotType, ItemSize?>, Deferred<AutoSuggest<ModuleType>>> =
        eveData.moduleTypes
            .groupBy { it.slotType to it.rigSize }
            .mapValues { (_, moduleTypes) ->
                onBackgroundThread {
                    autoSuggest(
                        items = moduleTypes,
                        itemToSearchString = ModuleType::name,
                        minCharacters = 2,
                        suggestionComparator = stdAutoSuggestComparator,
                        preferEarlierTerms = true
                    )
                }
            }


    /**
     * Returns a [remember]ed auto-suggest of modules for the given slot type on the given ship.
     */
    @Composable
    fun rememberForModules(ship: Ship, slotType: ModuleSlotType): AutoSuggest<ModuleType> {
        val shipType = ship.type
        val rigSize = if (slotType == ModuleSlotType.RIG) shipType.fitting.rigSize else null

        val autoSuggest = moduleNamesBySlot.getValue(slotType to rigSize).getOrWaitForCompleted()

        val dependencyValues = ship.canFitModuleKeys
        return remember(ship, slotType, *dependencyValues.toTypedArray()) {
            autoSuggest.filterResults { ship.canFit(it) }
        }
    }


    /**
     * The comparator for sorting charge types in the auto-suggest.
     */
    private val chargeTypeAutosuggestComparator: Comparator<ChargeType> = compareBy {
        with(eveData) {
            when {
                it.isEmpireNavyFactionItem -> 0
                it.metaGroupId == metaGroups.tech1.id -> 1
                it.metaGroupId == metaGroups.tech2.id -> 2
                it.isLowTierPirateFactionItem -> 3
                else -> 4
            }
        }
    }


    /**
     * Returns a [remember]ed auto-suggest for charges loaded into the given module type.
     */
    @Composable
    fun rememberForChargeTypes(moduleType: ModuleType): AutoSuggest<ChargeType> = remember(moduleType) {
        autoSuggest(
            items = eveData.chargesForModule(moduleType),
            itemToSearchString = ChargeType::name,
            minCharacters = 1,
            suggestionComparator = chargeTypeAutosuggestComparator
        )
    }


    /**
     * The auto-suggest for drone types.
     */
    private val droneTypes by onBackgroundThread {
        autoSuggest(
            items = eveData.droneTypes,
            itemToSearchString = DroneType::name,
            minCharacters = 2,
            suggestionComparator = stdAutoSuggestComparator,
            preferEarlierTerms = true
        )
    }


    /**
     * Returns a [remember]ed auto-suggest for drones.
     */
    @Composable
    fun rememberForDroneTypes(fit: Fit): AutoSuggest<DroneType> {
        val fitBandwidth = fit.drones.bandwidth.total
        val fitCapacity = fit.drones.capacity.total
        val maxActiveDrones = fit.drones.activeCount.total

        return remember(fitBandwidth, fitCapacity, maxActiveDrones) {
            droneTypes.filterResults { droneType ->
                canDroneTypeBeFitted(
                    droneType = droneType,
                    fitBandwidth = fitBandwidth,
                    fitCapacity = fitCapacity,
                    maxActiveDrones = maxActiveDrones
                )
            }
        }
    }


    /**
     * The auto-suggest for cargo items by name.
     */
    val cargoItemTypes: AutoSuggest<EveItemType> by onBackgroundThread {
        autoSuggest(
            items = eveData.cargoItemTypes,
            itemToSearchString = EveItemType::name,
            minCharacters = 2,
            suggestionComparator = compareBy<EveItemType> {
                // Sort by the popularity of putting those items in the cargohold
                when (it) {
                    is ChargeType -> 0
                    is MaterialType -> 1
                    is ImplantType -> 2
                    else -> 3
                }
            }.then { item1, item2 ->
                if ((item1 is ChargeType) && (item2 is ChargeType))
                    return@then chargeTypeAutosuggestComparator.compare(item1, item2)

                val stdComparatorResult = stdAutoSuggestComparator.compare(item1, item2)
                if (stdComparatorResult != 0)
                    return@then stdComparatorResult

                return@then item1.name.compareTo(item2.name) // For consistency
            }
        )
    }


    /**
     * The auto-suggest for items that can have a price, by name.
     */
    val pricedItemTypes: AutoSuggest<EveItemType> by onBackgroundThread {
        autoSuggest(
            items = eveData.cargoItemTypes,
            itemToSearchString = EveItemType::name,
            minCharacters = 2,
        )
    }


    /**
     * The auto-suggest for implant types by name.
     */
    val implantTypes: AutoSuggest<ImplantType> by onBackgroundThread {
        autoSuggest(
            items = eveData.implantTypes,
            itemToSearchString = ImplantType::name,
            minCharacters = 2,
            suggestionComparator = IMPLANT_NUMBERS_COMPARATOR
                    then LEARNING_IMPLANTS_COMPARATOR
                    then SAME_SLOT_PIRATE_IMPLANTS_COMPARATOR
                    then SAME_TYPE_PIRATE_IMPLANTS_COMPARATOR
                    then compareBy { it.name }  // For consistency
        )
    }


    /**
     * Returns a [remember]ed auto-suggest for implant types for the given slot.
     */
    @Composable
    fun rememberForImplantTypes(implantSlot: Int): AutoSuggest<ImplantType> = remember(implantSlot) {
        // Instead of pre-creating an autosuggest for every implant slot, we simply filter the results.
        implantTypes.filterResults { it.slot == implantSlot }
    }


    /**
     * The auto-suggest for pirate implant sets.
     */
    val pirateImplantSets: AutoSuggest<PirateImplantSet> by onBackgroundThread {
        autoSuggest(
            items = eveData.implantTypes.pirateSets,
            minCharacters = 2,
            itemToSearchString = PirateImplantSet::displayName,
            suggestionComparator = compareBy(PirateImplantSet::grade, PirateImplantSet::name)
        )
    }


    /**
     * The auto-suggest for booster types by name.
     */
    val boosterTypes: AutoSuggest<BoosterType> by onBackgroundThread {
        autoSuggest(
            items = eveData.boosterTypes,
            itemToSearchString = BoosterType::name,
            minCharacters = 2,
            suggestionComparator = CLASSIC_BOOSTER_COMPARATOR
                    then AGENCY_BOOSTER_COMPARATOR
                    then compareBy { it.name }  // For consistency
        )
    }


    /**
     * Returns a [remember]ed auto-suggest for booster types for the given slot.
     */
    @Composable
    fun rememberForBoosterTypes(boosterSlot: Int): AutoSuggest<BoosterType> = remember(boosterSlot) {
        // Instead of pre-creating an autosuggest for every booster slot, we simply filter the results.
        boosterTypes.filterResults { it.slot == boosterSlot }
    }


    /**
     * The auto-suggest of tactical modes for each ship type.
     */
    private val tacticalModesByShipType: Map<ShipType, Deferred<AutoSuggest<TacticalModeType>>> =
        eveData.tacticalModeTypesByShipType.mapValues { entry ->
            onBackgroundThread {
                autoSuggest(
                    items = entry.value.values,
                    itemToSearchString = { it.shortName() },
                    minCharacters = 1,
                    suggestionComparator = compareBy { it.kind }
                )
            }
        }


    /**
     * The auto-suggest for tactical modes for the given ship type.
     */
    fun tacticalModes(shipType: ShipType): AutoSuggest<TacticalModeType> {
        val deferred = tacticalModesByShipType[shipType] ?:
            throw IllegalArgumentException("The ship type $shipType does not use tactical modes")
        return deferred.getOrWaitForCompleted()
    }


    /**
     * The auto-suggest of environment types.
     */
    val environmentTypes: AutoSuggest<EnvironmentType> by onBackgroundThread {
        autoSuggest(
            items = eveData.environmentTypes,
            itemToSearchString = EnvironmentType::name,
            minCharacters = 1,
            suggestionComparator = compareBy { it.name }
        )
    }


    /**
     * The auto-suggest of subsystems for each ship type and subsystem kind.
     */
    private val subsystemTypesByShipTypeAndKind:
            Map<ShipType, ValueByEnum<SubsystemType.Kind, Deferred<AutoSuggest<SubsystemType>>>> =
        eveData.subsystemTypeByShipType.mapValues { (_, subsystemTypeByKind) ->
            valueByEnum { subsystemKind ->
                onBackgroundThread {
                    autoSuggest(
                        items = subsystemTypeByKind[subsystemKind],
                        itemToSearchString = { it.shortName(includeKind = false) },
                        minCharacters = 1,
                        suggestionComparator = compareBy {
                            it.kind.ordinal
                        }
                    )
                }
            }
        }


    /**
     * The auto-suggest for subsystem types for the given ship type and subsystem kind.
     */
    fun subsystemTypes(shipType: ShipType, subsystemKind: SubsystemType.Kind): AutoSuggest<SubsystemType> {
        val deferred = subsystemTypesByShipTypeAndKind[shipType]?.get(subsystemKind) ?:
            throw IllegalArgumentException("Ship type $shipType cannot fit subsystems of kind $subsystemKind")

        return deferred.getOrWaitForCompleted()
    }


    /**
     * Returns a [Deferred] for an auto-suggest for modules and drones that pass the given [predicate].
     */
    private fun filteredModulesAndDrones(predicate: (ModuleOrDroneType) -> Boolean) =
        onBackgroundThread {
            autoSuggest(
                items = eveData.moduleTypes.filter(predicate) +
                        eveData.droneTypes.filter(predicate),
                itemToSearchString = ModuleOrDroneType::name,
                minCharacters = 2,
                suggestionComparator = stdAutoSuggestComparator,
                preferEarlierTerms = true
            )
        }


    /**
     * The auto-suggest for hostile module/drone effects.
     */
    val hostileModulesAndDrones: AutoSuggest<ModuleOrDroneType> by filteredModulesAndDrones { eveData.isOffensive(it) }


    /**
     * The auto-suggest for friendly module/drone effects.
     */
    val friendlyModulesAndDrones: AutoSuggest<ModuleOrDroneType> by filteredModulesAndDrones { eveData.isAssistive(it) }


    /**
     * The auto-suggest for all market items.
     */
    val allMarketItems: AutoSuggest<EveItemType> by lazy {
        autoSuggest(
            items = eveData.itemTypesByMarketGroup.values.flatten(),
            itemToSearchString = EveItemType::name,
            minCharacters = 3
        )
    }


}