package com.unciv.logic.city

import com.unciv.logic.map.tile.Tile
import com.unciv.models.stats.Stat
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.GameContext
import com.unciv.models.ruleset.unique.UniqueType
import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly
import com.unciv.models.ruleset.unique.UniqueParameterType

object CityResources {

    /** Returns ALL resources, city-wide and civ-wide */
    @Readonly
    fun getResourcesGeneratedByCity(city: City, resourceModifiers: Map<String, Float>): ResourceSupplyList {
        @LocalState val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifiers)
        cityResources += getCityResourcesGeneratedFromUniques(city, resourceModifiers, false)
        return cityResources
    }


    /** Only for *city-wide* resources - civ-wide resources should use civ-level resources */
    @Readonly
    fun getCityResourcesAvailableToCity(city: City): ResourceSupplyList {
        val resourceModifers = HashMap<String, Float>()
        for (resource in city.civ.gameInfo.ruleset.tileResources.values)
            resourceModifers[resource.name] = city.civ.getResourceModifier(resource)

        @LocalState val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifers)
        // We can't use getResourcesGeneratedByCity directly, because that would include the resources generated by buildings -
        //   which are part of the civ-wide uniques, so we'd be getting them twice!
        // This way we get them once, but it is ugly, I welcome other ideas :/
        cityResources.add(getCityResourcesGeneratedFromUniques(city, resourceModifers, true))

        cityResources.removeAll { !it.resource.isCityWide }

        return cityResources
    }


    @Readonly
    private fun getResourcesGeneratedByCityNotIncludingBuildings(city: City, resourceModifers: Map<String, Float>): ResourceSupplyList {
        val cityResources = ResourceSupplyList()
        
        cityResources.add(getResourcesFromTiles(city, resourceModifers))
        cityResources.add(getResourceFromUniqueImprovedTiles(city, resourceModifers))
        cityResources.add(getNegativeCityResourcesRequiredByBuildings(city))

        if (city.civ.isCityState && city.isCapital() && city.civ.cityStateResource != null) {
            cityResources.add(
                city.getRuleset().tileResources[city.civ.cityStateResource]!!,
                "Mercantile City-State"
            )
        }

        return cityResources
    }

    /** Gets the number of resources available to this city
     * Accommodates both city-wide and civ-wide resources */
    @Readonly
    fun getAvailableResourceAmount(city: City, resourceName: String): Int {
        val resource = city.getRuleset().tileResources[resourceName] ?: return 0

        if (!resource.isCityWide) return city.civ.getResourceAmount(resourceName)
        if (resource.isStockpiled) return city.resourceStockpiles[resourceName]
        return getCityResourcesAvailableToCity(city).asSequence().filter { it.resource == resource }.sumOf { it.amount }
    }

    @Readonly
    private fun getResourcesFromTiles(city: City, resourceModifer: Map<String, Float>): ResourceSupplyList {
        val resourceSupplyList = ResourceSupplyList()
        for (tileInfo in city.getTiles().filter { it.resource != null }) {
            val resource = tileInfo.tileResource
            val amount = getTileResourceAmount(city, tileInfo) * resourceModifer[resource.name]!!
            if (amount > 0) resourceSupplyList.add(resource, "Tiles", amount.toInt())
        }
        return resourceSupplyList
    }

    @Readonly
    private fun getResourceFromUniqueImprovedTiles(city: City, resourceModifer: Map<String, Float>): ResourceSupplyList {
        val resourceSupplyList = ResourceSupplyList()
        for (tileInfo in city.getTiles().filter { it.getUnpillagedImprovement() != null }) {
            val gameContext = GameContext(city.civ, city, tile = tileInfo)
            val tileImprovement = tileInfo.getUnpillagedTileImprovement()
            for (unique in tileImprovement!!.getMatchingUniques(UniqueType.ProvidesResources, gameContext)) {
                val resource = city.getRuleset().tileResources[unique.params[1]] ?: continue
                resourceSupplyList.add(
                    resource, "Improvements",
                    (unique.params[0].toFloat() * resourceModifer[resource.name]!!).toInt()
                )
            }
            for (unique in tileImprovement.getMatchingUniques(UniqueType.ConsumesResources, gameContext)) {
                val resource = city.getRuleset().tileResources[unique.params[1]] ?: continue
                resourceSupplyList.add(
                    resource, "Improvements",
                    -1 * unique.params[0].toInt()
                )
            }
        }
        return resourceSupplyList
    }

    @Readonly
    private fun getNegativeCityResourcesRequiredByBuildings(city: City): ResourceSupplyList {
        val resourceSupplyList = ResourceSupplyList()
        val freeBuildings = city.civ.civConstructions.getFreeBuildingNames(city)
        for (building in city.cityConstructions.getBuiltBuildings()) {
            // Free buildings cost no resources
            if (building.name in freeBuildings) continue
            resourceSupplyList.subtractResourceRequirements(building.getResourceRequirementsPerTurn(city.state), city.getRuleset(), "Buildings")
        }
        return resourceSupplyList
    }

    @Readonly
    private fun getCityResourcesGeneratedFromUniques(city: City, resourceModifers: Map<String, Float>, includeCivUniques: Boolean = true): ResourceSupplyList {
        val buildingResources = ResourceSupplyList()
        for (unique in city.getMatchingUniques(UniqueType.ProvidesResources, city.state, includeCivUniques)) { // E.G "Provides [1] [Iron]"
            val resource = city.getRuleset().tileResources[unique.params[1]]
                ?: continue
            buildingResources.add(
                resource, unique.getSourceNameForUser(),
                (unique.params[0].toFloat() * resourceModifers[resource.name]!!).toInt()
            )
        }

        // StatPercentFromObjectToResource - Example: "[50]% of [Culture] from every [improvementFilter/buildingFilter] in the city added to [Iron]"
        for (unique in city.getMatchingUniques(UniqueType.StatPercentFromObjectToResource, city.state, includeCivUniques)) {
            val resource = city.getRuleset().tileResources[unique.params[3]] ?: continue
            val stat = Stat.safeValueOf(unique.params[1]) ?: continue
            val filter = unique.params[2]
            var amount = 0.0

            // Building Filter
            if (UniqueParameterType.BuildingFilter.isKnownValue(filter, city.getRuleset())) {
                amount += city.cityConstructions.getBuiltBuildings()
                    .filter { it.isStatRelated(stat) && it.matchesFilter(filter, city.state) }
                    .sumOf { it.getStats(city)[stat].toDouble() }
            }

            // Improvement Filter
            if (UniqueParameterType.ImprovementFilter.isKnownValue(filter, city.getRuleset())) {
                amount += city.getWorkedTiles()
                    .mapNotNull { it.getUnpillagedTileImprovement() }
                    .filter { it[stat] > 0f && it.matchesFilter(filter, city.state) }
                    .sumOf { it[stat].toDouble() }
            }

            if (amount > 0.0) {
                amount *= unique.params[0].toDouble() / 100.0 * (resourceModifers[resource.name] ?: 1f).toDouble()
                buildingResources.add(resource, unique.getSourceNameForUser(), amount.toInt())
            }
        }

        return buildingResources
    }

    @Readonly
    private fun getTileResourceAmount(city: City, tile: Tile): Int {
        if (tile.resource == null) return 0
        if (!tile.providesResources(city.civ)) return 0

        val resource = tile.tileResource
        var amountToAdd = if (resource.resourceType == ResourceType.Strategic) tile.resourceAmount
        else 1
        if (resource.resourceType == ResourceType.Luxury
            && city.containsBuildingUnique(UniqueType.ProvidesExtraLuxuryFromCityResources))
            amountToAdd += 1

        return amountToAdd
    }
}
