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

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

encode_tooltip = common.encode_tooltip
decode_tooltip = common.decode_tooltip

function update(room)
    local objects = room.objects
    local num_chairs = 0
    for _, c in ipairs(objects) do
        if c.key:sub(1, ('base:chairs/'):len()) == 'base:chairs/' then
            num_chairs = num_chairs + 1
        end
    end
    room.capacity = num_chairs
    common.update(room)
end

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

    local v_data = {}
    for _, e in ipairs(visitors) do
        v_data[e.id] = {
            stand_delay = e.properties['stand_delay'],
            chair = room:object_index(e.properties['chair']),
            common = common.save_student(e),
        }
    end

    local o_data = {}
    for idx, e in ipairs(objects) do
        local sid = nil
        if e.properties['owned'] ~= nil then
            sid = e.properties['owned'].id
        end
        if e.key:sub(1, ('base:chairs/'):len()) == 'base:chairs/' then
            o_data[idx] = {
                owned = sid,
            }
        else
            o_data[idx] = {}
        end
    end

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

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

    for _, e in ipairs(visitors) do
        local d = data.visitors[e.id]
        e.properties['stand_delay'] = d.stand_delay
        if d.chair ~= nil then
            e.properties['chair'] = objects[d.chair]
        end
        common.load_student(e, d.common)
    end

    for idx, e in ipairs(objects) do
        if e.key:sub(1, ('base:chairs/'):len()) == 'base:chairs/' then
            if data.objects[idx].owned then
                e.properties['owned'] = get_entity_by_id(data.objects[idx].owned)
            end
        end
    end
end

force_entity_release = common.force_entity_release

function server(room)
    local visitors = room.visitors
    local objects = room.objects

    plants.tick_plants(room)

    -- Send students to sit at chairs
    local num_active = 0
    for _, e in ipairs(visitors) do
        local data = {}
        if e.should_release then
            local chair = e.properties['chair']
            if chair ~= nil then
                chair.properties['owned'] = nil
            end
            -- TODO: Allow customizing
            common.release_student(room, e, 300, 1.0)
            if sit.try_stand(e, data) then
                e:release()
            else
                e.state = student_data.encode(data)
            end
        else
            num_active = num_active + 1
            if not e.is_walking then
                local chair = e.properties['chair']
                if chair == nil then
                    util.table_shuffle(objects)
                    for _, c in ipairs(objects) do
                        if (not c.properties['owned'] or not c.properties['owned'].valid) and c.key:sub(1, ('base:chairs/'):len()) == 'base:chairs/' then
                            chair = c
                            break
                        end
                    end
                end
                if chair ~= nil then
                    local x, y = e:get_position()
                    local chair_e = chair.entities[1]
                    local cx, cy = chair_e:get_position()
                    chair.properties['owned'] = e
                    e.properties['chair'] = chair
                    local rotation = chair_e.rotation
                    cx = cx - 0.15 * math.sin(rotation)
                    cy = cy - 0.15 * math.cos(rotation)
                    if math.abs(x - cx) > 0.05 or math.abs(y - cy) > 0.05 then
                        if e.properties.tried_walk ~= chair then
                            -- Try to sit on the target chair
                            e.properties.tried_walk = chair
                            e:walk_to_face(cx, cy, rotation)
                        else
                            -- We've failed to sit once, mark as stuck
                            e.properties.stuck = true
                        end
                    else
                        common.tick_student(room, e)
                        sit.make_sit(e, data)
                        e.state = student_data.encode(data)
                    end
                end
            end
        end
    end

    local has_prof = false
    for i = #room.owned, 1, -1 do
        local e = room.owned[i]
        if e.key == 'base:professor' then
            if not has_prof then
                has_prof = true
            else
                e:force_release()
            end
        else
            e:force_release()
        end
    end

    common.tick_room(room, num_active > 0 or room.has_waiting)
end

function client(room, entities)
    common.tick_staff_client(room, entities)
    local width, height = room:get_size()

    if room.properties['paper_plane'] then
        local plane = room.properties['paper_plane']
        plane.time = plane.time + 1
        plane.move_time = plane.move_time - 1
        if plane.time > plane.max_time and plane.move_time < 0 then
            if not plane.fall_time then
                local px, pz = plane.entity:get_position()
                plane.fall_time = 60
                plane.entity:move(px, -0.3, pz, 60)
            else
                plane.fall_time = plane.fall_time - 1
                if plane.fall_time <= 0 then
                    plane.entity:remove()
                    room.properties['paper_plane'] = nil
                end
            end
        else
            if plane.move_time <= 0 then
                local tx = math.random(0, (width * 16) - 24) / 16 + 0.5
                local tz = math.random(0, (height * 16) - 24) / 16 + 0.5

                local px, pz = plane.entity:get_position()
                local dx = px - tx
                local dz = pz - tz

                local time = math.sqrt(dx*dx + dz*dz) * math.random(0.6, 1.5) * 25.0

                local seg_time = math.random(20, 30)
                local segments = time / seg_time

                plane.movement = {
                    time = 0,
                    seg_time = seg_time,
                    segments = segments,
                    target_x = tx,
                    target_z = tz,
                    start_x = px,
                    start_z = pz,
                    segment = -1,
                }
                plane.move_time = time
                plane.entity:rotate(math.atan2(dx, dz), 5.0)
            end

            plane.movement.time = plane.movement.time + 1
            local segment = math.floor(plane.movement.time / plane.movement.seg_time) + 1
            if segment >= plane.movement.segments then
                segment = plane.movement.segments
            end
            if plane.movement.segment ~= segment then
                local dx = plane.movement.target_x - plane.movement.start_x
                local dz = plane.movement.target_z - plane.movement.start_z
                plane.entity:move(
                    plane.movement.start_x + (dx / plane.movement.segments) * segment,
                    math.random(0.8, 1.0),
                    plane.movement.start_z + (dz / plane.movement.segments) * segment,
                    plane.movement.seg_time
                )
                plane.movement.segment = segment
            end
        end
    end

    for _, e in ipairs(entities) do
        if e.state ~= nil and e.key == "base:student" then
            local data = student_data.decode(e.state)
            sit.client_sit_tick(e, data)

            if not room.properties['paper_plane'] and math.random(500000) == 1 and data.sit then
                local x, z = e:get_position()
                local plane = room:create_static_entity("base:silly/paper_plane", x, 0.5, z)
                plane:rotate(e.rotation, 0)

                room.properties['paper_plane'] = {
                    time = 0,
                    max_time = math.random(20 * 20, 60 * 20),
                    move_time = 0,
                    entity = plane
                }
            end
        end
    end
    plants.tick_plants_client(room)
end

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