package theorycrafter.tournaments

import eve.data.EveData
import eve.data.ShipType

/**
 * The interface for points-based composition rules.
 *
 * Each ship allowed in the tournament has a points "cost", and a composition has a maximum number of ships and a
 * maximum number of points it can use.
 */
interface PointsCompositionRules: TournamentRules.CompositionRules {


    /**
     * The maximum number of ships in a composition.
     */
    val maxCompositionSize: Int


    /**
     * The maximum allowed ship cost for a composition.
     */
    val maxCompositionCost: Int


    /**
     * The list of legal ship types.
     */
    val legalShips: Collection<ShipType>


    /**
     * Prepares a computation of marginal ship costs.
     */
    fun marginalShipCostComputation(usedShipTypes: List<ShipType?>): MarginalShipCostComputation


    /**
     * The interface for computing the marginal ship cost.
     */
    fun interface MarginalShipCostComputation {

        /**
         * Returns the marginal cost of the given ship type.
         *
         * As the set of allowed ships can change in the pre-tournament period, the function should not crash, but
         * return 0 for ships that are not allowed in the tournament.
         */
        fun marginalCostOf(shipType: ShipType): Int

    }


}

/**
 * A base implementation of [PointsCompositionRules].
 */
abstract class BasicPointsCompositionRules(
    protected val eveData: EveData,
    override val maxCompositionSize: Int,
    override val maxCompositionCost: Int,
    val shipCostByType: Map<ShipType, Int>,
): PointsCompositionRules {


    /**
     * Returns whether the ship has a point cost in [shipCostByType].
     */
    override fun isShipLegal(shipType: ShipType) = shipType in shipCostByType


    /**
     * Returns the set of ships that have a point cost.
     */
    override val legalShips: Collection<ShipType> = shipCostByType.keys


    /**
     * Returns the cost of each ship according to [shipCostByType].
     */
    override fun shipsCost(shipTypes: List<ShipType?>): List<Int> {
        return shipTypes.map {
            if (it == null)
                0
            else
                shipCostByType[it] ?: 0
        }
    }


    /**
     * A simple [PointsCompositionRules.MarginalShipCostComputation] that just returns the cost of the ship.
     */
    private val marginalShipCostComputation = regularMarginalShipCostComputation(shipCostByType)


    override fun marginalShipCostComputation(
        usedShipTypes: List<ShipType?>
    ): PointsCompositionRules.MarginalShipCostComputation {
        return marginalShipCostComputation
    }


    /**
     * Returns for each ship whether it is [isShipLegal].
     */
    override fun compositionShipsIllegalityReason(composition: Composition): List<String?> {
        return composition.ships.map {
            if ((it == null) || isShipLegal(it.shipType)) null else "Illegal ship type"
        }
    }


    override fun compositionIconShipTypes(composition: Composition): List<ShipType?> {
        // Return the non-logi ship type with the most summed points, and the logi

        val (logiShips, nonLogiShips) = composition.ships.filterNotNull().partition {
            with(eveData) { it.shipType.isLogistics }
        }
        val nonLogiShipAndCost = nonLogiShips zip shipsCost(nonLogiShips.map { it.shipType })
        val mainNonLogiShipType = nonLogiShipAndCost
            .groupingBy { it.first.shipType }
            .fold(0) { acc, (_, cost) -> acc + cost }
            .maxByOrNull { it.value }
            ?.key
        val logi = logiShips.firstOrNull()?.shipType
        return listOf(mainNonLogiShipType, logi)
    }


}


/**
 * Returns a [PointsCompositionRules.MarginalShipCostComputation] where the marginal cost is the regular cost.
 */
fun regularMarginalShipCostComputation(shipCostByType: Map<ShipType, Int>) =
    PointsCompositionRules.MarginalShipCostComputation { shipType -> shipCostByType[shipType] ?: 0 }


/**
 * Computes ship costs with the duplicate penalty rule.
 */
fun shipsCostWithDuplicatePenalty(
    shipTypes: List<ShipType?>,
    shipCostByType: Map<ShipType, Int>,
    penalty: (ShipType) -> Int = { 1 }
): List<Int> {
    val countByShipType = mutableMapOf<ShipType, Int>()
    return buildList {
        for (shipType in shipTypes) {
            if (shipType == null) {
                add(0)
                continue
            }

            val regularCost = shipCostByType[shipType] ?: 0
            val currentCount = countByShipType[shipType] ?: 0
            val penaltyAmount = penalty(shipType)
            add(regularCost + 2 * penaltyAmount * currentCount)
            countByShipType[shipType] = currentCount + 1
        }
    }
}


/**
 * Returns a [PointsCompositionRules.MarginalShipCostComputation] for tournaments with the duplicate penalty rule.
 */
fun marginalShipCostComputationWithDuplicatePenalty(
    usedShipTypes: List<ShipType?>,
    shipCostByType: Map<ShipType, Int>,
    penalty: (ShipType) -> Int = { 1 }
): PointsCompositionRules.MarginalShipCostComputation {
    val amountByType = usedShipTypes.groupingBy { it }.eachCount()
    return PointsCompositionRules.MarginalShipCostComputation { shipType ->
        val regularCost = shipCostByType[shipType] ?: return@MarginalShipCostComputation 0
        val currentShipTypeAmount = amountByType[shipType] ?: 0
        val penaltyAmount = penalty(shipType)
        regularCost + 2 * penaltyAmount * currentShipTypeAmount
    }
}
