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

local student_data = serialize.define({
    {"room", "i16"},
    {"sit", "bool"}
})
local food_data = serialize.define({
    {"object_idx", "u14"},
    {"seat", "u2"},
    -- 0 == Remove the food
    {"food_ty", "u4"},
})
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 width, height = room:get_size()
    local objects = room.objects
    local num_chairs = sit.seat_capacity(objects)

    room.capacity = num_chairs
    room.properties.can_eat_at = true
end

function save_idle(room, e)
    return {
        stand_delay = e.properties['stand_delay'],
        seat = room:object_index(e.properties['seat']),
        seat_pos = e.properties['seat_pos'],
    }
end
function save(room)
    return {
        plants = plants.save_plants(room),
        seats = sit.save_seats(room),
    }
end

function load_idle(room, e, d)
    e.properties['stand_delay'] = d.stand_delay
    if d.seat ~= nil and d.seat_pos ~= nil then
        e.properties['seat'] = room.objects[d.seat]
        e.properties['seat_pos'] = d.seat_pos
    end
end
function load(room, data)
    plants.load_plants(room, data.plants)
    sit.load_seats(room, data.seats)
end

function update_food(room, obj, seat, food)
    room:execute_command("set_food", food_data.encode({
        object_idx = obj.id,
        seat = seat,
        food_ty = food,
    }))
end

function force_entity_release(room, e)
    if e.properties.table ~= nil then
        e.properties.table.properties.waited_on = false
    end
end

function server(room)
    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/cafe_table" then
            max_staff = max_staff + 1
        end
    end
    for _, e in ipairs(room.owned) do
        if e.key == 'base:office_worker' then
            num_staff = num_staff + 1
        end
    end

    local required_staff = math.ceil(#visitors / 12)
    -- 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()
    for _, e in ipairs(visitors) do
        local data = {room = room.room_id}
        if e.properties.do_release or e.should_release then
            if e.properties.has_food then
                local seat = e.properties.seat
                if seat ~= nil then
                    update_food(room, seat, e.properties.seat_pos, 0)
                    seat.properties.num_with_food = seat.properties.num_with_food - 1
                    seat.properties.has_food[e.properties.seat_pos] = false
                end
                e.properties.has_food = false
            end
            if e.properties.at_table then
                local seat = e.properties.seat
                if seat ~= nil then
                    seat.properties.food_target[e.properties.seat_pos] = nil
                    seat.properties.table_users = seat.properties.table_users - 1
                end
                e.properties.at_table = false
            end
            sit.release_seat(e)
            if sit.try_stand(e, data) then
                e.properties.release = true
            else
                e.state = student_data.encode(data)
            end
        else
            if not e.is_walking then
                local state = sit.try_find_and_sit(e, data, objects, {x=rx,y=ry})
                if state == sit.SITTING then
                    if not e.properties.at_table then
                        e.properties.at_table = true
                        local target = e.properties.seat.properties.food_target or {}
                        target[e.properties.seat_pos] = e
                        e.properties.seat.properties.food_target = target
                        e.properties.seat.properties.table_users = (e.properties.seat.properties.table_users or 0) + 1

                        e.properties.sit_time = math.random(20, 60) * 20
                    end
                    e.properties.sit_time = (e.properties.sit_time or 0) - 1
                    e.state = student_data.encode(data)
                    e:change_stat("happiness", 0.002 / 20.0)
                    if e.properties.sit_time < 0 then
                        e.properties.do_release = true
                    end
                elseif state == sit.NO_SEAT then
                    e.properties.do_release = true
                end
            end
        end
    end


    --- Find a random table with people waiting for food.
    --- Go to each person missing food on the table and wait a few ticks
    --- @ Make the food appear instantly? Could fetch but that may be a later thing
    --- @ When the student leaves, remove the food

    for idx, e in ipairs(room.owned) do
        local x, y = e:get_position()
        if e.should_release then
            if e.properties.table ~= nil then
                e.properties.table.properties.waited_on = false
            end
            e:release()
        elseif not e.is_walking then
            if not e.properties.serving or not e.properties.table.valid then
                -- Try and find a table to serve
                local table = nil
                for _, obj in ipairs(objects) do
                    local props = object_info.get_properties(obj.key)
                    if props.can_sit and not obj.properties.waited_on and (obj.properties.num_with_food or 0) < (obj.properties.table_users or 0) then
                        table = obj
                        break
                    end
                end

                if table ~= nil then
                    e.properties.serving = true
                    e.properties.table = table
                    e.properties.serve_target = 1
                    table.properties.waited_on = true
                else
                    if math.abs(x - 1) > 0.5 or math.abs(y - 1) > 0.5 then
                        e:walk_to(1, 1)
                    end
                end
            else
                -- Serve the table
                local table = e.properties.table
                local target = e.properties.serve_target
                while target <= 4 do
                    if (table.properties.has_food or {})[target] or (table.properties.food_target or {})[target] == nil then
                        target = target + 1
                    else
                        break
                    end
                end
                if target > 4 then
                    table.properties.waited_on = false
                    e.properties.serve_target = 1
                    e.properties.serving = false
                    e.properties.table = nil
                else
                    e.properties.serve_target = target

                    local ox, oy = table.entities[1]:get_position()

                    local rotation = (target - 1) * math.pi * 0.5 + math.pi * 0.25
                    local s, c = math.sin(rotation), math.cos(rotation)
                    local tx = ox + 0.41 * s
                    local ty = oy + 0.41 * c

                    if math.abs(x - tx) > 0.5 or math.abs(y - ty) > 0.5 then
                        e:walk_to_face(tx, ty, rotation + math.pi * 0.25)
                    else
                        e.properties.serve_wait = (e.properties.serve_wait or 0) + 1
                        if e.properties.serve_wait > (e.properties.serve_wait_target or 40) then
                            e.properties.serve_target = e.properties.serve_target + 1
                            e.properties.serve_wait = 0
                            e.properties.serve_wait_target = math.random(35, 60)
                            local has_food = table.properties.has_food or {}
                            has_food[target] = true
                            table.properties.has_food = has_food
                            table.properties.num_with_food = (table.properties.num_with_food or 0) + 1
                            local student = table.properties.food_target[target]
                            if student ~= nil and student.valid then
                                update_food(room, table, target, math.random(1, 2))
                                student.properties.has_food = true

                                local toggle = not (e.properties['sale_toggle'] or false)
                                e.state = staff_data.encode({
                                    sale_toggle = toggle
                                })
                                e.properties['sale_toggle'] = toggle

                                student:charge("Food", 50)
                                student:change_stat("hunger", 1.0)
                                student:change_stat("happiness", 0.7)
                            end
                        end
                    end
                end
            end
        end

    end
end

function client_idle(room, e)
    if e.state ~= nil then
        local data = student_data.decode(e.state)
        sit.client_sit_tick(e, data)
    end
end
function client(room, entities)
    for _, e in ipairs(entities) do
        if e.state ~= nil then
            if e.key == 'base:office_worker' 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)
    if is_client and method == "set_food" then
        local info = food_data.decode(data)
        local obj = room.objects[info.object_idx]
        if obj == nil then
            return
        end
        local food_info = obj.properties.food_info or {}

        if food_info[info.seat] ~= nil then
            food_info[info.seat]:remove()
        end

        if info.food_ty == 0 then
            return
        end

        local ox, oy = obj.entities[1]:get_position()

        local rotation = (info.seat - 1) * math.pi * 0.5
        local s, c = math.sin(rotation), math.cos(rotation)
        local x = 0.20 * s
        local y = 0.20 * c

        local food = "base:shop/food_steak"
        if info.food_ty == 2 then
            food = "base:shop/food_salad"
        end

        local e = room:create_static_entity(
            food,
            ox + x, 0.425, oy + y
        )
        e:rotate(math.random(0, 3) * math.pi * 0.5, 0)
        food_info[info.seat] = e

        obj.properties.food_info = food_info
    end
end