local sit = require 'room.sit'
local plants = require 'objects.plants'
local common = require 'room.common'

local student_data = serialize.define({
    {"sit", "bool"}
})
local staff_data = serialize.define({
    {"sit", "bool"},
    {"sale_toggle", "bool"}
})


function update(room)
    local waiting_size = 0
    local width, height = room:get_size()
    local ox, oy = room:get_position()
    for x = 0, width - 1 do
        for y = 0, height - 1 do
            local tile = level.get_tile(ox + x, oy + y)
            if tile:get_name() == "base:waiting_area" then
                waiting_size = waiting_size + 1
            end
        end
    end
    room.capacity = waiting_size * 3
end

function save(room)
    local visitors = room.visitors
    local owned = room.owned
    local objects = room.objects

    local v_data = {}
    for _, e in ipairs(visitors) do
        local wid = nil
        if e.properties['worker'] ~= nil then
            wid = e.properties['worker'].id
        end
        v_data[e.id] = {
            stand_delay = e.properties['stand_delay'],
            wait_time = e.properties['wait_time'],
            waiting = e.properties['waiting'],
            desk = room:object_index(e.properties['desk']),
            worker = wid,
        }
    end

    local s_data = {}
    for _, e in ipairs(owned) do
        local sid = nil
        if e.properties['student'] ~= nil then
            sid = e.properties['student'].id
        end
        s_data[e.id] = {
            stand_delay = e.properties['stand_delay'],
            student = sid,
            desk = room:object_index(e.properties['desk']),
        }
    end

    local o_data = {}
    for idx, e in ipairs(objects) do
        if e.key == "base:desk_two_chair" then
            local wid = nil
            if e.properties['worker'] ~= nil then
                wid = e.properties['worker'].id
            end
            o_data[idx] = {
                worker = wid,
            }
        else
            o_data[idx] = {}
        end
    end

    return {
        plants = plants.save_plants(room),
        visitors = v_data,
        objects = o_data,
        staff = s_data,
    }
end

function load(room, data)
    plants.load_plants(room, data.plants)
    local visitors = room.visitors
    local owned = room.owned
    local objects = room.objects

    for _, e in ipairs(visitors) do
        local d = data.visitors[e.id]
        e.properties['stand_delay'] = d.stand_delay
        e.properties['wait_time'] = d.wait_time
        e.properties['waiting'] = d.waiting
        if d.desk ~= nil then
            e.properties['desk'] = objects[d.desk]
        end
        if d.worker ~= nil then
            e.properties['worker'] = get_entity_by_id(d.worker)
        end
    end
    for _, e in ipairs(owned) do
        local d = data.staff[e.id]
        e.properties['stand_delay'] = d.stand_delay
        if d.student ~= nil then
            e.properties['student'] = get_entity_by_id(d.student)
        end
        if d.desk ~= nil then
            e.properties['desk'] = objects[d.desk]
        end
    end

    for idx, e in ipairs(objects) do
        if e.key == "base:desk_two_chair" then
            local d = data.objects[idx]
            if d.worker ~= nil then
                e.properties['worker'] = get_entity_by_id(d.worker)
            end
        end
    end
end

local tooltip_data = serialize.define({
    {"staff", "u16"},
    {"max_staff", "u16"},
})
function encode_tooltip(room)
    local owned = room.owned
    local objects = room.objects
    local max_staff = 0
    local num_staff = 0
    for _, e in ipairs(objects) do
        if e.key == "base:desk_two_chair" then
            max_staff = max_staff + 1
        end
    end
    for _, e in ipairs(owned) do
        if e.key == 'base:office_worker' then
            num_staff = num_staff + 1
        end
    end

    return tooltip_data.encode({
        staff = num_staff,
        max_staff = max_staff,
    })
end
function decode_tooltip(data)
    local data = tooltip_data.decode(data)
    local content = ui.new("content")
    local msg = ui.new_text("Registration Office\n")
    msg:set_property_bool("title", true)
    content:add_child(msg)

    local msg = ui.new_text("Staff: ")
    content:add_child(msg)
    local msg = ui.new_text(tostring(data.staff) .. "/" .. tostring(data.max_staff))
    msg:set_property_bool("highlight", true)
    content:add_child(msg)
    return content
end

function force_entity_release(room, e)
    local desk = e.properties['desk']
    if desk ~= nil and desk.valid then
        desk.properties['worker'] = nil
        e.properties['desk'] = nil
        e.properties['at_desk'] = false
    end
end

function server(room)
    local owned = room.owned
    local visitors = room.visitors
    local objects = room.objects
    local ox, oy = room:get_position()
    local width, height = room:get_size()

    plants.tick_plants(room)

    local max_staff = 0
    local num_staff = 0
    for _, e in ipairs(objects) do
        if e.key == "base:desk_two_chair" then
            max_staff = max_staff + 1
        end
    end
    for _, e in ipairs(owned) do
        if e.key == 'base:office_worker' then
            num_staff = num_staff + 1
        end
    end

    -- Send students to sit at desks
    local has_waiting_students = false
    for _, e in ipairs(visitors) do
        local data = {}
        if e.should_release then
            if sit.try_stand(e, data) then
                e:release()
            else
                e.state = student_data.encode(data)
            end
        elseif not e.properties['desk'] then
            -- Find an office worker to go to
            local worker
            for _, e in ipairs(owned) do
                if not e.is_walking
                        and e.key == 'base:office_worker'
                        and not(e.properties['student'] and e.properties['student'].valid)
                        and e.properties['desk']
                        and e.properties['at_desk']
                        and not e.should_release then
                    worker = e
                    break
                end
            end
            if worker ~= nil then
                local desk = worker.properties['desk']
                e.properties['desk'] = desk
                e.properties['worker'] = worker
                e.properties['wait_time'] = 120
                worker.properties['student'] = e
                local desk_e = desk.entities[1]
                local dx, dy = desk_e:get_position()
                local rotation = desk_e.rotation + math.pi
                e:walk_to_face(dx + 0.3 * math.sin(rotation), dy + 0.3 * math.cos(rotation), rotation)
            else
                has_waiting_students = true
                if num_staff < max_staff then
                    room:request_entity('base:office_worker')
                    num_staff = num_staff + 1
                end
                if not (e.properties['waiting'] or false) then
                    e.properties['waiting'] = true
                    local tiles = room.capacity / 3
                    local tile = math.random(tiles)
                    local idx = 1
                    local found = false
                    for x = 0, width - 1 do
                        for y = 0, height - 1 do
                            local t = level.get_tile(ox + x, oy + y)
                            if t:get_name() == "base:waiting_area" then
                                if idx >= tile then
                                    e:walk_to_face(
                                        x + math.random() * 0.5 + 0.25,
                                        y + math.random() * 0.5 + 0.25,
                                        math.random() * math.pi * 2
                                    )
                                    found = true
                                    break
                                end
                                idx = idx + 1
                            end
                        end
                        if found then
                            break
                        end
                    end
                end
            end
        elseif not e.is_walking then
            e.properties['wait_time'] = e.properties['wait_time'] - 1
            if not e.properties['sitting'] then
                sit.make_sit(e, data)
                e.state = student_data.encode(data)
            end
            if e.properties['wait_time'] <= 0 then
                -- Charge a signup fee
                -- TODO: Allow customizing
                if sit.try_stand(e, data) then
                    local cost, err = e:generate_time_table()
                    local worker = e.properties['worker']
                    worker.properties['student'] = nil
                    e.properties['desk'] = nil
                    if err then
                        -- TODO: Let the player know
                        if err ~= "No courses" then
                            print(err)
                        end
                        e:quit()
                    else
                        if worker.valid then
                            local toggle = not (worker.properties['sale_toggle'] or false)
                            local sdata = worker.properties['data']
                            sdata.sale_toggle = toggle
                            worker.state = staff_data.encode(sdata)
                            worker.properties['sale_toggle'] = toggle
                        end
                        e:charge("Course signup fee", cost)
                        e:release()
                    end
                else
                    e.state = student_data.encode(data)
                end
            end
        end
    end

    -- Place staff at an empty desk
    for _, e in ipairs(owned) do
        e.properties['data'] = e.properties['data'] or {
            sale_toggle = false,
            sit = false
        }
        if e.should_release then
            local desk = e.properties['desk']
            if desk ~= nil and desk.valid then
                desk.properties['worker'] = nil
                e.properties['desk'] = nil
                e.properties['at_desk'] = false
            end
            if sit.try_stand(e, e.properties['data']) then
                e:release()
            else
                e.state = staff_data.encode(e.properties['data'])
            end
        elseif e.key == 'base:office_worker' then
            if not e.properties['desk'] then
                -- Find an empty desk
                local desk
                for _, e in ipairs(objects) do
                    if e.key == "base:desk_two_chair" and (not e.properties['worker'] or not e.properties['worker'].valid) then
                        desk = e
                        break
                    end
                end
                if desk ~= nil then
                    e.properties['desk'] = desk
                    e.properties['student'] = nil
                    desk.properties['worker'] = e
                else
                    e.active = false
                end
            end
            if e.properties['desk'] then
                if not e.is_walking then
                    local x, y = e:get_position()
                    local desk = e.properties['desk']
                    local desk_e = desk.entities[1]
                    local dx, dy = desk_e:get_position()
                    local rotation = desk_e.rotation
                    local tx, ty = dx + 0.3 * math.sin(rotation), dy + 0.3 * math.cos(rotation)
                    if math.abs(x - tx) > 0.1 or math.abs(y - ty) > 0.1 then
                        if e.properties.tried_walk ~= desk then
                            -- Try to sit on the target desk
                            e.properties.tried_walk = desk
                            e:walk_to_face(tx, ty, rotation)
                        else
                            if not e.properties.warned_stuck then
                                e.properties.warned_stuck = true
                                room:notify_player("room.common#notify_stuck_staff", common.notify_data.encode({
                                    room_id = room.room_id,
                                }))
                            end
                        end
                        e.properties['at_desk'] = false
                        e.active = true
                    else
                        sit.make_sit(e, e.properties['data'])
                        e.state = staff_data.encode(e.properties['data'])
                        e.properties['at_desk'] = true
                        e.active = (e.properties['student'] and e.properties['student'].valid) or has_waiting_students
                    end
                end
            else
                e.properties['at_desk'] = false
                e:force_release()
            end
        else
            e:force_release()
        end
    end
end

function client(room, entities)
    for _, e in ipairs(entities) do
        if e.key == 'base:office_worker' then
            if e.state ~= nil then
                local data = staff_data.decode(e.state)
                if data.sale_toggle ~= (e.properties['sale_toggle'] or false) then
                    local rx, ry = room:get_position()
                    local x, y = e:get_position()
                    audio.play_sound_at("coin", rx + x, ry + y)
                    e.properties['sale_toggle'] = data.sale_toggle
                end
                sit.client_sit_tick(e, data)
            end
        else
            if e.state ~= nil then
                e.properties['sit_animation'] = "sit_and_chat"
                local data = student_data.decode(e.state)
                sit.client_sit_tick(e, data)
            end
        end
    end
    plants.tick_plants_client(room)
end

function on_exec(room, method, data)
    plants.on_exec(room, method, data)
end