package theorycrafter.fitting

import eve.data.*
import kotlin.test.Test

/**
 * Tests memory leaks in the fitting engine.
 */
class MemoryLeakTests {


    /**
     * Verifies there is no memory leak when there are two modules with one affecting the other.
     */
    @Test
    fun testModuleWithEffectOnModule() = runFittingTest {
        val attribute = attribute()
        val affectedGroupId = newTestGroup()

        val affectedModuleType = moduleType(group = affectedGroupId) {
            attributeValue(attribute, 2.0)
        }

        val affectingModuleType = moduleType(
            flags = ModuleFlags.PASSIVE
        ) {
            attributeValue(attribute, 1.0)

            effectReference(
                effectOnModules(
                    category = Effect.Category.ONLINE,
                    modifiedAttribute = attribute,
                    modifyingAttribute = attribute,
                    operation = AttributeModifier.Operation.ADD,
                    groupId = affectedGroupId.id
                )
            )
        }

        val shipType = testShipType()

        // 1. Fit affected module
        // 2. Fit affecting module
        // 3. Remove affected module
        // 4. Remove affecting module
        run {
            val (fit, _) = fit(shipType)

            val emptyFitAllocations = memoryAllocations()

            // Fit affected module
            val affectedModule = modify {
                fit.fitModule(affectedModuleType, 0)
            }
            val allocationsAfterFittingAffectedModule = memoryAllocations()

            // Fit affecting module
            val affectingModule = modify {
                fit.fitModule(affectingModuleType, 1)
            }

            // Remove affected module and verify allocations
            modify {
                fit.removeModule(affectedModule)
            }

            assertCurrentAllocationsEqual(allocationsAfterFittingAffectedModule)

            modify {
                fit.removeModule(affectingModule)
            }

            assertCurrentAllocationsEqual(emptyFitAllocations)
        }

        // 1. Fit affected module
        // 2. Fit affecting module
        // 3. Remove affecting module
        // 4. Remove affected module
        run {
            val (fit, _) = fit(shipType)

            val emptyFitAllocations = memoryAllocations()

            // Fit affected module
            val affectedModule = modify {
                fit.fitModule(affectedModuleType, 0)
            }
            val allocationsAfterFittingAffectedModule = memoryAllocations()

            // Fit affecting module
            val affectingModule = modify {
                fit.fitModule(affectingModuleType, 1)
            }

            // Remove affecting module and verify allocations
            modify {
                fit.removeModule(affectingModule)
            }
            assertCurrentAllocationsEqual(allocationsAfterFittingAffectedModule)

            // Remove affected module and verify allocations
            modify {
                fit.removeModule(affectedModule)
            }

            assertCurrentAllocationsEqual(emptyFitAllocations)
        }

        // 1. Fit affecting module
        // 2. Fit affected module
        // 3. Remove affecting module
        // 4. Remove affected module
        run {
            val (fit, _) = fit(shipType)

            val emptyFitAllocations = memoryAllocations()

            // Fit affecting module
            val affectingModule = modify {
                fit.fitModule(affectingModuleType, 0)
            }
            val allocationsAfterFittingAffectingModule = memoryAllocations()

            // Fit affected module
            val affectedModule = modify {
                fit.fitModule(affectedModuleType, 1)
            }

            // Remove affecting module and verify allocations
            modify {
                fit.removeModule(affectingModule)
            }
            assertCurrentAllocationsEqual(allocationsAfterFittingAffectingModule)

            // Remove affected module and verify allocations
            modify {
                fit.removeModule(affectedModule)
            }

            assertCurrentAllocationsEqual(emptyFitAllocations)
        }

        // 1. Fit affecting module
        // 2. Fit affected module
        // 3. Remove affected module
        // 4. Remove affecting module
        run {
            val (fit, _) = fit(shipType)

            val emptyFitAllocations = memoryAllocations()

            // Fit affecting module
            val affectingModule = modify {
                fit.fitModule(affectingModuleType, 0)
            }
            val allocationsAfterFittingAffectingModule = memoryAllocations()

            // Fit affected module
            val affectedModule = modify {
                fit.fitModule(affectedModuleType, 1)
            }

            // Remove affected module and verify allocations
            modify {
                fit.removeModule(affectedModule)
            }
            assertCurrentAllocationsEqual(allocationsAfterFittingAffectingModule)

            // Remove affecting module and verify allocations
            modify {
                fit.removeModule(affectingModule)
            }

            assertCurrentAllocationsEqual(emptyFitAllocations)
        }
    }


    /**
     * Tests a hostile effect on the target ship for memory leaks.
     */
    @Test
    fun testHostileEffectOnShip() = runFittingTest {
        val attribute = attribute()

        val shipType = testShipType {
            attributeValue(attribute, 1.0)
        }

        val moduleType = moduleType(flags = ModuleFlags.PROJECTED) {
            attributeValue(attribute, 1.0)

            effectReference(
                effectOnShip(
                    category = Effect.Category.PROJECTED,
                    modifiedAttribute = attribute,
                    modifyingAttribute = attribute,
                    operation = AttributeModifier.Operation.ADD,
                )
            )
        }

        val (targetFit, _) = fit(shipType)
        val (sourceFit, _) = fit(shipType, moduleType) { _, module: Module ->
            module.setState(Module.State.ACTIVE)
        }

        val memoryAllocationsBeforeEffect = memoryAllocations()

        val remoteEffect = modify {
            targetFit.addHostileEffect(sourceFit)
        }

        modify {
            targetFit.removeHostileEffect(remoteEffect)
        }

        assertCurrentAllocationsEqual(memoryAllocationsBeforeEffect)
    }


    /**
     * Tests adding and removing a fit.
     */
    @Test
    fun testAddRemoveFit() = runFittingTest {
        // Add some skill effects to see if they'll be removed
        val charAttribute = attribute()
        val shipAttribute = attribute()

        testSkillType(skillLevel = 5) {
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = charAttribute,
                itemType = AttributeModifier.AffectedItemKind.CHARACTER,
            )
            skillBonus(
                baseSkillBonusValue = 10.0,
                attribute = shipAttribute,
                itemType = AttributeModifier.AffectedItemKind.SHIP,
            )
        }

        characterType {
            attributeValue(charAttribute, 100.0)
        }

        val shipType = testShipType {
            attributeValue(shipAttribute, 100.0)
        }

        val memoryAllocationsBeforeEffect = memoryAllocations()
        val (fit, _) = fit(shipType)
        modify {
            fit.remove()
        }

        assertCurrentAllocationsEqual(memoryAllocationsBeforeEffect)
    }


}