local equeue = require "util.entity_queue"
local plants = require 'objects.plants'

student_data = serialize.define({
    {"room", "i16"},
    {"ordering", "bool"}
})
local staff_data = serialize.define({
    {"sale_toggle", "bool"}
})

local common = require 'room.common'
encode_tooltip = common.encode_tooltip_def
decode_tooltip = common.decode_tooltip_def

function update(room)
    local objects = room.objects
    local num_counters = 0
    for _, c in ipairs(objects) do
        if c.key == "base:shop/counter_food" then
            num_counters = num_counters + 1
        end
    end
    room.capacity = num_counters * 4
    room.properties.can_eat_at = true
end

function save_idle(room, e)
    local wid = nil
    if e.properties['worker'] ~= nil then
        wid = e.properties['worker'].id
    end
    return {
        wait_time = e.properties['wait_time'],
        counter = room:object_index(e.properties['counter']),
        worker = wid,
    }
end
function save(room)
    local owned = room.owned
    local objects = room.objects

    local s_data = {}
    for _, e in ipairs(owned) do
        local queue = e.properties['queue'] or {entries={}, length= 0}
        local queue_data = {}
        for idx, e in ipairs(queue.entries) do
            local sid = nil
            if e.properties['worker'] ~= nil then
                sid = e.id
            end
            queue_data[idx] = sid
        end
        s_data[e.id] = {
            queue = queue_data,
            at_counter = e.properties['at_counter'],
            handling_student = e.properties['handling_student'],
            counter = room:object_index(e.properties['counter']),
        }
    end

    local o_data = {}
    for idx, e in ipairs(objects) do
        if e.key ==  "base:shop/counter_food" 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),
        objects = o_data,
        staff = s_data,
    }
end

function load_idle(room, e, d)
    e.properties['wait_time'] = d.wait_time
    if d.counter ~= nil then
        e.properties['counter'] = room.objects[d.counter]
    end
    if d.worker ~= nil then
        e.properties['worker'] = get_entity_by_id(d.worker)
    end
end
function load(room, data)
    plants.load_plants(room, data.plants)
    local owned = room.owned
    local objects = room.objects

    for _, e in ipairs(owned) do
        local d = data.staff[e.id]
        e.properties['at_counter'] = d.at_counter
        e.properties['handling_student'] = d.handling_student
        local queue = e.properties['queue'] or equeue.new()
        for _, ee in ipairs(d.queue) do
            if ee ~= nil then
                queue:add(get_entity_by_id(ee))
            end
        end
        e.properties['queue'] = queue
        if d.counter ~= nil then
            e.properties['counter'] = objects[d.counter]
        end
    end

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

function find_worker(e, owned)
    -- Find an office worker to go to
    local worker
    local max_len = 4
    for _, e in ipairs(owned) do
        local queue = e.properties['queue'] or {length= 0}
        if not e.is_walking
                and e.key == 'base:office_worker'
                and e.properties['at_counter']
                and queue.length < max_len then
            worker = e
            max_len = queue.length
        end
    end
    if worker ~= nil then
        local counter = worker.properties['counter']
        e.properties['counter'] = counter
        e.properties['worker'] = worker
    else
        e.properties['worker'] = nil
        e.properties['counter'] = nil
    end
end

function force_entity_release(room, e)
end

function server(room)
    local owned = room.owned
    local visitors = room.properties.users or {}
    local objects = room.objects

    plants.tick_plants(room)
    for _, e in ipairs(room.visitors) do
        e:release()
    end

    local max_staff = 0
    local num_staff = 0
    for _, e in ipairs(objects) do
        if e.key == "base:shop/counter_food" 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

    local required_staff = math.ceil(#visitors / 4)
    -- Don't request extra staff if the room is overfilled somehow
    if required_staff > max_staff then
        required_staff = max_staff
    end

    for i = num_staff + 1, required_staff do
        if room:request_entity('base:office_worker') ~= nil then
            num_staff = num_staff + 1
        end
    end

    local rx, ry = room:get_position()

    -- Send students to queue at counters
    for idx, e in ipairs(visitors) do
        local skip = false
        if e.properties.do_release or e.should_release then
            -- Don't release if they are getting food.
            -- They'll be done shortly
            if not e.properties['wait_time'] or room.closing then
                local worker = e.properties['worker']
                if worker ~= nil then
                    local queue = worker.properties['queue']
                    if e.properties['queue_pos'] then
                        queue:remove(e)
                        e.properties['queue_pos'] = nil
                    end
                end
                e.properties.release = true
                skip = true
            end
        end
        if not skip then
            if not e.properties['counter'] or not e.properties['counter'].valid
                or not e.properties['worker'] or not e.properties['worker'].valid then
                -- Just entered the room or the counter/worker was removed somehow
                -- Find a new one
                find_worker(e, owned)
                -- Remove from any existing queue
                e.properties['queue_pos'] = nil
            elseif not e.properties['queue_pos'] then
                -- Not queued yet, only queue once they are in the room
                if e.in_room then
                    find_worker(e, owned)
                    if e.properties['worker'] then
                        local worker = e.properties['worker']
                        local queue = worker.properties['queue'] or equeue.new()
                        queue:add(e)
                        worker.properties['queue'] = queue
                    end
                elseif not e.is_walking then
                    local counter = e.properties['counter']
                    local counter_e = counter.entities[1]

                    -- Walk to the end of the queue just to end up inside the room
                    local dx, dy = counter_e:get_position()
                    local rotation = counter_e.rotation + math.pi*0.5
                    local queue_pos = 3 -- Assume the end
                    e:walk_to_face(
                        rx + dx + (0.8 + 0.2 * queue_pos) * math.sin(rotation),
                        ry + dy + (0.8 + 0.2 * queue_pos) * math.cos(rotation),
                        rotation
                    )
                end
            elseif not e.is_walking then
                local queue_pos = e.properties['queue_pos'] - 1
                local counter = e.properties['counter']
                local counter_e = counter.entities[1]

                -- Walk to the current position in the queue
                local dx, dy = counter_e:get_position()
                local rotation = counter_e.rotation + math.pi*0.5
                local target_x = dx + (0.8 + 0.2 * queue_pos) * math.sin(rotation)
                local target_y = dy + (0.8 + 0.2 * queue_pos) * math.cos(rotation)
                local cx, cy = e:get_position()
                local moving = false
                if math.abs(cx - (rx + target_x)) > 0.01 or math.abs(cy - (ry + target_y)) > 0.01 then
                    e:walk_to_face(
                        rx + target_x,
                        ry + target_y,
                        rotation
                    )
                    moving = true
                end

                if e.is_walking or moving then
                    -- Do nothing
                elseif not e.properties['wait_time'] then
                    -- If not waiting at the front do nothing
                    -- otherwise start being served
                    if queue_pos == 0 then
                        e.properties['wait_time'] = 40
                        e.state = student_data.encode({
                            room = room.room_id,
                            ordering = true
                        })
                    end
                else
                    e.properties['wait_time'] = e.properties['wait_time'] - 1
                    if e.properties['wait_time'] == 20 then
                        local worker = e.properties['worker']
                        if worker.valid then
                            local toggle = not (worker.properties['sale_toggle'] or false)
                            worker.state = staff_data.encode({
                                sale_toggle = toggle
                            })
                            worker.properties['sale_toggle'] = toggle
                        end
                        -- Charge for food
                        -- TODO: Allow customizing
                        e:charge("Food", 15)
                        e:change_stat("hunger", 1.0)
                        e:change_stat("happiness", 0.3)

                    end
                    if e.properties['wait_time'] <= 0 then
                        -- Finished being served
                        local worker = e.properties['worker']
                        e.properties['worker'] = nil
                        if worker.valid then
                            local queue = worker.properties['queue']
                            queue:remove(e)
                        end

                        e.state = student_data.encode({
                            room = room.room_id,
                            ordering = false
                        })
                        e.properties.release = true
                    end
                end
            end
        end
    end

    -- Place staff at an empty counter
    for _, e in ipairs(owned) do
        if e.should_release then
            local queue = e.properties['queue'] or {length= 0}
            for i = 1,queue.length do
                if queue.entries[i] ~= nil then
                    queue.entries[i].properties['queue_pos'] = nil
                    queue.entries[i].properties['counter'] = nil
                    queue.entries[i].properties['worker'] = nil
                    queue.entries[i].properties['wait_time'] = nil
                end
                queue.entries[i] = nil
            end
            queue.length = 0

            local counter = e.properties['counter']
            if counter ~= nil then
                counter.properties['worker'] = nil
                e.properties['counter'] = nil
                e.properties['at_counter'] = false
            end
            e:release()
        elseif e.key == 'base:office_worker' then
            local queue = e.properties['queue'] or {entries={}, length= 0}
            if queue.entries[1] ~= nil and not queue.entries[1].valid then
                print("Removing invalid queue entry")
                queue:remove(queue.entries[1])
            end
            if not e.properties['counter'] or not e.properties['counter'].valid then
                -- Find an empty counter
                local counter
                for _, e in ipairs(objects) do
                    if e.key == "base:shop/counter_food" and (not e.properties['worker'] or not e.properties['worker'].valid) then
                        counter = e
                        break
                    end
                end
                if counter ~= nil then
                    e.properties['at_counter'] = true
                    e.properties['counter'] = counter
                    counter.properties['worker'] = e
                else
                    e.properties['at_counter'] = false
                    e.active = false
                    e:force_release()
                end
            end
            if e.properties['at_counter'] or false then
                if not e.is_walking then
                    local x, y = e:get_position()
                    local counter = e.properties['counter']
                    local counter_e = counter.entities[1]
                    local dx, dy = counter_e:get_position()
                    local rotation = counter_e.rotation + math.pi + math.pi*0.5
                    local tx, ty = dx + 0.5 * math.sin(rotation), dy + 0.5 * math.cos(rotation)
                    if math.abs(x - tx) > 0.1 or math.abs(y - ty) > 0.1 then
                        if e.properties.tried_walk ~= counter then
                            -- Try to sit on the target counter
                            e.properties.tried_walk = counter
                            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
                    else
                        e.active = (e.properties['queue'] or {length= 0}).length >= 1
                    end
                end
            end
        else
            e:force_release()
        end
    end
end

function client_idle(room, e)
    local rx, ry = room:get_position()

    if e.state ~= nil and not e.is_walking then
        local data = student_data.decode(e.state)
        if data.ordering and e.animation ~= "stand_chat" then
            e:play_animation("stand_chat")
        elseif not data.ordering and e.animation == "stand_chat" then
            e:play_animation("idle")
        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
            end
        end
    end
    plants.tick_plants_client(room)
end

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