package theorycrafter.fitting

import eve.data.*
import eve.data.utils.mapValues
import eve.data.utils.valueByEnum
import kotlin.test.Test


/**
 * Tests fitting functionality related to the Reactive Armor Hardener module.
 */
class ReactiveArmorHardenerTest {


    /**
     * Fits a Reactive Armor Hardener to a ship and runs [test].
     */
    private fun runRahTest(
        baseRahResonance: Double = 0.85,
        resistanceShiftAmount: Double = 6.0,
        baseResonances: ResonancePattern,
        test: suspend FittingEngineTest.(fit: Fit, rah: Module) -> Unit
    ) = runFittingTest {
        val rahType = reactiveArmorHardenerType(
            baseResonance = baseRahResonance,
            resistanceShiftAmount = resistanceShiftAmount
        )

        val armorResonanceAttrs = attributes.armorResonance
        val shipType = testShipType {
            DamageType.entries.forEach { damageType ->
                attributeValue(armorResonanceAttrs[damageType], baseResonances[damageType])
            }
        }

        val (fit, rah) = fit(shipType, rahType)
        test(fit, rah)
    }


    /**
     * A utility function to test the effect of a Reactive Armor Hardener with the given ship base armor resonances and
     * the expected resulting resonances.
     */
    private fun testRah(
        adaptationCycles: AdaptationCycles = AdaptationCycles.Maximum,
        baseRahResonance: Double = 0.85,
        resistanceShiftAmount: Double = 6.0,
        baseResonances: ResonancePattern,
        expectedResonances: ResonancePattern,
        toleratedDifference: Double,
    ) = runRahTest(
        baseRahResonance = baseRahResonance,
        resistanceShiftAmount = resistanceShiftAmount,
        baseResonances = baseResonances
    ) { fit, rah ->
        // Test that there's no effect when it's not active
        modify {
            rah.setState(Module.State.ONLINE)
            rah.setAdaptationCycles(adaptationCycles.cycleCount)
        }

        assertArmorResistancesEqual(
            ship = fit.ship,
            expectedResonances = baseResonances,
            toleratedDifference = 0.0,
            message = { "Reactive Armor Hardener provides effect when inactive" }
        )

        // Test the effect when it is active
        modify {
            rah.setState(Module.State.ACTIVE)
        }
        assertArmorResistancesEqual(
            ship = fit.ship,
            expectedResonances = expectedResonances,
            toleratedDifference = toleratedDifference,
            message = { "Reactive Armor Hardener provides wrong effect on $it armor resonance" }
        )
    }


    /**
     * Asserts that the resonances of the given ship match [expectedResonances], within a tolerance of
     * [toleratedDifference].
     */
    private fun FittingEngineTest.assertArmorResistancesEqual(
        ship: Ship,
        expectedResonances: ResonancePattern,
        toleratedDifference: Double,
        message: (DamageType) -> String
    ) {
        val armorResonanceAttrs = attributes.armorResonance
        DamageType.entries.forEach { damageType ->
            ship.assertPropertyEquals(
                attribute = armorResonanceAttrs[damageType],
                expected = expectedResonances[damageType],
                absoluteTolerance = toleratedDifference,
                message = message(damageType)
            )
        }
    }



    /**
     * Test the effect of un-adapted Reactive Armor Hardener.
     */
    @Test
    fun testRahWithoutAdaptation() {
        val baseRahResonance = 0.85
        val baseResonances = valueByEnum<DamageType, Double> { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.1
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        }
        val expectedResonances = baseResonances.mapValues { it * baseRahResonance }
        testRah(
            baseRahResonance = baseRahResonance,
            adaptationCycles = AdaptationCycles.Number(0),
            baseResonances = baseResonances,
            expectedResonances = expectedResonances,
            toleratedDifference = 0.01
        )
    }


    /**
     * Test the effect of the first few cycles of adaptation of a Reactive Armor Hardener when there are two lower
     * resistances.
     */
    @Test
    fun testRahFirstAdaptationsWithTwoLowResistances() {
        val baseResonances = valueByEnum<DamageType, Double> { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.8
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        }
        runRahTest(
            baseResonances = baseResonances
        ) { fit, rah ->
            modify {
                rah.setState(Module.State.ACTIVE)
            }

            suspend fun testWithCycles(cycles: Int, expectedResonances: ResonancePattern) {
                modify {
                    rah.setAdaptationCycles(cycles)
                }
                assertArmorResistancesEqual(
                    ship = fit.ship,
                    expectedResonances = expectedResonances,
                    toleratedDifference = 0.01,
                    message = { "Reactive Armor Hardener provides wrong effect on $it armor resonance after $cycles cycle of adaptation" }
                )
            }

            testWithCycles(
                cycles = 0,
                expectedResonances = valueByEnum { damageType ->
                    when (damageType) {
                        DamageType.EM -> 0.8 * (1 - 0.15)
                        DamageType.THERMAL -> 0.8 * (1 - 0.15)
                        DamageType.KINETIC -> 0.1 * (1 - 0.15)
                        DamageType.EXPLOSIVE -> 0.1 * (1 - 0.15)
                    }
                }
            )
            testWithCycles(
                cycles = 1,
                expectedResonances = valueByEnum { damageType ->
                    when (damageType) {
                        DamageType.EM -> 0.8 * (1 - 0.18)
                        DamageType.THERMAL -> 0.8 * (1 - 0.18)
                        DamageType.KINETIC -> 0.1 * (1 - 0.12)
                        DamageType.EXPLOSIVE -> 0.1 * (1 - 0.12)
                    }
                }
            )
            testWithCycles(
                cycles = 2,
                expectedResonances = valueByEnum { damageType ->
                    when (damageType) {
                        DamageType.EM -> 0.8 * (1 - 0.21)
                        DamageType.THERMAL -> 0.8 * (1 - 0.21)
                        DamageType.KINETIC -> 0.1 * (1 - 0.09)
                        DamageType.EXPLOSIVE -> 0.1 * (1 - 0.09)
                    }
                }
            )
            testWithCycles(
                cycles = 3,
                expectedResonances = valueByEnum { damageType ->
                    when (damageType) {
                        DamageType.EM -> 0.8 * (1 - 0.24)
                        DamageType.THERMAL -> 0.8 * (1 - 0.24)
                        DamageType.KINETIC -> 0.1 * (1 - 0.06)
                        DamageType.EXPLOSIVE -> 0.1 * (1 - 0.06)
                    }
                }
            )
        }
    }


    /**
     * Test the effect of the Reactive Armor Hardener when there is one resists that take so much more damage that the
     * RAH is forced to give it all of its resistance.
     */
    @Test
    fun testRahWithOneLowResist() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.1
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8 * (1 - 0.6)
                DamageType.THERMAL -> 0.1
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        toleratedDifference = 0.01
    )


    /**
     * Test the effect of the Reactive Armor Hardener when there are 2 resists that take so much more damage that the
     * RAH is forced to split its resistance between them evenly.
     */
    @Test
    fun testRahWithTwoLowResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.8
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8 * (1 - 0.3)
                DamageType.THERMAL -> 0.8 * (1 - 0.3)
                DamageType.KINETIC -> 0.1
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        toleratedDifference = 0.01
    )


    /**
     * Test the effect of the Reactive Armor Hardener when there are 3 resists that take so much more damage that the
     * RAH is forced to split its resistance between them approximately evenly.
     */
    @Test
    fun testRahWithThreeLowResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.8
                DamageType.KINETIC -> 0.8
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8 * (1 - 0.2)
                DamageType.THERMAL -> 0.8 * (1 - 0.2)
                DamageType.KINETIC -> 0.8 * (1 - 0.2)
                DamageType.EXPLOSIVE -> 0.1
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test the effect of the Reactive Armor Hardener when all 4 resists are equal.
     */
    @Test
    fun testRahWithFourEqualResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8
                DamageType.THERMAL -> 0.8
                DamageType.KINETIC -> 0.8
                DamageType.EXPLOSIVE -> 0.8
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.8 * (1 - 0.15)
                DamageType.THERMAL -> 0.8 * (1 - 0.15)
                DamageType.KINETIC -> 0.8 * (1 - 0.15)
                DamageType.EXPLOSIVE -> 0.8 * (1 - 0.15)
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test the Reactive Armor Hardener with the base tech 1 resistance profile.
     */
    @Test
    fun testRahWithTech1Resists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.4
                DamageType.THERMAL -> 0.65
                DamageType.KINETIC -> 0.75
                DamageType.EXPLOSIVE -> 0.9
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.4
                DamageType.THERMAL -> 0.6
                DamageType.KINETIC -> 0.6
                DamageType.EXPLOSIVE -> 0.6
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test the Reactive Armor Hardener with the base tech 2 Amarr resistance profile.
     */
    @Test
    fun testRahWithTech2AmarrResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.5
                DamageType.THERMAL -> 0.65
                DamageType.KINETIC -> 0.375
                DamageType.EXPLOSIVE -> 0.2
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.4
                DamageType.THERMAL -> 0.4
                DamageType.KINETIC -> 0.375
                DamageType.EXPLOSIVE -> 0.2
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test the Reactive Armor Hardener with the base tech 2 Gallente resistance profile.
     */
    @Test
    fun testRahWithTech2GallenteResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.5
                DamageType.THERMAL -> 0.325
                DamageType.KINETIC -> 0.1625
                DamageType.EXPLOSIVE -> 0.9
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.35
                DamageType.THERMAL -> 0.325
                DamageType.KINETIC -> 0.1625
                DamageType.EXPLOSIVE -> 0.63
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test the Reactive Armor Hardener with the base tech 2 Triglavian resistance profile.
     */
    @Test
    fun testRahWithTech2TriglavianResists() = testRah(
        baseResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.5
                DamageType.THERMAL -> 0.25
                DamageType.KINETIC -> 0.75
                DamageType.EXPLOSIVE -> 0.35
            }
        },
        expectedResonances = valueByEnum { damageType ->
            when (damageType) {
                DamageType.EM -> 0.35
                DamageType.THERMAL -> 0.25
                DamageType.KINETIC -> 0.525
                DamageType.EXPLOSIVE -> 0.35
            }
        },
        toleratedDifference = 0.02  // The split isn't going to be very exact due to perturbations
    )


    /**
     * Test that the Reactive Armor Hardener correctly adjusts its resistance bonuses when ship resistances change due
     * to other modules being fitted after the RAH.
     */
    @Test
    fun testRahAfterChangingResists() = runFittingTest {
        val rahType = reactiveArmorHardenerType(
            baseResonance = 0.85,
            resistanceShiftAmount = 6.0
        )
        val resistModule = moduleType(slotType = ModuleSlotType.LOW, flags = ModuleFlags.PASSIVE) {
            val emResistanceBonusAttribute = attributes.armorResistanceBonus[DamageType.EM]
            attributeValue(emResistanceBonusAttribute, -90.0)
            effectReference(
                effectOnShip(
                    category = Effect.Category.ALWAYS,
                    modifyingAttribute = emResistanceBonusAttribute,
                    modifiedAttribute = attributes.armorResonance[DamageType.EM],
                    operation = AttributeModifier.Operation.ADD_PERCENT
                )
            )
        }

        val armorResonanceAttrs = attributes.armorResonance
        val shipType = testShipType {
            DamageType.entries.forEach { damageType ->
                attributeValue(
                    attribute = armorResonanceAttrs[damageType],
                    value = when (damageType) {
                        DamageType.EM -> 1.0
                        DamageType.EXPLOSIVE -> 0.9
                        else -> 0.1
                    }
                )
            }
        }

        val (fit, rah) = fit(shipType, rahType)
        modify {
            rah.setState(Module.State.ACTIVE)
        }
        modify {
            fit.fitModule(resistModule, 1)
        }

        fit.ship.assertPropertyEquals(
            attribute = armorResonanceAttrs[DamageType.EXPLOSIVE],
            expected = 0.9 * (1 - 0.6),
            absoluteTolerance = 0.01,
            message = "Reactive Armor Hardener provides wrong after resist module is fitted"
        )
    }


}