package theorycrafter.ui.fiteditor

import eve.data.EveData
import theorycrafter.TheorycrafterContext
import theorycrafter.TheorycrafterTest
import theorycrafter.formats.fitFromEft
import theorycrafter.storage.StoredFit
import theorycrafter.fits.mutatedWith
import kotlin.test.*


/**
 * Tests parsing and creating EFT-format fits.
 */
class EftTest: TheorycrafterTest() {


    /**
     * Verifies that the given list of modules contains exactly the given modules.
     */
    private fun List<StoredFit.StoredModule?>.assertModulesEqual(vararg expectedModulesAndCharges: String?) {
        val eveData = TheorycrafterContext.eveData

        assertEquals(expectedModulesAndCharges.size, this.size, "Number of modules is incorrect")

        for ((expectedModuleAndCharge, actualModule) in (expectedModulesAndCharges zip this)) {
            if (expectedModuleAndCharge == null) {
                assertNull(actualModule)
                continue
            }
            assertNotNull(actualModule)

            val (expectedModuleName, expectedChargeName) = expectedModuleAndCharge
                .split(",")
                .map { it.trim() }
                .run {
                    get(0) to getOrNull(1)
                }
            val expectedModuleType = eveData.moduleType(expectedModuleName)
            val actualModuleType = eveData.moduleType(actualModule.itemId)
            assertEquals(expectedModuleType, actualModuleType)

            val chargeId = actualModule.chargeId
            if (expectedChargeName == null) {
                assertNull(chargeId)
                continue
            }
            assertNotNull(chargeId)
            val expectedChargeType = eveData.chargeType(expectedChargeName)
            val actualChargeType = eveData.chargeType(chargeId)
            assertEquals(expectedChargeType, actualChargeType)
        }
    }


    /**
     * Verifies that the given list of drones containes exactly the given drones.
     */
    private fun List<StoredFit.StoredDroneGroup>.assertDronesEqual(vararg expectedDrones: String) {
        val eveData = TheorycrafterContext.eveData

        assertEquals(size, expectedDrones.size, "Amount of drones is incorrect")

        for ((expectedDroneAndSize, actualDroneGroup) in (expectedDrones zip this)) {
            val (expectedDroneName, expectedSize) = expectedDroneAndSize
                .split(" x")
                .run {
                    get(0) to get(1).toInt()
                }

            val expectedDroneType = eveData.droneType(expectedDroneName)
            val actualDroneType = eveData.droneType(actualDroneGroup.itemId)
            assertEquals(expectedDroneType, actualDroneType)
            assertEquals(expectedSize, actualDroneGroup.size)

        }
    }


    /**
     * Verifies that the given list of cargo items containes exactly the given ones.
     */
    private fun List<StoredFit.StoredCargoItem>.assertCargoEqual(vararg expectedCargoItems: String) {
        val eveData = TheorycrafterContext.eveData

        assertEquals(size, expectedCargoItems.size, "Amount of cargo items is incorrect")

        for ((expectedItemAndAmount, actualItem) in (expectedCargoItems zip this)) {
            val (expectedItemName, expectedAmount) = expectedItemAndAmount
                .split(" x")
                .run {
                    get(0) to get(1).toInt()
                }

            val expectedDroneType = eveData.cargoItemType(expectedItemName)
            val actualDroneType = eveData.cargoItemType(actualItem.itemId)
            assertEquals(expectedDroneType, actualDroneType)
            assertEquals(expectedAmount, actualItem.amount)

        }
    }


    /**
     * Converts the attribute name (the strings in the list) to the id of the corresponding attribute.
     */
    context(EveData)
    private fun List<Pair<String, Double>>.attrNamesToIds() = map { (attrName, value) ->
        attributes[attrName]!!.id to value
    }


    @Test
    fun testFitWithBlankLines() {
        val text = """
            [Typhoon, Republic Typhoon]

            Damage Control II
            
            Ballistic Control System II
            
            Ballistic Control System II
            
            Ballistic Control System II
            
            Reactive Armor Hardener
            
            Multispectrum Energized Membrane II
            
            Large Armor Repairer II

            Large Micro Jump Drive
            
            Sensor Booster II, Targeting Range Script
            
            Missile Guidance Computer II, Missile Precision Script
            
            Republic Fleet Large Cap Battery
            
            Target Painter II

            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Cruise Missile Launcher II, Nova Fury Cruise Missile
            
            Large Proton Smartbomb II
            

            Large Kinetic Armor Reinforcer II
            
            Large Explosive Armor Reinforcer II
            
            Large Warhead Calefaction Catalyst I

            Warrior II x5
            
            Berserker II x4


            Bouncer II x4
        """.trimIndent()

        with(TheorycrafterContext.eveData) {
            val fit = fitFromEft(text)
            assertNotNull(fit)

            assertEquals(shipType("Typhoon"), shipType(fit.shipTypeId))

            fit.lowSlotRack.assertModulesEqual(
                "Damage Control II",
                "Ballistic Control System II",
                "Ballistic Control System II",
                "Ballistic Control System II",
                "Reactive Armor Hardener",
                "Multispectrum Energized Membrane II",
                "Large Armor Repairer II",
            )
            fit.medSlotRack.assertModulesEqual(
                "Large Micro Jump Drive",
                "Sensor Booster II, Targeting Range Script",
                "Missile Guidance Computer II, Missile Precision Script",
                "Republic Fleet Large Cap Battery",
                "Target Painter II",
            )
            fit.highSlotRack.assertModulesEqual(
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Cruise Missile Launcher II, Nova Fury Cruise Missile",
                "Large Proton Smartbomb II",
            )
            fit.rigs.assertModulesEqual(
                "Large Kinetic Armor Reinforcer II",
                "Large Explosive Armor Reinforcer II",
                "Large Warhead Calefaction Catalyst I",
            )
            fit.droneGroups.assertDronesEqual(
                "Warrior II x5",
                "Berserker II x4",
            )
            fit.cargoItems.assertCargoEqual(
                "Bouncer II x4"
            )
        }
    }

    @Test
    fun testStrategicCruiserWithUnknownModules() {
        val text = """
            [Proteus, Test]
            
            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]
            Reactor Control Unit II
            Not a module
            [Empty Low slot]
            
            [Empty Med slot]
            Not a module
            Large Shield Extender II
            
            [Empty High slot]
            [Empty High slot]
            Not a module
            Heavy Neutron Blaster I
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            
            [Empty Rig slot]
            [Empty Rig slot]
            [Empty Rig slot]
            
            Proteus Core - Augmented Fusion Reactor
            Proteus Defensive - Covert Reconfiguration
            Proteus Offensive - Hybrid Encoding Platform
            Proteus Propulsion - Hyperspatial Optimization
        """.trimIndent()

        with(TheorycrafterContext.eveData) {
            val fit = fitFromEft(text)
            assertNotNull(fit)

            assertEquals(shipType("Proteus"), shipType(fit.shipTypeId))

            fit.lowSlotRack.assertModulesEqual(
                null,
                null,
                null,
                "Reactor Control Unit II",
                null,
                null
            )

            fit.medSlotRack.assertModulesEqual(
                null,
                null,
                "Large Shield Extender II",
            )
            fit.highSlotRack.assertModulesEqual(
                null,
                null,
                null,
                "Heavy Neutron Blaster I",
                null,
                null,
                null,
                null
            )
            fit.rigs.assertModulesEqual(
                null,
                null,
                null
            )
            assertTrue(fit.droneGroups.isEmpty())
            assertTrue(fit.cargoItems.isEmpty())
        }
    }


    /**
     * Tests importing an Abyssal module and drone.
     */
    @Test
    fun testFitWithAbyssalModuleAndDrone() {
        val text = """
            [Caracal, Test]
            
            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]
            
            50MN Microwarpdrive II [1]
            [Empty Med slot]
            [Empty Med slot]
            [Empty Med slot]
            [Empty Med slot]
            
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            
            [Empty Rig slot]
            [Empty Rig slot]
            [Empty Rig slot]
            
            
            Acolyte II x2 [2]
            
            
            [1] 50MN Microwarpdrive II
              Unstable 50MN Microwarpdrive Mutaplasmid
              capacitorNeed 200, speedFactor 561, power 165, cpu 50, signatureRadiusBonus 500
            [2] Acolyte II
              Exigent Light Drone Firepower Mutaplasmid
              hp 204, maxVelocity 4620, maxRange 2760, damageMultiplier 2.016, falloff 2000, trackingSpeed 2.988, shieldCapacity 48, armorHP 228
        """.trimIndent()

        with(TheorycrafterContext.eveData) {
            val fit = fitFromEft(text)
            assertNotNull(fit)
            assertEquals(shipType("Caracal"), shipType(fit.shipTypeId))

            val module = fit.medSlotRack[0]
            assertNotNull(module)
            val moduleType = moduleType(module.itemId)
            assertEquals(moduleType("50MN Microwarpdrive II"), moduleType)

            val moduleMutation = module.mutation
            assertNotNull(moduleMutation)
            val moduleMutaplasmid = mutaplasmid(moduleMutation.mutaplasmidId)
            assertEquals(mutaplasmidOrNull("Unstable 50MN Microwarpdrive Mutaplasmid"), moduleMutaplasmid)
            assertEquals("Unstable 50MN Microwarpdrive II", moduleType.mutatedWith(moduleMutaplasmid).name)

            assertContentEquals(
                moduleMutation.attributeIdsAndValues,
                listOf(
                    "capacitorNeed" to 200.0,
                    "speedFactor" to 561.0,
                    "power" to 165.0,
                    "cpu" to 50.0,
                    "signatureRadiusBonus" to 500.0,
                ).attrNamesToIds()
            )

            val drone = fit.droneGroups.firstOrNull()
            assertNotNull(drone)
            val droneType = droneType(drone.itemId)
            assertEquals(droneType("Acolyte II"), droneType)

            val droneMutation = drone.mutation
            assertNotNull(droneMutation)
            val droneMutaplasmid = mutaplasmid(droneMutation.mutaplasmidId)
            assertEquals(mutaplasmidOrNull("Exigent Light Drone Firepower Mutaplasmid"), droneMutaplasmid)
            assertEquals("Exigent Firepower Acolyte II", droneType.mutatedWith(droneMutaplasmid).name)
            assertContentEquals(
                droneMutation.attributeIdsAndValues,
                listOf(
                    "hp" to 204.0,
                    "maxVelocity" to 4620.0,
                    "maxRange" to 2760.0,
                    "damageMultiplier" to 2.016,
                    "falloff" to 2000.0,
                    "trackingSpeed" to 2.988,
                    "shieldCapacity" to 48.0,
                    "armorHP" to 228.0
                ).attrNamesToIds()
            )

            fit.lowSlotRack.assertModulesEqual(*arrayOfNulls(4))
            fit.medSlotRack.subList(1, 5).assertModulesEqual(*arrayOfNulls(4))
            fit.highSlotRack.assertModulesEqual(*arrayOfNulls(5))
            fit.rigs.assertModulesEqual(*arrayOfNulls(3))
            assertTrue(fit.cargoItems.isEmpty())
        }
    }

    /**
     * Tests a strategic cruiser with drones.
     */
    @Test
    fun testStrategicCruiserWithDrones() {
        val text = """
            [Loki, LogiLoki cheap]

            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]
            [Empty Low slot]

            [Empty Med slot]
            [Empty Med slot]
            [Empty Med slot]
            [Empty Med slot]
            [Empty Med slot]

            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]
            [Empty High slot]

            [Empty Rig slot]
            [Empty Rig slot]
            [Empty Rig slot]

            [Empty Subsystem slot]
            Loki Defensive - Adaptive Defense Node
            Loki Offensive - Support Processor
            Loki Propulsion - Wake Limiter


            Warrior II x5
        """.trimIndent()

        with(TheorycrafterContext.eveData) {
            val fit = fitFromEft(text)
            assertNotNull(fit)
            assertEquals(shipType("Loki"), shipType(fit.shipTypeId))

            fit.droneGroups.assertDronesEqual("Warrior II x5")
        }
    }

}