Framework = {}

function Framework:new()
    file_load_lua("framework/animator.lua")
    file_load_lua("framework/tools.lua")
    file_load_lua("framework/transition.lua")

    file_load_lua("framework/objects/base_object.lua")
    file_load_lua("framework/objects/shape.lua")
    file_load_lua("framework/objects/bitmap.lua")
    file_load_lua("framework/objects/animated_bitmap.lua")
    file_load_lua("framework/objects/text.lua")
    file_load_lua("framework/objects/active_text.lua")
    file_load_lua("framework/objects/animated_text.lua")
    file_load_lua("framework/objects/button.lua")
    file_load_lua("framework/objects/labeled_button.lua")
    file_load_lua("framework/objects/combo_fx.lua")
    file_load_lua("framework/objects/slider.lua")
    file_load_lua("framework/objects/input_line.lua")
    file_load_lua("framework/objects/resizable_box.lua")

    file_load_lua("framework/views/base_view.lua")

    file_load_lua("framework/sandy.lua")

    fw = copy_table(self)
    fw:init()
    return fw
end


function Framework:boot_end()
    sandy_boot()
end


function Framework:init()
    math.randomseed( timer_systime() )
    math.random(); math.random(); math.random()
    self:net_init()
    self.show_fps = false
end


function Framework:init_end()
    sandy_init()
end


function Framework:update(dt)
    math.random() -- just to make it all even more random
    self:net_update(dt)
    
    if self.scheduled_view_change then
        self.transition = copy_table(Transition)
        self.transition:init(self.scheduled_view_change.view, self.scheduled_view_change.options, self.scheduled_view_change.transition)
        self.scheduled_view_change = nil
    end

    if self.scheduled_view_add then
        self.view = self.scheduled_view_add.view
        self.scheduled_view_add = nil
        if self.view._transition then
            input_enable(false)
        end
    end


    if self.transition then
        self.transition:update(dt)
        if self.transition.to_destroy then
            self.transition:destroy()
            self.transition = nil
        end
    else
        if self.view._transition then
            local t = self.view._transition
            if t.closing then
                t.time = t.time - dt
                if t.time <= 0 then
                    self.view:destroy()
                    self.view = self.view.parent_view
                    input_enable(true)
                end
            else
                t.time = t.time + dt
                if t.time >= t.total_time then
                    self.view:destroy_prerendered_view()
                    self.view._transition = nil
                    input_enable(true)
                end
            end
        elseif self.view.to_destroy then
            if self.view.parent_view then
                self.view._transition = { time = 0.3, total_time = 0.3, closing = true }
                self.view:prerender_view()
            else
                self.view:destroy()
                self.view = self.view.parent_view
            end
        else
            self.view:update(dt)
        end
    end

    if kbd_struck("f2") then
        self.show_fps = not self.show_fps
    end
end


function Framework:recursive_render(view)
    if view.parent_view then
        self:recursive_render(view.parent_view)
    end
    if view._transition then
        local t = view._transition
        local scale = 1 + 0.2 * (1 - t.time / t.total_time)
        if view.prerendered_view then
            gfx_render_sprite(view.prerendered_view, "main", 400, 300, t.time / t.total_time, 0, scale, scale)
        end
    else
        view:render()
    end
end


function Framework:render()
    if self.transition then
        self.transition:render()
    else
        self:recursive_render(self.view)
    end

    if debug_info then
        gfx_render_simple_text(10, 570, debug_info)
    end

    if self.show_fps then
        gfx_render_text("hud12", 5, 5, tostring(math.floor(timer_fps() + 0.5)), "left top")
    end
end


function Framework:destroy_view_chain(last_view)
    local view = last_view
    while view do
        view:destroy()
        view = view.parent_view
    end
end


function Framework:destroy()
    if self.transition then
        self.transition:destroy()
    end
    self:destroy_view_chain(self.view)
end


--- VIEWS ------------------------------------------------------------

function Framework:change_view(new_view, options, transition)
    assert(new_view)
    self.scheduled_view_change = {
        view = new_view,
        options = options,
        transition = transition,
    }
end


function Framework:add_view(new_view, options, transition)
    assert(new_view)

    local v = copy_table(new_view)
    v.parent_view = self.view
    v:init(options)
    v:prerender_view()
    timer_catchup()

    self.scheduled_view_add = {
        view = v,
        options = options,
        transition = transition or "fade",
    }
    v._transition = { time = 0, total_time = 0.3 }
end


--- NET QUEUE --------------------------------------------------------

function Framework:net_queue_post(data, url)
    local job = {
        url = url,
        sent = false,
        data = data,
    }
    table.insert(self.net_queue, job)
    self:net_save_queue()
    self:net_update_queue()
end


function Framework:net_init()
    self.net_update_timer = 5
    self.net_queue = {}
    self:net_load_queue()
end


function Framework:net_update(dt)
    self.net_update_timer = self.net_update_timer - dt
    if self.net_update_timer <= 0 then
        if #self.net_queue > 0 then
            self:net_update_queue()
        end
        self.net_update_timer = 5
    end
end


function Framework:net_update_queue()
    -- post queued data
    for i,v in ipairs(self.net_queue) do
        if not v.sent then
            local id = net_post_begin(api_server..v.url, api_user, api_password)
            for key, value in pairs(v.data) do
                net_post_value(id, key, value)
            end
          	net_post_end(id)

            v.sent = true
            v.job_id = id
        end
    end

    -- check post results
    local status
    for i,v in ipairs(self.net_queue) do
        if v.sent then
            status = net_status(v.job_id)
            if status == "ok" or status == "error" then
                if status == "ok" and net_output(v.job_id) == "OK" then
                    v.to_destroy = true
                    log("Net job finished successfully")
                else
                    -- restart job
                    v.sent = false
                    v.job_id = nil
                    log("Error sending data - restarting job")
                end
                net_release(v.job_id)
            end
        end
    end

    -- prune finished jobs
    for i = #self.net_queue, 1, -1 do
        if self.net_queue[i].to_destroy then
            table.remove(self.net_queue, i)
        end
    end
    
    self:net_save_queue()
end


function Framework:net_save_queue()
    file_save("net_queue.bin", table_to_string(self.net_queue), true)
end


function Framework:net_load_queue()
    if file_exists("net_queue.bin") then
        self.net_queue = file_load_lua_table("net_queue.bin")
        if #self.net_queue > 0 then
            log("Restored net queue ("..#self.net_queue.." elements)")
        end
    else
        self.net_queue = {}
    end
end
