
local actions = require 'staff.actions'
local util = require 'util'
local objects = require 'objects'

notify_data = serialize.define({
    {"room_id", "i16"},
})
local notify_stuck_data = serialize.define({
    {"room_id", "i16"},
    {"students", "u16"},
})

function update(room)
    local skill = 0.0
    local happiness_mul = 0.0
    local obj_count = {}
    for _, obj in ipairs(room.objects) do
        local properties = objects.get_properties(obj.key)
        local count = obj_count[obj.key] or 0

        -- Skill
        local bonus_skill = properties.bonus_skill or 0.0
        local bonus_dropoff = properties.bonus_dropoff or 0.0
        skill = skill + bonus_skill * math.pow(bonus_dropoff, count)

        -- Happiness
        local happiness = properties.happiness or 0.0
        local happiness_dropoff = properties.happiness_dropoff or 0.0
        happiness_mul = happiness_mul + happiness * math.pow(happiness_dropoff, count)

        obj_count[obj.key] = count + 1
    end
    room.properties.bonus_skill = skill
    room.properties.happiness_mul = happiness_mul
end

function force_entity_release(room, e)
end

function tick_room(room, active)
    for _, e in ipairs(room.owned) do
        if e.should_release or (not active and e.should_release_inactive) then
            e:release()
        else
            e.active = active
            -- No default action as objects cover everything
            actions.tick_staff(room, e, nil, 0.0)
        end
    end


    -- Assume for now that only one teacher is teaching at
    -- at a time. May change in the future though
    local teacher = room.owned[1]
    -- Not much can be done if the teacher is missing
    if teacher == nil or teacher.key ~= "base:professor" then
        return
    end

    if not teacher.is_walking then
        if not teacher.in_room and not teacher.properties.been_inside then
            teacher.properties.time_outside = (teacher.properties.time_outside or 0) + 1
            if teacher.properties.time_outside > 5 then
                if not teacher.properties.warned_stuck then
                    teacher.properties.warned_stuck = true
                    room:notify_player("room.common#notify_stuck_staff", notify_data.encode({
                        room_id = room.room_id,
                    }))
                end
                teacher:force_release()
            end
        elseif teacher.in_room then
            teacher.properties.been_inside = true
        end
    end

    local control = teacher:get_var_float("control")

    local max_students = math.floor(20 + control * 175)
    local in_control = #room.visitors < max_students
    if not in_control then
        local has_warned = room.properties['has_warned_control'] or false
        if not has_warned then
            room.properties['has_warned_control'] = true
            room:notify_player("room.common#notify_control", notify_data.encode({
                room_id = room.room_id,
            }))
        end
        room.properties['teacher_has_control'] = false
        teacher:change_stat("job_satisfaction", -0.01)
    else
        room.properties['has_warned_control'] = false
        room.properties['teacher_has_control'] = true
    end

    local stuck_students = room.properties.stuck_students or 0
    local stuck_timer = room.properties.stuck_timer or 0
    for _, s in ipairs(room.visitors) do
        if s.properties.stuck then
            stuck_students = stuck_students + 1
            s.should_release = true
        end
    end
    if stuck_students > 0 then
        stuck_timer = stuck_timer + 1
        -- Collect stuck students for 5 seconds and then tell the player
        if stuck_timer > 5 * 20 then
            room.properties.stuck_students = 0
            room.properties.stuck_timer = 0
            room:notify_player("room.common#notify_stuck", notify_stuck_data.encode({
                room_id = room.room_id,
                students = stuck_students
            }))
        else
            room.properties.stuck_students = stuck_students
            room.properties.stuck_timer = stuck_timer
        end
    else
        room.properties.stuck_students = 0
        room.properties.stuck_timer = 0
    end
end

function tick_staff_client(room, entities)
    actions.tick_staff_client(room, entities)
end

function tick_student(room, student)
    if not student.properties.teacher and room.owned[1] then
        student.properties.teacher = room.owned[1]
    end
    student.properties['time_in_room'] = (student.properties['time_in_room'] or 0) + 1
end

function save_student(student)
    return {
        time_in_room = student.properties['time_in_room'] or 0,
    }
end

function load_student(student, data)
    if data == nil then
        return
    end
    student.properties['time_in_room'] = data.time_in_room or 0
end

local grades = {
    [0] = "F",
    [1] = "F",
    [2] = "F",
    [3] = "F",
    [4] = "E",
    [5] = "E",
    [6] = "E",
    [7] = "E",
    [8] = "D",
    [9] = "D",
    [10] = "D",
    [11] = "C",
    [12] = "C",
    [13] = "B",
    [14] = "A",
}
function get_grade(ptime)
    if ptime < 0.0 then
        return "F"
    end
    local grade = grades[math.floor(ptime * 15)]
    if grade == nil then
        return "A"
    else
        return grade
    end
end

function notify_control(data)
    local data = notify_data.decode(data)
    local desc = ui.new("description")
    local name = level.get_room_display_name(data.room_id)
    desc:add_child(ui.new_text("A teacher in " .. name .. " is struggling to control their class"))
    return {
        icon = "ui/icons/speech",
        title = "Teacher Struggling",
        description = desc,
    }
end

function notify_stuck(data)
    local data = notify_stuck_data.decode(data)
    local desc = ui.new("description")
    local name = level.get_room_display_name(data.room_id)
    local student_str = "students"
    if data.students == 1 then
        student_str = "student"
    end
    desc:add_child(ui.new_text(tostring(data.students) .. " " .. student_str .. " were unable to attend " .. name .. " due to not being able to find a way there"))
    return {
        icon = "ui/icons/lost",
        title = "Students stuck",
        description = desc,
    }
end

function notify_stuck_staff(data)
    local data = notify_data.decode(data)
    local desc = ui.new("description")
    local name = level.get_room_display_name(data.room_id)
    desc:add_child(ui.new_text("A staff member is unable to reach " .. name .. " due to not being able to find a way there"))
    return {
        icon = "ui/icons/lost",
        title = "Staff stuck",
        description = desc,
    }
end

function release_student(room, student, fee, hmod)
    if student.properties.done_release then
        return
    end
    student.properties.done_release = true
    local time = student.properties['time_in_room'] or 0

    -- Assume for now that only one teacher is teaching at
    -- at a time. May change in the future though
    local teacher = student.properties.teacher
    -- Not much can be done if the teacher is missing
    if teacher == nil then
        print("Missing teacher")
        return
    end

    local fatigue = teacher:get_var_float("fatigue")
    local skill = teacher:get_var_float("skill")
    if fatigue < 0.1 then
        skill = skill * 0.8
    end

    skill = skill + room.properties.bonus_skill

    local in_control = room.properties['teacher_has_control']
    if in_control == nil then
        in_control = true
    end

    hmod = hmod * (1.0 + room.properties.happiness_mul)

    if not room.closing and not student.properties.stuck then
        local pscore = 0
        if time < (util.LESSON_LENGTH / 2) then
            local ptime = time / (util.LESSON_LENGTH / 2)
            student:change_stat("happiness", -ptime * 0.5 * hmod)
            ptime = ptime * skill
            if not in_control then
                ptime = ptime * 0.25
            end
            pscore = ptime
        else
            student:change_stat("happiness", (time / util.LESSON_LENGTH) * 0.6 * hmod)
            pscore = skill * 0.8 + (math.random() * 0.6 - 0.3) -- TODO: Make this based on the student too instead of completely random
            -- TODO: Base on distance from the teacher?
            if not in_control then
                pscore = pscore * 0.25
            end
        end
        local grade = get_grade(pscore)
        if student:give_grade(grade) then
            student:charge("Lesson fee", fee)
            if grade == "F" or grade == "E" then
                teacher:change_stat("happiness", -0.1 * (1.0 - skill))
                teacher:change_stat("job_satisfaction", -0.004)
            elseif grade ~= "A" then
                teacher:change_stat("happiness", 0.2 * hmod * skill)
                teacher:change_stat("job_satisfaction", 0.02)
            else
                teacher:change_stat("job_satisfaction", -0.001)
                teacher:change_stat("happiness", 0.4 * hmod * skill)
            end
        end
    else
        student:change_stat("happiness", -0.35 * hmod)
    end
end



local tooltip_data = serialize.define({
    {"room_id", "i16"},
    {"in_control", "bool"},
    {"students", "u10"},
    {"max_students", "u10"},
})
function encode_tooltip(room)
    local in_control = room.properties['teacher_has_control']
    if in_control == nil then
        in_control = true
    end

    return tooltip_data.encode({
        room_id = room.room_id,
        in_control = in_control,
        students = #room.visitors,
        max_students = room.capacity,
    })
end
function decode_tooltip(data)
    local data = tooltip_data.decode(data)
    local content = ui.new("content")
    local name = level.get_room_display_name(data.room_id)
    local msg = ui.new_text(name .. "\n")
    msg:set_property_bool("title", true)
    content:add_child(msg)

    local msg = ui.new_text("Teacher has control: ")
    content:add_child(msg)
    if data.in_control then
        local msg = ui.new_text("yes")
        msg:set_property_bool("highlight", true)
        content:add_child(msg)
    else
        local msg = ui.new_text("no")
        msg:set_property_bool("negative", true)
        content:add_child(msg)
    end

    content:add_child(ui.new_text("\n"))
    content:add_child(ui.new_text("Students: " .. tostring(data.students) .. "/" .. tostring(data.max_students)))

    return content
end

local tooltip_data_def = serialize.define({
    {"room_id", "i16"},
})
function encode_tooltip_def(room)
    return tooltip_data_def.encode({
        room_id = room.room_id,
    })
end
function decode_tooltip_def(data)
    local data = tooltip_data_def.decode(data)
    local content = ui.new("content")
    local name = level.get_room_display_name(data.room_id)
    local msg = ui.new_text(name)
    msg:set_property_bool("title", true)
    content:add_child(msg)
    return content
end