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"
        }
    }


}


/**
 * 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>): 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
            add(regularCost + 2 * 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>
): PointsCompositionRules.MarginalShipCostComputation {
    val amountByType = usedShipTypes.groupingBy { it }.eachCount()
    return PointsCompositionRules.MarginalShipCostComputation { shipType ->
        val regularCost = shipCostByType[shipType] ?: return@MarginalShipCostComputation 0
        val currentShipTypeAmount = amountByType[shipType] ?: 0
        regularCost + 2 * currentShipTypeAmount
    }
}
