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

encode_tooltip = common.encode_tooltip
decode_tooltip = common.decode_tooltip

local VALID_TRACK_DIRS = {
    ["base:track/straight"] = {
        {x = 0, y = -1},
        {x = 0, y = 1},
    },
    ["base:track/straight_h"] = {
        {x = -1, y = 0},
        {x = 1, y = 0},
    },
    ["base:track/corner_1"] = {
        {x = 1, y = 0},
        {x = 0, y = -1},
    },
    ["base:track/corner_2"] = {
        {x = -1, y = 0},
        {x = 0, y = -1},
    },
    ["base:track/corner_3"] = {
        {x = -1, y = 0},
        {x = 0, y = 1},
    },
    ["base:track/corner_4"] = {
        {x = 1, y = 0},
        {x = 0, y = 1},
    },
}

function update(room)
    local width, height = room:get_size()
    local visited = {}
    for x = 1, width do
        row = {}
        visited[x] = row
        for y = 1, height do
            row[y] = nil
        end
    end
    local current_track = 1
    local tiles = 0
    local tracks = {}

    local ox, oy = room:get_position()
    for x = 1, width do
        for y = 1, height do
            local tile = level.get_tile(ox + x - 1, oy + y - 1)
            local name = tile:get_name()
            if visited[x][y] == nil and VALID_TRACK_DIRS[name] ~= nil then
                local to_visit = {
                    {x = x, y = y, from = nil}
                }
                local track = {
                    tiles = {},
                    start_delay = 0,
                }
                tracks[current_track] = track
                while #to_visit > 0 do
                    local loc = table.remove(to_visit)
                    local tile = level.get_tile(ox + loc.x - 1, oy + loc.y - 1)
                    local name = tile:get_name()
                    local dirs = VALID_TRACK_DIRS[name]
                    if dirs ~= nil and visited[loc.x][loc.y] == nil then
                        local ok = true
                        if loc.from ~= nil then
                            ok = false
                            for _, dir in ipairs(dirs) do
                                -- Make sure we can enter from this direction
                                if dir.x == -loc.from.x and dir.y == -loc.from.y then
                                    ok = true
                                end
                            end
                        end
                        if ok then
                            visited[loc.x][loc.y] = current_track
                            table.insert(track.tiles, {
                                x = loc.x,
                                y = loc.y,
                            })
                            tiles = tiles + 1
                            for _, dir in ipairs(dirs) do
                                table.insert(to_visit, {
                                    x = loc.x + dir.x,
                                    y = loc.y + dir.y,
                                    from = dir,
                                })
                            end
                        end
                    end
                end

                current_track = current_track + 1
            end
        end
    end
    room.properties.tracks = tracks

    room.capacity = math.ceil(tiles / 2)

    common.update(room)
end

function save(room)
    local visitors = room.visitors

    local v_data = {}
    for _, e in ipairs(visitors) do
        v_data[e.id] = {
            common = common.save_student(e),
        }
    end
    return {
        plants = plants.save_plants(room),
        visitors = v_data,
    }
end

function load(room, data)
    plants.load_plants(room, data.plants)
    local visitors = room.visitors
    for _, e in ipairs(visitors) do
        local d = data.visitors[e.id]
        common.load_student(e, d.common)
    end
end

force_entity_release = common.force_entity_release

function server(room)
    local visitors = room.visitors

    plants.tick_plants(room)

    for _, t in ipairs(room.properties.tracks) do
        if t.start_delay > 0 then
            t.start_delay = t.start_delay - 1
        end
    end

    local num_active = 0
    local ox, oy = room:get_position()
    for _, e in ipairs(visitors) do
        if e.should_release then
            -- TODO: Allow customizing
            common.release_student(room, e, 400, 1.2)
        else
            common.tick_student(room, e)
            num_active = num_active + 1
            if not e.is_walking then
                -- If they aren't currently on a track put them
                -- on one
                if e.properties.track == nil then
                    e.properties.track = math.random(#room.properties.tracks)
                    local track = room.properties.tracks[e.properties.track]
                elseif not e.properties.on_track then
                    local track = room.properties.tracks[e.properties.track]
                    -- If free, take the spot
                    if track.start_delay <= 0 then
                        track.start_delay = 20 * 2 -- 2 Seconds
                        e:walk_to(track.tiles[1].x + 0.5 - 1.0, track.tiles[1].y + 0.5 - 1.0)
                        e.properties.on_track = true
                    end
                else
                    local x, y = e:get_position()
                    local tile = level.get_tile(ox + x, oy + y)
                    local name = tile:get_name()
                    local dirs = VALID_TRACK_DIRS[name]
                    if dirs == nil then
                        e.properties.on_track = false
                    else
                        local move_dir
                        local last_dir = e.properties.last_dir
                        if last_dir == nil then
                            -- Just take the first dir
                            move_dir = dirs[1]
                        else
                            for _, dir in ipairs(dirs) do
                                if dir.x ~= -last_dir.x and dir.y ~= -last_dir.y then
                                    move_dir = dir
                                    break
                                end
                            end
                        end
                        if move_dir == nil then
                            move_dir = {x = -last_dir.x, y = -last_dir.y}
                        end
                        local move_x = math.floor(x)
                        local move_y = math.floor(y)
                        e.properties.last_dir = move_dir
                        while true do
                            if room:owns_tile(move_x + move_dir.x, move_y + move_dir.y) then
                                local tile = level.get_tile(ox + move_x + move_dir.x, oy + move_y + move_dir.y)
                                local name = tile:get_name()
                                local dirs = VALID_TRACK_DIRS[name]

                                local ok_enter = false
                                local ok_leave = false
                                if dirs ~= nil then
                                    for _, dir in ipairs(dirs) do
                                        -- Make sure we can enter from this direction
                                        if dir.x == -move_dir.x and dir.y == -move_dir.y then
                                            ok_enter = true
                                        end
                                        -- Make sure we can leave from the current direction
                                        if dir.x == move_dir.x and dir.y == move_dir.y then
                                            ok_leave = true
                                        end
                                    end
                                end
                                if not ok_enter then
                                    break
                                else
                                    move_x = move_x + move_dir.x
                                    move_y = move_y + move_dir.y
                                    if not ok_leave then
                                        break
                                    end
                                end
                            else
                                break
                            end
                        end
                        e:walk_to(move_x + 0.5, move_y + 0.5)
                    end
                end
            end
        end
    end

    for _, e in ipairs(room.owned) do
        local x, y = e:get_position()
        if not e.is_walking and (math.abs(x - 1) > 0.5 or math.abs(y - 1) > 0.5) then
            e:walk_to(1, 1)
        end
    end

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

    local has_prof = false
    for i = #room.owned, 1, -1 do
        local e = room.owned[i]
        if e.valid then
            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
    end
end

function client(room, entities)
    common.tick_staff_client(room, entities)
    plants.tick_plants_client(room)
end

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