local LOVElyTests = require("tests.LOVElyTests")
local LT = require("LOVElyTree")
local RESULT = LT.RESULT

local function WaitAction(duration)
    local action = {}
    action.timer = 0
    action.name = "WaitAction (" .. duration .. "s)"

    function action.update(self, dt)
        self.timer = self.timer + dt
        return (self.timer >= duration) and RESULT.SUCCESS or RESULT.RUNNING
    end

    return action
end

local WaitActionWrapped = LT.WrapAction(WaitAction)

-- Helper function for assertions within tests
-- The only bad thing is that this messes with the line numbers in the error messages
-- If only Lua had C defines...
local function assert_equal(actual, expected, message)
    if actual ~= expected then
        error(message .. " (Expected: " .. tostring(expected) .. ", Got: " .. tostring(actual) .. ")")
    end
end

local function FailureNode()
    local node = { status = RESULT.RUNNING }
    function node:update(dt)
        node.status = RESULT.FAILURE
        return node.status
    end

    function node:reset() end

    return node
end

-- Compositor tests
LOVElyTests.register("Compositors", function(test_case)
    local function test_sequence()
        local t1 = WaitActionWrapped(0.5)
        local t2 = WaitActionWrapped(0.5)
        local fail = LT.Decorators.Invert(WaitActionWrapped(0))
        local seq = LT.Compositors.Sequence({ t1, t2, fail })
        local status = seq:update(0.3)
        assert_equal(status, RESULT.RUNNING, "Sequence should be running when first child is incomplete")
        status = seq:update(0.3) -- first child completes; second starts
        assert_equal(status, RESULT.RUNNING, "Sequence should be running on second child")
        status = seq:update(0.6)
        assert_equal(status, RESULT.FAILURE, "Sequence should have failed")
    end

    local function test_selector()
        local FalseTask = {}
        FalseTask.__index = FalseTask
        function FalseTask:new()
            local obj = { status = RESULT.RUNNING }
            setmetatable(obj, self)
            return obj
        end

        function FalseTask:update(dt)
            self.status = RESULT.FAILURE
            return self.status
        end

        function FalseTask:reset() end

        local falseTask = FalseTask:new()
        local successTask = WaitActionWrapped(0)
        successTask.update = function(self, dt)
            self.status = RESULT.SUCCESS
            return self.status
        end

        local sel = LT.Compositors.Selector({ falseTask, successTask })
        local status = sel:update(0.1) -- Run the success task
        assert_equal(status, RESULT.SUCCESS, "Selector should succeed when a child returns success")
    end

    local function test_random_once()
        local t1 = WaitActionWrapped(0)
        t1.update = function(self, dt)
            self.status = RESULT.SUCCESS
            return self.status
        end
        local t2 = WaitActionWrapped(0)
        t2.update = function(self, dt)
            self.status = RESULT.FAILURE
            return self.status
        end
        local ro = LT.Compositors.RandomOnce({ t1, t2 })
        local status = ro:update(0.1)
        assert_equal(status, ro.chosen == t1 and RESULT.SUCCESS or RESULT.FAILURE,
            "RandomOnce should return the status of its chosen child")
        ro:reset()
    end


    -- Test ParallelSequence with all children returning SUCCESS
    local function test_parallel_sequence_success()
        local t1 = LT.WrapAction(WaitAction)(0.3)
        local t2 = LT.WrapAction(WaitAction)(0.3)
        local ps = LT.Compositors.ParallelSequence({ t1, t2 })
        local status = ps:update(0.2)
        assert_equal(status, RESULT.RUNNING, "ParallelSequence should be running while there are incomplete children")
        ps:update(0.2)
        status = ps:update(0.2)
        assert_equal(status, RESULT.SUCCESS, "ParallelSequence should succeed if all children return success")
    end

    -- Test ParallelSequence with one child failing
    local function test_parallel_sequence_failure()
        local t1 = LT.WrapAction(WaitAction)(0.3)
        local fail = FailureNode()
        local ps = LT.Compositors.ParallelSequence({ t1, fail })
        local status = ps:update(0.1)
        assert_equal(status, RESULT.FAILURE,
            "ParallelSequence should fail if any child fails even if others are success or running")

        t1 = WaitActionWrapped(0.3)
        local t2 = LT.Decorators.Invert(WaitActionWrapped(0.5))
        ps = LT.Compositors.ParallelSequence({ t1, t2 })
        status = ps:update(0.1)
        assert_equal(status, RESULT.RUNNING, "ParallelSequence should be running")
        status = ps:update(0.3)
        assert_equal(status, RESULT.RUNNING, "ParallelSequence should be running")
        status = ps:update(0.3)
        assert_equal(status, RESULT.FAILURE, "ParallelSequence should fail after a running child fails")
    end

    -- Test ParallelSelector: succeeds if at least one child returns SUCCESS (after all finish)
    local function test_parallel_selector_success()
        local fail1 = FailureNode()
        local fail2 = FailureNode()
        local successTask = LT.WrapAction(WaitAction)(0)
        successTask.update = function(self, dt)
            self.status = RESULT.SUCCESS; return self.status
        end
        local ps = LT.Compositors.ParallelSelector({ fail1, fail2, successTask })
        ps:update(0.1)
        local status = ps:update(0.1)
        assert_equal(status, RESULT.SUCCESS, "ParallelSelector should succeed if at least one child returns success")
    end

    -- Test RandomSelector: succeeds if any child returns SUCCESS, regardless of order
    local function test_random_selector_success()
        local fail = FailureNode()
        local successTask = LT.WrapAction(WaitAction)(0)
        successTask.update = function(self, dt)
            self.status = RESULT.SUCCESS; return self.status
        end
        local rs = LT.Compositors.RandomSelector({ fail, successTask })
        rs:update(0.1)
        local status = rs:update(0.1)
        assert_equal(status, RESULT.SUCCESS, "RandomSelector should succeed if any child returns success")
    end

    -- Test RandomSequence with all children returning SUCCESS
    local function test_random_sequence_success()
        local t1 = LT.WrapAction(WaitAction)(0)
        t1.update = function(self, dt)
            self.status = RESULT.SUCCESS; return self.status
        end
        local t2 = LT.WrapAction(WaitAction)(0)
        t2.update = function(self, dt)
            self.status = RESULT.SUCCESS; return self.status
        end
        local rs = LT.Compositors.RandomSequence({ t1, t2 })
        rs:update(0.1)
        local status = rs:update(0.1)
        assert_equal(status, RESULT.SUCCESS, "RandomSequence should succeed if all children return success")
    end

    -- Test RandomSequence with at least one child failing
    local function test_random_sequence_failure()
        local t1 = LT.WrapAction(WaitAction)(0)
        t1.update = function(self, dt)
            self.status = RESULT.SUCCESS; return self.status
        end
        local fail = FailureNode()
        local rs = LT.Compositors.RandomSequence({ t1, fail })
        rs:update(0.1)
        local status = rs:update(0.1)
        assert_equal(status, RESULT.FAILURE, "RandomSequence should fail if any child fails")
    end


    test_case("test_sequence", test_sequence)
    test_case("test_selector", test_selector)
    test_case("test_random_once", test_random_once)

    test_case("test_parallel_sequence_success", test_parallel_sequence_success)
    test_case("test_parallel_sequence_failure", test_parallel_sequence_failure)
    test_case("test_parallel_selector_success", test_parallel_selector_success)
    test_case("test_random_selector_success", test_random_selector_success)
    test_case("test_random_sequence_success", test_random_sequence_success)
    test_case("test_random_sequence_failure", test_random_sequence_failure)
end)

LOVElyTests.run_all()
