local PriorityQueue        = require "lib.utils.priority_queue"
local easing               = require "lib.utils.easing"

local initiativeComparator = function(id_a, id_b)
    local res = Tree.level.characters[id_a]:try(Tree.behaviors.stats, function(astats)
        local res = Tree.level.characters[id_b]:try(Tree.behaviors.stats, function(bstats)
            return astats.initiative > bstats.initiative
        end)
        return res
    end)
    return res or false
end

--- @class TurnOrder
--- @field actedQueue PriorityQueue Очередь тех, кто сделал ход в текущем раунде
--- @field pendingQueue PriorityQueue Очередь тех, кто ждет своего хода в текущем раунде
--- @field current? Id Считаем того, кто сейчас ходит, отдельно, т.к. он ВСЕГДА первый в списке
--- @field isTurnsEnabled boolean
local turnOrder            = {}
turnOrder.__index          = turnOrder

local function new()
    return setmetatable({
        actedQueue = PriorityQueue.new(initiativeComparator),
        pendingQueue = PriorityQueue.new(initiativeComparator),
        isTurnsEnabled = true,
    }, turnOrder)
end

--- Перемещаем активного персонажа в очередь сходивших
---
--- Если в очереди на ход больше никого нет, заканчиваем раунд
---
--- Анимируем камеру к следующему персонажу. Если это ИИ, то активируем его логику.
function turnOrder:next()
    self.actedQueue:insert(self.current)
    local next = self.pendingQueue:peek()
    if not next then self:endRound() else self.current = self.pendingQueue:pop() end

    local char = Tree.level.characters[self.current]

    Tree.level.selector:lock()
    char:try(Tree.behaviors.positioned, function(positioned)
        Tree.level.camera:animateTo(positioned.position, 1500, easing.easeInOutCubic)(
            function()
                if char:has(Tree.behaviors.ai) then
                    char:has(Tree.behaviors.ai):makeTurn()(
                        function()
                            self:next()
                        end)
                else
                    char:try(Tree.behaviors.effects, function(effects)
                        -- print("[TurnOrder]: ну мы пытаемся применить эффект к", char.id)
                        effects:beforeTurn()
                    end)
                    Tree.level.selector:unlock()
                    Tree.level.selector:select(self.current)
                end
            end
        )
    end)
end

--- Производим действия в конце раунда
---
--- Меняем местами очередь сходивших и не сходивших (пустую)
function turnOrder:endRound()
    assert(self.pendingQueue:size() == 0, "[TurnOrder]: tried to end the round before everyone had a turn")
    print("[TurnOrder]: end of the round")

    for _, id in ipairs(self.actedQueue.data) do
        local char = Tree.level.characters[id]
        char:try(Tree.behaviors.spellcaster, function(spellcaster)
            spellcaster:processCooldowns()
        end)
        char:try(Tree.behaviors.effects, function(effects)
            effects:afterTurn()
        end)
    end

    self.actedQueue, self.pendingQueue = self.pendingQueue, self.actedQueue
    self.current = self.pendingQueue:pop()
end

--- Пересчитать очередность хода
function turnOrder:reorder()
    local _acted, _pending = PriorityQueue.new(initiativeComparator), PriorityQueue.new(initiativeComparator)

    --- сортировка отдельно кучи не ходивших и ходивших
    while self.pendingQueue:peek() do
        _pending:insert(self.pendingQueue:pop())
    end

    while self.actedQueue:peek() do
        _acted:insert(self.actedQueue:pop())
    end

    self.actedQueue, self.pendingQueue = _acted, _pending

    local t = {}
    for id in self:getOrder(10) do
        table.insert(t, id)
    end
    print("[TurnOrder]: next 10 turns")
    print(table.concat(t, ", "))
end

--- Итератор по бесконечной цикличной очереди хода
--- @param count integer?
function turnOrder:getOrder(count)
    local order = { self.current }
    local _acted, _pending = self.actedQueue:copy(), self.pendingQueue:copy()

    local i, j = 0, 0
    local nextTurn = false -- если вышли за пределы текущего хода, то сортируем список, чтобы поставить активного персонажа на свое место
    return function()
        -------------------- Очередь этого хода: активный + не сходившие
        i = i + 1
        if count and count < 1 then return nil end
        if count and i > count then return nil end
        if i == 1 then return self.current end

        if _pending:peek() then
            local next = _pending:pop()
            table.insert(order, next)
            return next
        end

        -------------------- Очередь следующих ходов: цикл по всем персонажам в порядке инициативы
        if not nextTurn then
            while _acted:peek() do
                table.insert(order, _acted:pop())
            end
            table.sort(order, initiativeComparator)
            nextTurn = true
        end

        j = j + 1
        if j % #order == 0 then return order[#order] end
        return order[j % #order]
    end
end

--- @param id Id
function turnOrder:add(id)
    self.actedQueue:insert(id) -- новые персонажи по умолчанию попадают в очередь следующего хода
end

--- Удалить персонажа из очереди хода (например, при смерти)
--- @param id Id
function turnOrder:remove(id)
    if self.current == id then
        self.current = self.pendingQueue:pop()
        if not self.current then
            self:endRound()
        end
        return
    end

    local function filterQueue(q, targetId)
        local newQ = PriorityQueue.new(initiativeComparator)
        for _, val in ipairs(q.data) do
            if val ~= targetId then
                newQ:insert(val)
            end
        end
        return newQ
    end

    self.actedQueue = filterQueue(self.actedQueue, id)
    self.pendingQueue = filterQueue(self.pendingQueue, id)
end

return { new = new }
