package theorycrafter.tournaments.impl

import eve.data.*
import eve.data.typeid.*
import theorycrafter.fitting.Fit
import theorycrafter.tournaments.*
import theorycrafter.ui.fiteditor.PackForBattleConfiguration
import theorycrafter.ui.fiteditor.consistentMaxByOrNull
import theorycrafter.ui.fiteditor.isEmpireFactionHybridChargeProperForShipType
import theorycrafter.ui.fiteditor.preferredMissile


/**
 * The base class for Alliance Tournament composition rules.
 */
open class AllianceTournamentCompositionRules(
    eveData: EveData,
    maxCompositionSize: Int,
    maxCompositionCost: Int,
    shipCostByName: Map<String, Int>,
    val maxShipsInSizeClass: Int,
    val nonBattleshipFlagships: Set<String> = emptySet(),
): BasicPointsCompositionRules(
    eveData = eveData,
    maxCompositionSize = maxCompositionSize,
    maxCompositionCost = maxCompositionCost,
    shipCostByType = shipCostByName.mapKeys { (name, _) -> eveData.shipType(name) },
) {


    override fun compositionShipsIllegalityReason(composition: Composition): List<String?> {
        with(eveData) {
            val basicShipIllegality = super.compositionShipsIllegalityReason(composition)
            val sizeClassIllegality = shipIllegalityBySizeClass(
                composition = composition,
                maxInSizeClass = {
                    when (it) {
                        ShipSizeClass.LOGI_CRUISER -> 1
                        ShipSizeClass.LOGI_FRIGATE -> 2
                        else -> maxShipsInSizeClass
                    }
                },
                ::standardLogiLegalityCheck
            )

            // Allow at most one AT prize ship, as only one flagship is allowed at a time
            val atPrizeShipFlagshipIllegality = shipIllegalityByKind(
                composition = composition,
                illegalityReason = { "At most one AT prize ship is allowed" },
                shipKind = {
                    if (it.shipType.name in nonBattleshipFlagships) "atShipFlagship" else "other"
                },
                maxOfKind = { if (it == "atShipFlagship") 1 else Unlimited }
            )

            return combineIllegalityReasons(basicShipIllegality, sizeClassIllegality, atPrizeShipFlagshipIllegality)
        }
    }


}


/**
 * The fitting rules for Alliance Tournaments.
 */
open class AllianceTournamentFittingRules(
    val eveData: EveData,
    val nonBattleshipFlagships: Set<String> = emptySet(),
): TournamentRules.FittingRules {


    /**
     * Returns whether the given ship can be a flagship.
     */
    context(EveData)
    open fun canBeFlagship(shipType: ShipType): Boolean {
        return when (shipType.name) {
            in nonBattleshipFlagships -> true
            else -> shipType.isBattleship
        }
    }


    override fun isModuleLegal(moduleType: ModuleType, shipType: ShipType): Boolean {
        with(eveData) {
            if (moduleType.mutation != null)
                return false

            if (!moduleType.isTech1Item && !moduleType.isTech2Item)
                return canBeFlagship(shipType) && isModuleLegalOnFlagship(moduleType)

            return when {
                moduleType.isRemoteRepairer -> shipType.isLogistics
                moduleType.isEcm() || moduleType.isBurstJammer() -> isEcmLegal(moduleType, shipType)
                moduleType.isWeaponDisruptor() -> isWeaponDisruptorLegal(moduleType, shipType)
                moduleType.isRemoteSensorDampener() -> isRemoteSensorDampenerLegal(moduleType, shipType)
                moduleType.isMicroJumpFieldGenerator() -> false
                moduleType.isBastionModule() -> false
                moduleType.isCloakingDevice() -> false
                moduleType.isRig -> moduleType.isTech1Item
                else -> true
            }
        }
    }


    override fun fitModulesLegality(moduleTypes: List<ModuleType>, shipType: ShipType): List<String?> {
        with(eveData) {
            val regularIllegality = moduleTypes.map {
                if (isModuleLegal(it, shipType)) null else "Module is illegal here"
            }
            val illegalityByGroup = moduleIllegalityByGroup(
                moduleTypes = moduleTypes,
                maxInGroup = {
                    when (it) {
                        groups.remoteCapacitorTransmitter -> 1
                        groups.ancillaryShieldBooster -> 1
                        groups.warpDisruptFieldGenerator -> 1
                        else -> Unlimited
                    }
                }
            )
            val flagshipRepairerLegality = if (!canBeFlagship(shipType))
                List(moduleTypes.size) { null }
            else {
                standardFlagshipActiveFitModulesIllegality(moduleTypes)
            }

            return combineIllegalityReasons(regularIllegality, illegalityByGroup, flagshipRepairerLegality)
        }
    }


    override fun isChargeLegal(chargeType: ChargeType?, moduleType: ModuleType): Boolean {
        with(eveData) {
            return isChargeLegal(chargeType)
        }
    }


    override fun isDroneLegal(droneType: DroneType): Boolean {
        return with(eveData) {
            when {
                droneType.name == "Gecko" -> false
                droneType.isSentry -> droneType.isTech1Item || droneType.isEmpireNavyFactionItem
                droneType.isCombatDrone() -> droneType.isTech1Item || droneType.isEmpireNavyFactionItem
                droneType.isShieldMaintenanceBot() ||
                        droneType.isArmorMaintenanceBot() ||
                        droneType.isHullMaintenanceBot() -> droneType.isTech1Item
                droneType.isStasisWebifyingDrone() ||
                        droneType.isTargetPaintingDrone() ||
                        droneType.isEnergyNeutralizerDrone() ||
                        droneType.isEcmDrone() ||
                        droneType.isSensorDampeningDrone() ||
                        droneType.isTrackingDisruptingDrone() -> true
                else -> false
            }
        }
    }


    override fun isImplantLegal(implantType: ImplantType): Boolean {
        return with(eveData) {
            implantType.is123Hardwiring || implantType.isMindlinkImplant()
        }
    }


    override fun isCargoItemLegal(itemType: EveItemType): Boolean {
        with(eveData) {
            if (itemType.isCargoContainer)
                return false

            return when (itemType) {
                is ChargeType -> isChargeLegal(itemType)
                is ImplantType -> isImplantLegal(itemType)
                is BoosterType -> isBoosterLegal(itemType)
                else -> true
            }
        }
    }


    override fun preloadedCharge(
        fit: Fit,
        moduleType: ModuleType,
        charges: Collection<ChargeType>
    ): Pair<Boolean, ChargeType?> {
        with(eveData) {
            val chargeType = when {
                moduleType.isEnergyWeapon() -> forEnergyTurret(charges)
                moduleType.isHybridWeapon() -> forHybridTurret(fit.ship.type, charges)
                moduleType.isProjectileWeapon() -> forProjectileTurret(charges)
                moduleType.isMissileLauncher() -> forMissileLauncher(fit, moduleType, charges)
                moduleType.isWarpDisruptionFieldGenerator() -> charges.find { it.isFocusedWarpScramblingScript }
                moduleType.isInterdictionSphereLauncher() -> charges.find { it.isStasisWebificationProbe }
                else -> return Pair(false, null)
            }

            return Pair(true, chargeType)
        }
    }


    override fun hasPreloadedCharge(
        fit: Fit,
        moduleType: ModuleType,
        charges: Collection<ChargeType>,
    ): Pair<Boolean, Boolean> {
        val hasTournamentSpecificCharge = theorycrafter.utils.with(eveData, moduleType) {
            isEnergyWeapon() ||
                    isHybridWeapon() ||
                    isProjectileWeapon() ||
                    isMissileLauncher() ||
                    isWarpDisruptionFieldGenerator() ||
                    isInterdictionSphereLauncher()
        }

        return Pair(hasTournamentSpecificCharge, true)
    }


    override val packForBattleConfiguration: PackForBattleConfiguration
        get() = PackForBattleConfig


}


/**
 * Returns whether the given module is legal on a flagship.
 */
context(EveData)
private fun isModuleLegalOnFlagship(moduleType: ModuleType): Boolean {
    if (moduleType.mutation != null)
        return false

    return with(moduleType) {
        when {
            isTurretOrVortonProjector() || isMissileLauncher() -> true
            isStasisWebifier() || isStasisGrappler() -> true
            isSmartbomb() -> true
            isPropulsionModule() -> true
            isWarpScrambler() -> true
            isTargetPainter() -> true
            isSensorBooster() || isSignalAmplifier() -> true
            isNanofiberInternalStructure() || isOverdrive() || isInertialStabilizer() -> true
            isBallisticControlSystem() || isMissileGuidanceComputer() || isMissileGuidanceEnhancer() -> true
            isVortonTuningSystem() -> true
            isGyrostabilizer() || isMagneticFieldStabilizer() || isHeatSink() || isEntropicRadiationSink() -> true
            isTrackingComputer() || isTrackingEnhancer() -> true
            isDroneDamageAmplifier() || isDroneLinkAugmentor() || isDroneNavigationComputer() -> true
            isOmnidirectionalTrackingLink() || isOmnidirectionalTrackingEnhancer() -> true
            isArmorPlate() || isShieldExtender() -> true
            isDamageControl() -> true
            isShieldBooster(includingAncillary = true) || isArmorRepairer(includingAncillary = true) -> true
            isMicroJumpDrive() -> true
            else -> false
        }
    }
}


/**
 * Returns whether the given ECM module is legal on the given ship type.
 */
context(EveData)
private fun isEcmLegal(moduleType: ModuleType, shipType: ShipType): Boolean {
    return moduleType.isTech1Item && (shipType.name in EcmShips)
}


/**
 * Returns whether the given weapon disruption module is legal on the given ship type.
 */
context(EveData)
private fun isWeaponDisruptorLegal(moduleType: ModuleType, shipType: ShipType): Boolean {
    return moduleType.isTech1Item && (shipType.name in WeaponDisruptionShips)
}


/**
 * Returns whether the given sensor dampener module is legal on the given ship type.
 */
context(EveData)
private fun isRemoteSensorDampenerLegal(moduleType: ModuleType, shipType: ShipType): Boolean {
    return moduleType.isTech1Item && (shipType.name in SensorDampeningShips)
}


/**
 * Returns whether the charge is legal, whether in a module or in the cargohold.
 */
context(EveData)
private fun isChargeLegal(chargeType: ChargeType?): Boolean {
    return with(chargeType) {
        when {
            this == null -> true
            isWarpDisruptionScript() -> true
            isInterdictionSphereLauncherProbe() -> isStasisWebificationProbe
            isAmmo() -> when {
                isTech2Item -> true
                isBomb() -> true
                isDefenderMissile() -> true
                isAutoTargetingMissile() -> isEmpireNavyFactionItem
                isMissile() || isProjectileAmmo() || isFrequencyCrystalAmmo() ||isHybridChargeAmmo() -> isEmpireNavyFactionItem
                else -> true
            }
            else -> true
        }
    }
}


/**
 * Returns the crystal to preload into the given laser turret.
 */
private fun EveData.forEnergyTurret(crystals: Collection<ChargeType>): ChargeType?{
    // Advanced pulse crystal with the longest range should be Scorch
    val scorch = crystals
        .filter { it.group == groups.advancedPulseLaserCrystal }
        .maxByOrNull { it.attributeValueOrNull(attributes.weaponRangeMultiplier) ?: 0.0 }
    if (scorch != null)
        return scorch

    // Highest damage faction should be Multifrequency
    return crystals
        .filter { it.isEmpireNavyFactionItem }
        .consistentMaxByOrNull { it.totalDamage ?: 0.0 }
}


/**
 * Returns a string the preferred faction hybrid charge should contain, for the given ship type.
 */
context(EveData)
private fun preferredFactionHybridChargeForShip(shipType: ShipType): String {
    return if (shipType.targeting.sensors.type.race == races.caldari) "Caldari Navy" else "Federation Navy"
}


/**
 * Returns the hybrid charge to preload into the given hybrid turret.
 */
private fun EveData.forHybridTurret(shipType: ShipType, charges: Collection<ChargeType>): ChargeType? {
    return charges
        .filter { it.isEmpireNavyFactionItem }
        .consistentMaxByOrNull(preferNameContains = preferredFactionHybridChargeForShip(shipType)) {
            it.totalDamage ?: 0.0
        }
}


/**
 * Returns the ammo to preload into the given projectile turret.
 */
private fun EveData.forProjectileTurret(ammo: Collection<ChargeType>): ChargeType?{
    // Advanced autocannon ammo with the highest damage should be Hail
    val hail = ammo
        .filter { it.group == groups.advancedAutocannonAmmo }
        .maxByOrNull { it.totalDamage ?: 0.0 }
    if (hail != null)
        return hail

    // EMP, Phased Plasma and Fusion all have the same damage, but Phased Plasma is the most versatile one.
    return ammo
        .filter { it.isEmpireNavyFactionItem }
        .consistentMaxByOrNull(preferNameContains = "Phased Plasma") { it.totalDamage ?: 0.0 }
}


/**
 * Returns the missile to preload into the given missile launcher fitted to the given ship.
 */
private fun EveData.forMissileLauncher(
    fit: Fit,
    missileLauncher: ModuleType,
    missiles: Collection<ChargeType>
): ChargeType? {
    val caldariNavy = missiles.filter { it.isEmpireNavyFactionItem }
    return preferredMissile(fit, missileLauncher, caldariNavy)
}


/**
 * The configuration for the "Pack for Battle" dialog.
 */
private val PackForBattleConfig = object: PackForBattleConfiguration {


    context(EveData)
    override fun script(chargeType: ChargeType): Boolean {
        return isChargeLegal(chargeType)
    }


    context(EveData)
    override fun interdictionSphereLauncherProbe(chargeType: ChargeType): Boolean {
        return isChargeLegal(chargeType)
    }


    context(EveData)
    override fun ammo(shipType: ShipType, moduleType: ModuleType, chargeType: ChargeType): Boolean {
        return when {
            chargeType.isBomb() -> true
            chargeType.isDefenderMissile() -> true
            chargeType.isTech2Item -> true
            moduleType.isHybridWeapon() -> isEmpireFactionHybridChargeProperForShipType(shipType, chargeType)
            else -> chargeType.isEmpireNavyFactionItem
        }
    }


}

