task = public()
task.tasks = {}
task.nextId = 1
task.cmdsMt = {}
task.cmdsMt.__index = task.cmdsMt


function task:Start() 
end


function task:Test()
    return true
end


function task:GetName()
    return self:GetClass().name
end


function task:GetCmd()
    for k, v in ipairs(task.tasks) do
        for l, w in ipairs(v) do
            if w == self then
                return v.cmd
            end
        end
    end

    return nil
end


local function GetSyncFunctionName(name)
    local upperName = string.upper(name)
    local length = string.len(name)
    local syncFunctionName

    for i = 1, length do
        if string.sub(name, i, i) == string.sub(upperName, i, i) then
            if i ~= 1 then
                syncFunctionName =  string.sub(name, i, l)
                break
            end
        end
    end

    return syncFunctionName
end


function NewTask(name)
    local syncFunctionName = GetSyncFunctionName(name)

    if not syncFunctionName or _G[name] or _G[syncFunctionName] then
        printerror("Wrong task name: " .. name)
        return
    end

    _G[name] = public(task)
    _G[name].name = name

    _G[syncFunctionName] = function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
        local t = _G[name](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
        local env, tenv, b
        local i = 1

        repeat
            b, tenv = pcall(getfenv, i)

            if b then
                env = tenv
            end

            i = i + 1
        until not b

        if env.sys_cmd and env.sys_cmd:IsActive() then
            env.sys_cmd(t)
        else
            env.sys_cmd = Cmd(t)
            env.sys_cmd:SetHolded()
        end
    end
end


function DeleteTask(name)
    _G[name] = nil
    _G[GetSyncFunctionName(name)] = nil
end


local function Unhold()
    if not task.hold then
        return
    end

    local unhold = true

    for k, v in ipairs(task.tasks) do
        if v.holded then
            unhold = false
            break
        end
    end

    if unhold then
        task.hold = false
        Game:Hold(task.hold)
    end
end


local function RunQueue(queue)
    local result

    while #queue > 0 do
        if not queue.s then
            queue[1]:Start(result)
            queue.s = true
        end

        result = queue[1]:Test()

        if result then
            table.remove(queue, 1)
            queue.s = false
        else
            break
        end
    end
end


function UpdateTasks()
    for k, v in ipairs(task.tasks) do
        local id = v.cmd.id

        RunQueue(v)

        if #v == 0 then
            for l, w in ipairs(task.tasks) do
                if w.cmd.id == id then
                    table.remove(task.tasks, l)
                    Unhold()
                    break
                end
            end
        end
    end
end


function RemoveTasks()
    task.tasks = {}
end


local function IsKey(id)
    local t = type(id)

    if t == "string" or t == "number" then
        return true
    end
end


local function FormatQueue(queue)
    if not queue then
        queue = {}
    end

    if (IsKey(queue) or queue.IsChildClassOf and queue:IsChildClassOf(task)) then
        queue = {queue}
    end

    for k, v in ipairs(queue) do
        if IsKey(v) then
            queue[k] = tAction(v)
        end
    end

    return queue
end


function Cmd(queue)
    queue = FormatQueue(queue)
    queue.cmd = {}
    setmetatable(queue.cmd, task.cmdsMt)
    queue.cmd.id = task.nextId
    task.nextId = task.nextId + 1
    table.insert(task.tasks, queue)
    RunQueue(queue)

    return queue.cmd
end


function IsCmd(cmd)
    return getmetatable(cmd) == task.cmdsMt
end


local function GetCmdKey(cmd)
    for k, v in ipairs(task.tasks) do
        if v.cmd.id == cmd.id then
            return k
        end
    end
end


function task.cmdsMt:Remove()
    local k = GetCmdKey(self)

    if k then
        table.remove(task.tasks, k)
        Unhold()
    end
end


function task.cmdsMt:IsActive()
    local k = GetCmdKey(self)

    if k then
        return true
    end

    return false
end


function task.cmdsMt:__call(queue, pin)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    queue = FormatQueue(queue)

    if pin then
        for i, v in ipairs(task.tasks[k]) do
            if v:GetClass() == tPin and v[1] == pin then
                for l, w in ipairs(queue) do
                    table.insert(task.tasks[k], i, w)
                    i = i + 1
                end

                break
            end
        end
    else
        for l, w in ipairs(queue) do
            table.insert(task.tasks[k], w)
        end
    end
end


function task.cmdsMt:SetHolded(holded)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    if holded == nil then
        holded = true
    end

    task.tasks[k].holded = holded

    if holded then
        if not task.hold then
            task.hold = true
            Game:Hold(task.hold)
        end
    else
        Unhold()
    end
end


function task.cmdsMt:SetNotSavable(notSavable)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    if notSavable == nil then
        notSavable = true
    end

    task.tasks[k].notSavable = notSavable
end


function task.cmdsMt:SetNotPersist(notPersist)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    if notPersist == nil then
        notPersist = true
    end

    task.tasks[k].notPersist = notPersist
end


function task.cmdsMt:SetSceneOnly(sceneOnly)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    if sceneOnly == nil then
        sceneOnly = true
    end

    task.tasks[k].sceneOnly = sceneOnly
end

function task.cmdsMt:SetName(name)
    local k = GetCmdKey(self)

    if not k then
        return
    end

    if sceneOnly == nil or name == "" then
        return end

    task.tasks[k].name = name
end

function RemoveTask(name)
    local i = 1
    while i <= #task.tasks do
        if task.tasks[i].name == name then
            table.remove(task.tasks, i)
        else
            i = i + 1
        end
    end
end

function CmdCanSave()
    for k, v in ipairs(task.tasks) do
        if v.notSavable then
            return false
        end
    end

    return true
end


function CmdSceneClean()
    local i = 1

    while i <= #task.tasks do
        if task.tasks[i].sceneOnly then
            table.remove(task.tasks, i)
        else
            i = i + 1
        end
    end

    Unhold()
end


local function GetSaveValue(value)
    local tv = type(value)

    if tv ~= "number" and tv ~= "string" and tv ~= "boolean" and tv ~= "nil" and not IsCmd(value) then
        return
    end

    if tv == "boolean" then
        value = value and "|TRUE" or "|FALSE"
    elseif tv == "number" then
        value = tostring(value)
    elseif tv == "string" then
        value = "&" .. value
    elseif IsCmd(value) then
        value = "|CMD" .. value.id
    end

    return value
end


local function GetLoadValue(value)
    if not value then
        return value
    end

    local valueNumber = tonumber(value)

    if string.len(value) == 0 then
        value = nil
    elseif value == "|TRUE" then
        value = true
    elseif value == "|FALSE" then
        value = false
    elseif valueNumber then
        value = valueNumber
    elseif string.sub(value, 1, 1) == "&" then
        value = string.sub(value, 2)
    elseif string.sub(value, 1, 4) == "|CMD" then
        local id = tonumber(string.sub(value, 5))
        value = {}
        setmetatable(value, task.cmdsMt)
        value.id = id
    end

    return value
end


function SaveTasks()
    local t = {}

    t.nextId = task.nextId

    for k, v in ipairs(task.tasks) do
        if not v.notPersist then
            for i = 1, #v do
                table.insert(t, {})
    
                if i == 1 then
                    t[#t].sys_queue = GetSaveValue(k)
                    t[#t].sys_cmd_id = GetSaveValue(v.cmd.id)
                    t[#t].sys_holded = GetSaveValue(v.holded)
                    t[#t].sys_notSavable = GetSaveValue(v.notSavable)
                    t[#t].sys_sceneOnly = GetSaveValue(v.sceneOnly)
                end
    
                t[#t].sys_task = GetSaveValue(v[i]:GetName())
    
                for l, w in pairs(v[i]) do
                    if l ~= "sys_queue" and l ~= "sys_cmd_id" and l ~= "sys_holded" and l ~= "sys_notSavable" and l ~= "sys_sceneOnly" and l ~= "sys_task" then
                        t[#t][l] = GetSaveValue(w)
                    end
                end
            end
        end
    end

    local s = table.save(t)
    local st = SavableTable("sys_tasks")
    st:Set("tasks_table", s)
end


function LoadTasks()
    local st = SavableTable("sys_tasks")
    local s = st:Get("tasks_table")
    
    if s == nil then return end
    
    local t = table.load(s)

    local queueVal, queue, object, objectName, value

    task.tasks = {}
    task.nextId = t.nextId

    for k, v in ipairs(t) do
        queueVal = GetLoadValue(v.sys_queue)

        if queueVal then
            queue = queueVal
            task.tasks[queue] = {}
            task.tasks[queue].cmd = {}
            setmetatable(task.tasks[queue].cmd, task.cmdsMt)
            task.tasks[queue].cmd.id = GetLoadValue(v.sys_cmd_id)
            task.tasks[queue].holded = GetLoadValue(v.sys_holded)
            task.tasks[queue].notSavable = GetLoadValue(v.sys_notSavable)
            task.tasks[queue].sceneOnly = GetLoadValue(v.sys_sceneOnly)
            task.tasks[queue].s = true
        end

        objectName = GetLoadValue(v.sys_task)

        if not _G[objectName] then
            printerror("LoadTasks() error: cannot find task named: " .. objectName)
        else
            object = _G[objectName]()

            for l, w in pairs(v) do
                if l ~= "sys_queue" and l ~= "sys_cmd_id" and l ~= "sys_holded" and l ~= "sys_notSavable" and l ~= "sys_sceneOnly" and l ~= "sys_task" then
                    object[l] = GetLoadValue(w)
                end
            end

            table.insert(task.tasks[queue], object)
        end
    end
end


NewTask("tAction")
action = {}


function tAction:Start(result)
    if type(action[self[1]]) == "function" then
        self.result = action[self[1]](result, self:GetCmd(), self[2], self[3], self[4], self[5], self[6], self[7], self[8], self[9], self[10])
    else
        printerror("No action: " .. self[1])
    end
end


function tAction:Test()
    if self.result then
        return self.result
    else
        return true
    end
end


NewTask("tPin")


NewTask("tWaitForCmd")


function tWaitForCmd:Create()
    if type(self[1]) == "table" then
        self[1] = self[1].id
    end
end


function tWaitForCmd:Test()
    for k, v in ipairs(task.tasks) do
        if v.cmd.id == self[1] then
            return false
        end
    end

    return true
end


NewTask("tFrameDelay")


function tFrameDelay:Test()
    if not self[1] then
        self[1] = 1
    end

    if not self.frame then
        self.frame = -1
    else
        self.frame = self.frame + 1
    end

    if self.frame == self[1] then
        return true
    end
end