require("test/setup")

local Baserunning = require("baserunning")

---@return Baserunning, { called: boolean }
function buildBaserunning()
    local thirdOutCallbackData = { called = false }
    local baserunning = Baserunning.new(announcer, function()
        thirdOutCallbackData.called = true
    end)
    return baserunning, thirdOutCallbackData
end

---@alias BaseIndexOrXyPair (number | XyPair)

--- NOTE: in addition to the given runners, there is implicitly a batter running from first.
---@param runnerLocations BaseIndexOrXyPair[]
---@return Baserunning
function buildRunnersOn(runnerLocations)
    local baserunning = buildBaserunning()
    baserunning:convertBatterToRunner()
    for _, location in ipairs(runnerLocations) do
        baserunning:pushNewBatter()
        local runner = baserunning.batter
        baserunning:convertBatterToRunner()
        if type(location) == "number" then
            -- Is a base index
            -- Push the runner *through* each base.
            for b = 1, location do
                runner.x = C.Bases[b].x
                runner.y = C.Bases[b].y
                baserunning:updateNonBatterRunners(0.001, false, 0.001)
            end
        else
            -- Is a raw XyPair
            runner.x = location.x
            runner.y = location.y
        end
    end
    return baserunning
end

---@alias Condition { fielderWithBallAt: XyPair, outWhen: BaseIndexOrXyPair[][], safeWhen: BaseIndexOrXyPair[][] }

---@param expected boolean
---@param fielderWithBallAt XyPair
---@param when number[][]
function assertRunnerOutCondition(expected, when, fielderWithBallAt)
    local msg = expected and "out" or "safe"
    for _, runnersOn in ipairs(when) do
        local baserunning = buildRunnersOn(runnersOn)
        local outedSomeRunner = baserunning:outEligibleRunners(fielderWithBallAt)
        luaunit.failIf(outedSomeRunner ~= expected, "Runner should have been " .. msg .. ", but was not!")
    end
end

---@param condition Condition
function assertRunnerStatuses(condition)
    assertRunnerOutCondition(true, condition.outWhen, condition.fielderWithBallAt)
    assertRunnerOutCondition(false, condition.safeWhen, condition.fielderWithBallAt)
end

function testForceOutsAtFirst()
    assertRunnerStatuses({
        fielderWithBallAt = C.Bases[C.First],
        outWhen = {
            {},
            { 1 },
            { 2 },
            { 3 },
            { 1, 2 },
            { 1, 3 },
            { 2, 3 },
            { 1, 2, 3 },
        },
        safeWhen = {},
    })
end

function testForceOutsAtSecond()
    assertRunnerStatuses({
        fielderWithBallAt = C.Bases[C.Second],
        outWhen = {
            { 1 },
            { 1, 2 },
            { 1, 3 },
            { 1, 2, 3 },
        },
        safeWhen = {
            {},
            { 2 },
            { 3 },
            { 2, 3 },
        },
    })
end

function testForceOutsAtThird()
    assertRunnerStatuses({
        fielderWithBallAt = C.Bases[C.Third],
        outWhen = {
            { 1, 2 },
            { 1, 2, 3 },
        },
        safeWhen = {
            { 1 },
            { 2 },
            { 3 },
            { 2, 3 },
            { 1, 3 },
        },
    })
end

function testForceOutsAtHome()
    assertRunnerStatuses({
        fielderWithBallAt = C.Bases[C.Home],
        outWhen = {
            { 1, 2, 3 },
        },
        safeWhen = {
            {},
            { 1 },
            { 2 },
            { 3 },
            { 1, 2 },
            { 1, 3 },
            { 2, 3 },
        },
    })
end

function testTagOutsShouldHappenOffBase()
    local fielderWithBallAt = utils.xy(10, 10) -- Some location not on a base.
    local farFromFielder = utils.xy(100, 100)
    assertRunnerStatuses({
        fielderWithBallAt = fielderWithBallAt,
        outWhen = {
            { fielderWithBallAt },
        },
        safeWhen = {
            { farFromFielder },
        },
    })
end

function testTagOutsShouldNotHappenOnBase()
    assertRunnerStatuses({
        fielderWithBallAt = C.Bases[C.Third],
        outWhen = {},
        safeWhen = {
            { 2 },
            { 3 },
            { 2, 3 },
            { 2, 3, 4 },
        },
    })
end

os.exit(luaunit.LuaUnit.run())
