local sit = require 'room.sit'

local trash_data = serialize.define({
    {"x", "f32"},
    {"y", "f32"},
    {"model", "u1"}
})
local trash_pos_data = serialize.define({
    {"x", "f32"},
    {"y", "f32"}
})

function add_entity(handle, e)
    handle.properties.entities = handle.properties.entities or {}
    handle.properties.entities[e] = true
end

function save(handle)
    return {
        trash_list = handle.properties.trash_list,
    }
end

function load(handle, data)
    handle.properties.trash_list = data.trash_list
end

local function random_room(rooms)
    local offset = math.random(#rooms)
    for i = 1, #rooms do
        local room = rooms[((offset + i - 1) % #rooms) + 1]
        if room.properties.stand_areas then
            return room
        end
    end
    return nil
end

function update(handle)
    local rooms = handle.rooms
    local entities = handle.properties.entities or {}

    for _, trash in ipairs(handle.properties.to_remove or {}) do
        handle:execute_command("clear_trash", trash_pos_data.encode({
            x = trash.x,
            y = trash.y,
        }))
    end
    handle.properties.to_remove = {}

    for e, _ in pairs(entities) do
        if not e.valid then
            entities[e] = nil
        elseif e.should_release then
            entities[e] = nil
            e:release()
        elseif not e.is_walking then
            if e.properties.room and e.properties.room.valid then
                local room = e.properties.room
                local rx, ry = room:get_position()

                local x, y = e:get_position()
                local target = e.properties.target
                -- Did we reach our target?
                if target ~= nil then
                    if math.abs(x - target.x) > 0.05 or math.abs(y - target.y) > 0.05 then
                        e.should_release = true
                    end
                    e.properties.target = nil
                end
                e.properties.idle_time = (e.properties.idle_time or 40) - 1
                if e.properties.idle_time <= 0 then
                    local trash_list = handle.properties.trash_list or {}
                    local model = math.random(0, 1);
                    table.insert(trash_list, {
                        x = x,
                        y = y,
                        model = model,
                        drop_time = game_time(),
                    })
                    handle:execute_command("drop_trash", trash_data.encode({
                        x = x,
                        y = y,
                        model = model,
                    }))
                    handle.properties.trash_list = trash_list
                    e:set_var_bool("has_trash", false)
                    e.should_release = true
                end
            else
                e.properties.room = nil
                local room = random_room(rooms)
                if room ~= nil then
                    local rx, ry = room:get_position()
                    e.properties.idle_time = 40 -- Only stand around to drop trash
                    local stand_areas = room.properties.stand_areas
                    local pos = stand_areas[math.random(#stand_areas)]
                    local target = {
                        x = rx + pos.x + 0.1 + math.random() * 0.8,
                        y = ry + pos.y + 0.1 + math.random() * 0.8
                    }
                    e:walk_to(target.x, target.y)
                    e.properties.target = target
                    e.properties.room = room
                end
            end
        end
    end
end

function update_client(handle, entities)
    local fading = handle.properties.fading or {}
    local len = #fading
    for i = 1, len do
        if i > len then
            break
        end
        local t = fading[i]
        t.time = t.time - 1
        if t.time <= 0 then
            t.entity:remove()
            table.remove(fading, i)
            i = i - 1
            len = len - 1
        end
    end
end

local function get_model(idx)
    if idx == 0 then
        return "base:silly/paper_plane"
    else
        return "base:trash/takeout"
    end
end

function on_exec(handle, method, data)
    if is_client then
        if method == "drop_trash" then
            local d = trash_data.decode(data)
            local mdl = get_model(d.model)
            local e = create_global_static_entity(mdl, d.x, 0.7, d.y)
            e:rotate(math.random() * math.pi * 2, 10)
            e:move(d.x, 0.0, d.y, 10)
            local trash = handle.properties.trash or {}
            table.insert(trash, {
                info = d,
                entity = e
            })
            handle.properties.trash = trash
        elseif method == "clear_trash" then
            local d = trash_pos_data.decode(data)
            local to_remove = -1
            local trash = handle.properties.trash or {}
            for idx, t in ipairs(trash) do
                if t.info.x == d.x and t.info.y == d.y then
                    to_remove = idx
                    t.entity:move(t.info.x, -0.2, t.info.y, 200)
                    break
                end
            end
            if to_remove ~= -1 then
                local fading = handle.properties.fading or {}
                handle.properties.fading = fading
                local removed = table.remove(trash, to_remove)
                removed.time = 205
                table.insert(fading, removed)
            end
        end
    end
end

function create_state(handle)
    local trash_list = {}
    for _, t in ipairs(handle.properties.trash_list or {}) do
        -- Filter out the drop_time as the client doesn't
        -- need that info
        table.insert(trash_list, {
            x = t.x,
            y = t.y,
            model = t.model or 0,
        })
    end
    return {
        trash_list = trash_list,
    }
end

function apply_state(handle, state)
    if state.trash_list ~= nil then
        for _, t in ipairs(state.trash_list) do
            local mdl = get_model(t.model)
            local e = create_global_static_entity(mdl, t.x, 0.0, t.y)
            e:rotate(math.random() * math.pi * 2, 10)
            local trash = handle.properties.trash or {}
            table.insert(trash, {
                info = t,
                entity = e
            })
            handle.properties.trash = trash
        end
    end
end