-- Missions don't reload that well
NO_RELOAD = true

-- Decode the header first to work out which sub
-- type to use
local tutorial_header = serialize.define({
    {"stage", "u8"}
})
-- local tutorial_begin = serialize.define({
--     {"stage", "u8"},
-- })
local STAGE_UNSET = -1
local STAGE_BEGIN = 0
local STAGE_RESIZE = 1
local STAGE_BUILD = 2
local STAGE_STAFF = 3
local STAGE_FREE = 4
local STAGE_DONE = 5

local wanted_rooms = {
    {key = "base:lecture_room", name = "Lecture Room"},
    {key = "base:science_lab", name = "Science Laboratory"},
    {key = "base:staff_room", name = "Staff Room"},
    {key = "base:toilets", name = "Toilets"}
}

if not is_client then
    local player_state = {}

    -- Called once when the mission starts or loads
    --
    -- If the first time starting prev will be set to
    -- nil, otherwise it will contain the table returned
    -- from `save` previously
    function server_init(prev)
        for _, p in ipairs(control.get_players()) do
            player_state[p] = {
                stage = STAGE_BEGIN,
                rooms = {},
            }
            for _, v in ipairs(wanted_rooms) do
                player_state[p].rooms[v.key] = 0
            end
        end
        if prev then
            player_state = prev.player_state or player_state
        end
        for _, p in ipairs(control.get_players()) do
            send_stage(p)
        end
    end

    function send_stage(player)
        local data
        local state = player_state[player]
        data = tutorial_header.encode({
            stage = state.stage,
        })
        control.submit_command(control.cmd.exec_mission(data))
    end

    -- Called to save the state of the mission
    function save()
        return {
            player_state = player_state,
        }
    end

    --[[
    local place = control.cmd.place_selection("base:building", 0, 0, 30, 30)
    local s_place = control.cmd.sudo(control.get_players()[1], place)
    control.submit_command(s_place)

    `submit_command` will execute commands as the server player (a special marker).
    `cmd.sudo` will run the nested command as the target player but with higher
    powers in some cases (e.g. no money cost). All clients will run the same command
    so that they can stay in sync

    local data = mission_data.serialize({ .. })
    local client_self = control.cmd.exec_mission(data)
    control.submit_command(client_self)

    `exec_mission` will call the `on_exec` handler for the mission with the
    serialized data passed in. The server and every client will end up running
    the command
    ]]--

    -- Called once a tick (20tps)
    function server_update()
        for _, player in ipairs(control.get_players()) do
            local state = player_state[player]
            if state.resend then
                state.resend = false
                send_stage(player)
            end
            if state.give_bonus then
                state.give_bonus = false
                control.give_money(player, 100000)
            end
        end
    end

    -- Called on events
    function on_event(evt, player, param)
        print(tostring(evt) .. " for player " .. player)
        local state = player_state[player]
        if evt == "place_selection" then
            if state.stage == STAGE_BEGIN then
                state.stage = STAGE_RESIZE
                state.resend = true
            end
        elseif evt == "finalize_selection" then
            if state.stage == STAGE_RESIZE then
                state.stage = STAGE_BUILD
                state.resend = true
            end
        elseif evt == "finalize_room" then
            if state.stage == STAGE_BUILD then
                state.stage = STAGE_STAFF
                state.resend = true
            elseif state.stage == STAGE_FREE then
                state.rooms[param.key] = state.rooms[param.key] + 1
                local all_done = true
                for _, v in pairs(state.rooms) do
                    if v == 0 then
                        all_done = false
                    end
                end
                if all_done then
                    state.stage = STAGE_DONE
                    state.resend = true
                end
            end
        elseif evt == "finalize_staff_place" then
            if state.stage == STAGE_STAFF then
                state.stage = STAGE_FREE
                state.give_bonus = true
                state.resend = true
            end
        end
    end

else
    local client_stage = STAGE_UNSET
    local stage_state = {}
    local room_progress = {}
    local stage_controller = {
        [STAGE_UNSET] = {
            init = function(state) end,
            update = function(state) end,
            deinit = function(state) end,
        },
        [STAGE_BEGIN] = {
            init = function(state)
                show_popup("Welcome",
[[Welcome to #UniverCity#.
In this tutorial you will learn the basics of managing your
#UniverCity#.
Building *rooms* and hiring *staff*.
To begin click the *Build Room/Building* button that is marked with the arrow.]]
                )

                local action = ui.query()
                    :name("action")
                    :property_string("key", "buy_room")
                    :matches()(nil, nil)

                local arrow = ui.builder(function()
                    return tutorial_arrow {
                        offset_y = -48,
                        animated = "y",
                    }
                end)
                action:add_child(arrow)

                state.sub_stage = 1
                state.arrow = arrow
            end,
            update = function(state)
                if state.sub_stage == 1 then
                    -- Waiting for build room window

                    local buy_room = ui.query()
                        :name("window")
                        :property_string("key", "buy_rooms")
                        :matches()(nil, nil)

                    if buy_room ~= nil then
                        state.sub_stage = 2
                        state.arrow.parent:remove_child(state.arrow)
                        state.arrow = nil
                        show_popup("Select a room",
[[A good first *room* to build is a #Registration Office#.
Select it in the list and press #Build#.]]
                        )

                        for btn in buy_room:query():name("button"):matches() do
                            local key = btn:get_property_string("key")
                            if key ~= nil and key ~= "base:registration_office" then
                                btn:set_property_bool("disabled", true)
                            elseif key ~= nil or btn:get_property_string("id") == "accept" then
                                local arrow = ui.builder(function()
                                    return tutorial_arrow {
                                        offset_y = -48,
                                        animated = "y",
                                    }
                                end)
                                btn:add_child(arrow)
                            end
                        end
                    end
                elseif state.sub_stage == 2 then
                    -- Waiting selection window
                    local select_area = ui.query()
                        :name("window")
                        :property_string("key", "select_area")
                        :matches()(nil, nil)

                    if select_area ~= nil then
                        state.sub_stage = 3
                        show_popup("Mark an area",
[[You can now mark an area for the selected *room* to be placed.
To select an area click when your cursor is over where you want the first corner to be, then drag until the *room* is the right size.
The area will turn completely blue if its the right size and not blocked.
Markers have been placed in an example area but anywhere will do!
]]
                        )

                        local rooms = control.rooms_for_player(control.get_player())
                        local building
                        for _, rm in ipairs(rooms) do
                            if rm.key == "base:building" then
                                building = rm
                                break
                            end
                        end

                        if not building then
                            error("Failed to finding building for tutorial")
                        end

                        local min_x, min_y, width, height = building:get_bounds()

                        local marker = control.create_static_entity(
                            "base:tutorial/building_marker",
                            min_x + 5.5,
                            0.0,
                            min_y + math.floor(height / 2) + 5.5
                        )
                        marker:rotate(math.pi * 0.5, 0.0)

                        local marker2 = control.create_static_entity(
                            "base:tutorial/building_marker",
                            min_x + 1.5,
                            0.0,
                            min_y + math.floor(height / 2) + 5.5
                        )
                        marker2:rotate(0, 0.0)

                        local marker3 = control.create_static_entity(
                            "base:tutorial/building_marker",
                            min_x + 1.5,
                            0.0,
                            min_y + math.floor(height / 2) + 1.5
                        )
                        marker3:rotate(-math.pi * 0.5, 0.0)

                        local marker4 = control.create_static_entity(
                            "base:tutorial/building_marker",
                            min_x + 5.5,
                            0.0,
                            min_y + math.floor(height / 2) + 1.5
                        )
                        marker4:rotate(math.pi, 0.0)

                        local markers = {marker, marker2, marker3, marker4}

                        for i = 1, 3 do
                            local edge = control.create_static_entity(
                                "base:tutorial/building_marker_edge",
                                min_x + 1.5 + i,
                                0.0,
                                min_y + math.floor(height / 2) + 1.5
                            )
                            edge:rotate(math.pi * 1.5, 0.0)
                            table.insert(markers, edge)
                            edge = control.create_static_entity(
                                "base:tutorial/building_marker_edge",
                                min_x + 1.5 + i,
                                0.0,
                                min_y + math.floor(height / 2) + 5.5
                            )
                            edge:rotate(math.pi * 0.5, 0.0)
                            table.insert(markers, edge)

                            edge = control.create_static_entity(
                                "base:tutorial/building_marker_edge",
                                min_x + 1.5,
                                0.0,
                                min_y + math.floor(height / 2) + 1.5 + i
                            )
                            table.insert(markers, edge)
                            edge = control.create_static_entity(
                                "base:tutorial/building_marker_edge",
                                min_x + 5.5,
                                0.0,
                                min_y + math.floor(height / 2) + 1.5 + i
                            )
                            edge:rotate(math.pi, 0.0)
                            table.insert(markers, edge)
                        end

                        state.markers = markers
                    end
                end
            end,
            deinit = function(state)
                hide_popup()
                if state.markers ~= nil then
                    for _, e in ipairs(state.markers) do
                        e:remove()
                    end
                end
            end,
        },
        [STAGE_RESIZE] = {
            init = function()
                show_popup("Resizing",
[[You can now resize the *room* if you are not happy with the current size.

Once you are happy click the #Accept# button.]]
                )
            end,
            update = function()
            end,
            deinit = function()
                hide_popup()
            end,
        },
        [STAGE_BUILD] = {
            init = function(state)
                show_popup("Building Objects",
[[At this point you can place *objects* in the *room*. There are certain *objects* that must be placed in the *room* before it can be built. The objects will differ between *rooms*.

Use the #Show Required# button to shorten the list to only the *objects* you need.]]
                )


                local btn = ui.query()
                    :name("button")
                    :property_string("id", "show_required")
                    :matches()(nil, nil)

                local arrow = ui.builder(function()
                    return tutorial_arrow {
                        offset_y = -48,
                        animated = "y",
                    }
                end)
                btn:add_child(arrow)

                state.sub_stage = 1
                state.arrow = arrow
            end,
            update = function(state)
                if state.sub_stage == 1 then
                    local btn = ui.query()
                        :name("button")
                        :property_string("id", "accept")
                        :property_bool("disabled", false)
                        :matches()(nil, nil)
                    if btn ~= nil then
                        state.sub_stage = 2
                        if state.arrow ~= nil then
                            state.arrow.parent:remove_child(state.arrow)
                        end
                        local arrow = ui.builder(function()
                            return tutorial_arrow {
                                offset_y = -48,
                                animated = "y",
                            }
                        end)
                        btn:add_child(arrow)
                        state.arrow = arrow

                        show_popup("Building Objects",
[[You now have all the required objects for this *room*. Click #Finish# to place the room.

Feel free to place more if you wish.]]
                        )
                    end
                end
            end,
            deinit = function(state)
                hide_popup()
            end,
        },
        [STAGE_STAFF] = {
            init = function(state)
                show_popup("Hiring Staff",
[[The next job is to hiring someone to work in the office.
Press the #Hire Staff# button to begin.]]
                )

                local action = ui.query()
                    :name("action")
                    :property_string("key", "buy_staff")
                    :matches()(nil, nil)

                local arrow = ui.builder(function()
                    return tutorial_arrow {
                        offset_y = -48,
                        animated = "y",
                    }
                end)
                action:add_child(arrow)

                state.sub_stage = 1
                state.arrow = arrow
            end,
            update = function(state)
                if state.sub_stage == 1 then
                    -- Waiting for build room window

                    local buy_staff = ui.query()
                        :name("window")
                        :property_string("key", "buy_staff")
                        :matches()(nil, nil)

                    if buy_staff ~= nil then
                        state.sub_stage = 2
                        state.arrow.parent:remove_child(state.arrow)
                        state.arrow = nil
                        show_popup("Select a type",
[[To work in the office you'll need an #Office Worker#.
Select it in the list, then choose who you want to hire and press #Hire#.]]
                        )

                        for btn in buy_staff:query():name("button"):matches() do
                            local name = btn:get_property_string("name")
                            if name ~= nil and name ~= "Office Worker" then
                                btn:set_property_bool("disabled", true)
                            elseif name ~= nil or btn:get_property_string("id") == "hire" then
                                local arrow = ui.builder(function()
                                    return tutorial_arrow {
                                        offset_y = -48,
                                        animated = "y",
                                    }
                                end)
                                btn:add_child(arrow)
                            end
                        end
                    end
                elseif state.sub_stage == 2 then
                    -- Waiting selection window
                    local select_area = ui.query()
                        :name("window")
                        :property_string("key", "place_staff")
                        :matches()(nil, nil)

                    if select_area ~= nil then
                        state.sub_stage = 3
                        show_popup("Placing staff",
[[Now you can choose where to place the hired *staff member*. Placing them directly into the *room* will cause them to start working straight away if they are able to work there.
]]
                        )
                    end
                end
            end,
            deinit = function(state)
                hide_popup()
            end,
        },
        [STAGE_FREE] = {
            init = function(state)
                for _, v in ipairs(wanted_rooms) do
                    room_progress[v.key] = 0
                end
                for _, v in ipairs(control.rooms_for_player(control.get_player())) do
                    if room_progress[v.key] ~= nil then
                        room_progress[v.key] = room_progress[v.key] + 1
                    end
                end
                show_room_progress()
            end,
            update = function(state)

            end,
            deinit = function(state) end,
        },
        [STAGE_DONE] = {
            init = function(state)
                show_popup("Tutorial Complete",
[[This tutorial is now complete. You may either continue building here or move over to the #Free Build# option.]]
                )
                state.timer = 20 * 90
            end,
            update = function(state)
                if state.timer > 0 then
                    state.timer = state.timer - 1
                end
                if state.timer <= 0 then
                    hide_popup()
                end
            end,
            deinit = function(state) end,
        },
    }

    function show_room_progress()
        local msg = "This covers the basics you need to get started with the game. Try building the rooms below and creating courses to get students. You can place objects in the main building by editing it.\n"
        for _, v in ipairs(wanted_rooms) do
            if room_progress[v.key] > 0 then
                msg = msg .. "- *" .. v.name .. "*\n"
            else
                msg = msg .. "- " .. v.name .. "\n"
            end
        end
        show_popup("Build some more", msg)
    end

    local current_popup
    function show_popup(title_str, message_str)
        hide_popup()
        local hud = ui.query()
            :name("fullscreen")
            :property_bool("hud_area", true)
            :matches()(nil, nil)

        local format = require 'ui.format'

        local popup = ui.builder(function()
            local c = content {}
            format.add_text(c, message_str)
            return tutorial_popup {
                title {
                    title_str
                },
                c
            }
        end)
        hud:add_child(popup)
        current_popup = popup
    end

    function hide_popup()
        if current_popup == nil then
            return
        end
        local hud = ui.query()
            :name("fullscreen")
            :property_bool("hud_area", true)
            :matches()(nil, nil)
        hud:remove_child(current_popup)
        current_popup = nil
    end

    function change_stage(new_stage)
        print("Stage change from " .. tostring(client_stage) .. " to " .. tostring(new_stage))
        stage_controller[client_stage].deinit(stage_state)
        client_stage = new_stage
        stage_state = {}
        stage_controller[client_stage].init(stage_state)
    end

    -- Called on events
    function on_event(evt, player, param)
        print(tostring(evt) .. " for player " .. player)
        if evt == "finalize_room" then
            room_progress[param.key] = (room_progress[param.key] or 0) + 1
            show_room_progress()
        end
    end

    -- Called when the client joins
    function client_init()
    end

    -- Called once a tick (20tps)
    function client_update()
        stage_controller[client_stage].update(stage_state)
    end

end

-- Called for `exec_mission` commands for the server and every client
function on_exec(data)
    if is_client then
        local header = tutorial_header.decode(data)
        change_stage(header.stage)
    end
end

-- Called when the exec command should be reverted
function undo_exec(data)
    error("undo_exec was called - NYI")
end