package com.dowstats.service.w40k

import com.dowstats.Metadata
import com.dowstats.data.dto.BuildingDataToSave
import com.dowstats.data.entities.*
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.fillDataFromRgdTable
import com.dowstats.data.entities.ModifiersBase.ModifiersBaseCompanion.filterUseless
import com.dowstats.data.entities.ability.Ability
import com.dowstats.data.entities.building.Building
import com.dowstats.data.entities.building.BuildingModifiers
import com.dowstats.data.entities.building.BuildingRequirements
import com.dowstats.data.entities.research.Research
import com.dowstats.data.entities.unit.DowUnit
import com.dowstats.data.entities.weapon.Weapon
import com.dowstats.data.rgd.RgdData
import com.dowstats.data.rgd.RgdDataUtil.getBooleanByName
import com.dowstats.data.rgd.RgdDataUtil.getDoubleByName
import com.dowstats.data.rgd.RgdDataUtil.getIntByName
import com.dowstats.data.rgd.RgdDataUtil.getRgdTableByName
import com.dowstats.data.rgd.RgdDataUtil.getStringByName
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class BuildingRgdExtractService @Autowired constructor(
    private val addonRgdExtractService: BuildingAddonRgdExtractService,
    private val commonParseRgdService: CommonParseRgdService,
    private val deathExplosionRgdExtractService: DeathExplosionRgdExtractService,
) {

    val log = LoggerFactory.getLogger(BuildingRgdExtractService::class.java)

    data class HealthData(
        val hitpoints: Double?,
        val regeneration: Double?,
        val armour: Double?,
        val maxRepaires: Int?,
    )

    data class WeaponsData(
        val hardpoint: Int,
        val hardpointOrder: Int,
        val weaponFilename: String,
    )

    fun extractToBuildingEntity(
        fileName: String,
        modDictionary: Map<Int, String>,
        buildingData: List<RgdData>,
        weapons: Set<Weapon>,
        abilities: Set<Ability>,
        researches: Set<Research>,
        units: List<DowUnit>,
        race: Race,
        modFolderData: String,
        mod: Mod,
        armorTypes: Set<ArmorType>,
        addonsRgdData: Map<String, List<RgdData>>,
    ): BuildingDataToSave {

        val building = Building()

        building.race = race
        building.armorType = commonParseRgdService.getArmorType(buildingData, armorTypes, "type_armour") ?: throw Exception("Cant get armor type")
        building.armorType2 = commonParseRgdService.getArmorType(buildingData, armorTypes, "type_armour_2")
        building.filename = fileName

        val uiExt = buildingData.getRgdTableByName("ui_ext") ?: throw Exception("Could not find ui_ext at $fileName")
        val buildingUiInfo = uiExt.getRgdTableByName("ui_info") ?: throw Exception("Could not find ui_info at $fileName")
        val buildingUiData = commonParseRgdService.getUiInfo(buildingUiInfo, modDictionary, modFolderData, mod, log)
        building.name = buildingUiData.name
        building.description = buildingUiData.description
        building.icon = buildingUiData.iconPath
        building.uiIndexHint = uiExt.getIntByName("ui_index_hint") ?: 0
        building.uiHotkeyName = uiExt.getStringByName("ui_hotkey_name")
        building.advancedBuildOption = buildingData.getRgdTableByName("structure_buildable_ext")?.getBooleanByName("advanced_build_option") ?: false

        val buildCost = commonParseRgdService.getBuildCost(buildingData.getRgdTableByName("cost_ext")?.getRgdTableByName("time_cost"))
        building.buildCostRequisition = buildCost.requisition
        building.buildCostPower = buildCost.power
        building.buildCostPopulation = buildCost.population
        building.buildCostFaith = buildCost.faith
        building.buildCostSouls = buildCost.souls
        building.buildCostTime = buildCost.time

        val incomeData = commonParseRgdService.getResourceIncome(buildingData.getRgdTableByName("resource_ext"))
        building.faithIncome = incomeData.faith
        building.powerIncome = incomeData.power
        building.requisitionIncome = incomeData.requisition


        val healthData = getHealthData(buildingData)
        building.health = healthData.hitpoints?.toInt()
        building.armour = healthData.armour
        building.healthRegeneration = healthData.regeneration

        building.sightRadius = getSightRadius(buildingData)?.toInt()
        building.detectRadius = getDetectRadius(buildingData)?.toInt()

        building.repairMax = healthData.maxRepaires

        building.buildingLimit = getLimit(buildingData)

        building.units = getUnits(buildingData, units).toMutableSet()
        building.researches = getResearches(buildingData, researches).toMutableSet()

        building.modifiers = getBuildingModifiers(building, buildingData).toMutableSet()

        building.buildingRequirements = getBuildingRequirements(building, buildingData).toMutableSet()

        building.abilities = commonParseRgdService.getAbilities(buildingData, abilities).toMutableSet()

        building.deathExplosions.addAll(deathExplosionRgdExtractService.getDeathExplosions(buildingData, armorTypes).toMutableSet())

        val addons = getAddons(buildingData)
        building.addons = addons?.mapNotNull {addonFileName ->
            val addonRgdData = addonsRgdData[addonFileName]
            if(addonRgdData != null){
                addonRgdExtractService.extractToAddonEntity(addonFileName, modDictionary, addonRgdData, modFolderData, building, mod)
            } else {
                log.warn("Can't find addon $addonFileName")
                null
            }
        }?.toMutableSet()


        val buildingWeapons = getBuildingWeapon(buildingData)?.mapNotNull { weaponData ->
            weapons.find {
                it.filename == weaponData.weaponFilename + ".rgd"
            }?.also {wp ->
                building.weaponHardpoints.add(Weapon.HardpointPosition( wp.id!!, weaponData.hardpoint, weaponData.hardpointOrder))
            }
        }.orEmpty().toSet()

        building.modId = mod.id

        return BuildingDataToSave(building, buildingWeapons)
    }

    private fun getLimit(buildingData: List<RgdData>): Int? {
        val requirements = buildingData.getRgdTableByName("requirement_ext")
            ?.getRgdTableByName("requirements")

        return requirements?.map {
            if (it.type == 100) {
                val reqirementData = it.value as List<RgdData>
                reqirementData.find {it.name == "max_cap"}?.value as Double?
            } else null
        }?.filterNotNull()?.firstOrNull()?.toInt()
    }

    private fun getHealthData(buildingData: List<RgdData>): HealthData {
        val healthExt = buildingData.getRgdTableByName("health_ext")
        return HealthData(
            healthExt?.getDoubleByName("hitpoints"),
            healthExt?.getDoubleByName("regeneration_rate"),
            healthExt?.getDoubleByName("armour")?.minus(100),
            healthExt?.getIntByName("max_repairers")
        )
    }


    private fun getSightRadius(unitData: List<RgdData>): Double? = unitData
        .getRgdTableByName("sight_ext")
        ?.getDoubleByName("sight_radius")

    private fun getDetectRadius(unitData: List<RgdData>): Double? = unitData
        .getRgdTableByName("sight_ext")
        ?.getDoubleByName("keen_sight_radius")


    private fun getBuildingWeapon(buildingData: List<RgdData>?): List<WeaponsData>? = buildingData
        ?.getRgdTableByName("combat_ext")
        ?.getRgdTableByName("hardpoints")
        ?.mapNotNull { hardpoint ->
            if (hardpoint.name.contains("hardpoint_")) {
                val hardpointValue = hardpoint.name.replace("hardpoint_", "").toInt()
                val hardpointTable = hardpoint.value as List<RgdData>
                hardpointTable.getRgdTableByName("weapon_table")?.let {
                    it.mapNotNull { weapon ->
                        (weapon.value as? List<RgdData>)?.getStringByName("weapon")?.let {
                            if (it != "") {
                                WeaponsData(hardpointValue,
                                weapon.name.replace("weapon_", "").toInt(),
                                it.replace("weapon\\", "").replace(".lua", "").replace(".rgd", ""))
                            } else null
                        }
                    }
                }
            } else null
        }?.flatten()


    private fun getAddons(buildingData: List<RgdData>): List<String>? = buildingData
        .getRgdTableByName("addon_ext")
        ?.getRgdTableByName("addons")
        ?.mapNotNull { addon ->
            if (addon.value != "") {
                addon.value.toString()
                    .replace("addons\\", "")
                    .replace(".lua", "")
                    .plus(".rgd")
            } else null
        }

    private fun getUnits(buildingData: List<RgdData>, units: List<DowUnit>): List<DowUnit> {
        return buildingData.getRgdTableByName("spawner_ext")
            ?.getRgdTableByName("squad_table")
            ?.mapNotNull { unit ->
                if (unit.name.contains("squad_")) {
                    val fileName = unit.value.toString().split("\\").last().replace(".lua", ".rgd")
                    units.find { it.filenameSquad == fileName }
                } else null
            } ?: emptyList()
    }

    private fun getResearches(buildingData: List<RgdData>, researches: Set<Research>): List<Research> {
        return buildingData.getRgdTableByName("research_ext")
            ?.getRgdTableByName("research_table")
            ?.mapNotNull { research ->
                if (research.name.contains("research_")) {
                    val fileName = research.value.toString().split("\\").last().replace(".lua", "")
                    researches.find { it.filename?.replace(".rgd", "") == fileName }
                } else null
            } ?: emptyList()
    }

    private fun getBuildingModifiers(building: Building, buildingData: List<RgdData>): List<BuildingModifiers> {
        val modifiersData = buildingData.getRgdTableByName("modifier_apply_ext")
        val allModifiers = modifiersData?.getRgdTableByName("modifiers").orEmpty() +
                modifiersData?.getRgdTableByName("modifiers_idle").orEmpty() +
                modifiersData?.getRgdTableByName("modifiers_immediate").orEmpty()
        return allModifiers.mapNotNull { m ->
            if (m.name.contains("modifier_")) {
                val mTable = m.value as List<RgdData>
                if (mTable.getStringByName("\$REF") == "modifiers\\no_modifier.lua" || mTable.getStringByName("\$REF") == "modifiers\\enable_infiltration.lua" ) null else {
                    BuildingModifiers().also {
                        it.building = building
                    }.fillDataFromRgdTable(mTable)
                }
            } else null
        }.filterUseless()
    }

    private fun getBuildingRequirements(building: Building, buildingData: List<RgdData>): List<BuildingRequirements> {
        val requirements = buildingData.getRgdTableByName("requirement_ext")?.getRgdTableByName("requirements")
        return requirements?.mapNotNull { r ->
            if (r.name.contains("required_")) {
                val rTable = r.value as List<RgdData>
                if (rTable.getStringByName("\$REF")?.contains(Metadata.Requirements.REFERENCE_REQUIREMENT_NONE) == true) null else {
                    BuildingRequirements().also {
                        it.building = building
                        it.reference = rTable.getStringByName("\$REF")
                        it.value = commonParseRgdService.getRequirementReference(it.reference, rTable, building.filename!!, log)
                    }
                }
            } else null
        } ?: emptyList()
    }
}
