require 'lib.utils.vec3'

--- @alias Id integer
--- @type Id
local characterId = 1

--- @class Character
--- @field id Id
--- @field behaviors Behavior[]
--- @field _behaviorsIdx {string: integer}
local character = {}
character.__index = character

--- Создаёт персонажа, которым будет управлять или игрок или компьютер
--- @param name string
local function spawn(name)
    local char = {}

    char = setmetatable(char, character)
    char.id = characterId
    characterId = characterId + 1
    char.behaviors = {}
    char._behaviorsIdx = {}

    Tree.level.characters[char.id] = char
    return char
end

--- Проверяет, есть ли у персонажа поведение [behavior].
--- @generic T : Behavior
--- @param behavior T
--- @return T | nil
function character:has(behavior)
    --- @cast behavior Behavior
    local idx = self._behaviorsIdx[behavior.id]
    if not idx then return nil end
    return self.behaviors[idx] or nil
end

--- Если у персонажа есть поведение [behavior], применяет к нему [fn]
---
--- Дальше meme для интеллектуалов
---
--- *Я: мам купи мне >>=*
---
--- *Мама: у нас дома есть >>=*
---
--- *Дома:*
--- @generic T : Behavior
--- @generic V
--- @param behavior T
--- @param fn fun(behavior: T) : V | nil
--- @return V | nil
function character:try(behavior, fn)
    local b = self:has(behavior)
    if not b then return end
    return fn(b)
end

--- usage:
--- addBehavior( {logic.new(), graphics.new(), ...} )
---
--- or you may chain this if you are a wannabe haskell kiddo
--- @param behaviors Behavior[]
--- @return Character | nil
function character:addBehavior(behaviors)
    for _, b in ipairs(behaviors) do
        -- if b.dependencies then
        --     for _, dep in ipairs(b.dependencies) do
        --         if not self:has(dep) then
        --             return print("[Character]: cannot add \"" .. b.id ..
        --                 "\" for a character (Id = " .. self.id .. "): needs \"" .. dep.id .. "\"!")
        --         end
        --     end
        -- end
        b.owner = self
        table.insert(self.behaviors, b)
        self._behaviorsIdx[b.id] = #self.behaviors
    end
    return self
end

--- Добавляет персонажа в очередь на удаление.
--- В конце фрейма он умирает. Ужасной смертью.
---
--- Ещё этот метод должен освобождать ресурсы в поведениях. Мы против утечек памяти!
function character:die()
    for _, b in ipairs(self.behaviors) do
        if b.die then b:die() end
    end

    table.insert(Tree.level.deadIds, self.id)
end

function character:update(dt)
    for _, b in ipairs(self.behaviors) do
        if b.update then b:update(dt) end
    end
end

function character:draw()
    for _, b in ipairs(self.behaviors) do
        if b.draw then b:draw() end
    end
end

return { spawn = spawn }
