AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")

-- Регистрация всех сетевых сообщений
util.AddNetworkString("SchoolBoard_OpenMenu")
util.AddNetworkString("SchoolBoard_SaveLayout")
util.AddNetworkString("SchoolBoard_SyncLayout")
util.AddNetworkString("SchoolBoard_RequestLayout")
util.AddNetworkString("SchoolBoard_Clear_Req")
util.AddNetworkString("SchoolBoard_Clear_Do")
util.AddNetworkString("SchoolBoard_DrawLine")
util.AddNetworkString("SchoolBoard_SavePosition")
util.AddNetworkString("SchoolBoard_SyncPosition")
util.AddNetworkString("SchoolBoard_SaveRotation")
util.AddNetworkString("SchoolBoard_SyncRotation")


-- helper: only SAM ranks superadmin/curator (or equivalent usergroups) can modify board
local function CanModifyBoard(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return false end

    -- standard checks
    if ply:IsSuperAdmin() then return true end
    if ply:GetUserGroup() == "curator" then return true end

    -- if SAM is present, consult its API as a fallback
    if sam and sam.IsPlayerInRank then
        if sam.IsPlayerInRank(ply, "superadmin") or sam.IsPlayerInRank(ply, "curator") then
            return true
        end
    end

    return false
end

-- append log entries to a file and console
local function LogBoardChange(ply, ent, action)
    local name = IsValid(ply) and ply:Nick() or "Console"
    local sid = IsValid(ply) and ply:SteamID() or "N/A"
    local id = IsValid(ent) and ent:EntIndex() or 0
    local pos = IsValid(ent) and tostring(ent:GetPos()) or "unknown"
    local msg = string.format("[%s] %s (%s) %s board #%d at %s\n", os.date(), name, sid, action, id, pos)

    print(msg)
    file.Append("schoolboard_changes.txt", msg)
end

-- console command for printing current log (admins only)
concommand.Add("schoolboard_printlog", function(ply, cmd, args)
    if IsValid(ply) and not CanModifyBoard(ply) then
        ply:ChatPrint("Нет доступа к логу доски.")
        return
    end

    local contents = file.Read("schoolboard_changes.txt", "DATA") or ""
    if IsValid(ply) then
        ply:PrintMessage(HUD_PRINTCONSOLE, contents)
        ply:ChatPrint("Лог выведен в консоль.")
    else
        print(contents)
    end
end)

-- очистка лога доски (только для старшего админства)
concommand.Add("schoolboard_clearlog", function(ply, cmd, args)
    if IsValid(ply) and not CanModifyBoard(ply) then
        ply:ChatPrint("Нет доступа к управлению логом.")
        return
    end

    file.Write("schoolboard_changes.txt", "")
    if IsValid(ply) then
        ply:ChatPrint("Лог доски очищен.")
        ply:PrintMessage(HUD_PRINTCONSOLE, "Лог доски был очищен.")
    else
        print("schoolboard_changes.txt was cleared by console")
    end
end)

function ENT:Initialize()
    self:SetModel("models/props/cs_office/offcorkboarda.mdl")
    self:PhysicsInit(SOLID_VPHYSICS)
    self:SetMoveType(MOVETYPE_VPHYSICS)
    self:SetSolid(SOLID_VPHYSICS)
    self:SetUseType(SIMPLE_USE) -- Позволяет нажимать 'E' на доску

    local phys = self:GetPhysicsObject()
    if IsValid(phys) then
        phys:Wake()
    end

    self.BoardLayoutStr = "[]" -- По умолчанию пустой массив JSON
    self.PositionOffset = Vector(3713.2, 4595.6, -200) -- Смещение позиции доски в 3D пространстве
    self.RotationOffset = Angle(0, -180, 90) -- Смещение поворота доски
end

-- Открываем меню настройки, когда игрок нажимает 'E'
function ENT:Use(activator, caller)
    if IsValid(activator) and activator:IsPlayer() then
        if not CanModifyBoard(activator) then
            activator:ChatPrint("Только superadmin/curator может редактировать доску.")
            return
        end

        net.Start("SchoolBoard_OpenMenu")
        net.WriteEntity(self)
        net.Send(activator)
    end
end

-- Клиент отправляет свой новый макет доски (массив картинок и текстов)
net.Receive("SchoolBoard_SaveLayout", function(len, ply)
    local ent = net.ReadEntity()
    
    if len > 64000 then -- Ограничение размера пакета на всякий случай
        return
    end
    
    local layoutSize = net.ReadUInt(32)
    local compressedData = net.ReadData(layoutSize)
    
    if IsValid(ent) and ent:GetClass() == "school_board" then
        -- Разрешены только superadmin/curator (SAM ранги) или соответствующие usergroup
        if not CanModifyBoard(ply) then
            if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end
            return
        end

        -- Проверка расстояния, чтобы школьник с другого конца карты не менял доску
        if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end
        
        ent.BoardLayoutStr = util.Decompress(compressedData) or "[]"

        -- логируем факт сохранения
        LogBoardChange(ply, ent, "saved layout")
        
        -- Рассказываем всем остальным, что доска обновилась
        net.Start("SchoolBoard_SyncLayout")
        net.WriteEntity(ent)
        net.WriteUInt(layoutSize, 32)
        net.WriteData(compressedData, layoutSize)
        net.Broadcast()
    end
end)

-- Когда игрок подключается или загружает сущность, он просит её данные
net.Receive("SchoolBoard_RequestLayout", function(len, ply)
    local ent = net.ReadEntity()
    
    if IsValid(ent) and ent:GetClass() == "school_board" then
        if not ent.BoardLayoutStr then ent.BoardLayoutStr = "[]" end
        
        local compressedData = util.Compress(ent.BoardLayoutStr)
        if compressedData then
            local layoutSize = string.len(compressedData)
            
            net.Start("SchoolBoard_SyncLayout")
            net.WriteEntity(ent)
            net.WriteUInt(layoutSize, 32)
            net.WriteData(compressedData, layoutSize)
            net.Send(ply)
        end
    end
end)

-- Принимаем запрос на очистку от игрока и рассылаем всем
net.Receive("SchoolBoard_Clear_Req", function(len, ply)
    local ent = net.ReadEntity()
    
    if IsValid(ent) and ent:GetClass() == "school_board" then
        if not CanModifyBoard(ply) then
            if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end
            return
        end

        LogBoardChange(ply, ent, "cleared board")

        net.Start("SchoolBoard_Clear_Do")
        net.WriteEntity(ent)
        net.Broadcast()
    end
end)

-- Принимаем координаты линии и цвет от рисующего и рассылаем остальным
net.Receive("SchoolBoard_DrawLine", function(len, ply)
    local ent = net.ReadEntity()
    local x1 = net.ReadFloat()
    local y1 = net.ReadFloat()
    local x2 = net.ReadFloat()
    local y2 = net.ReadFloat()
    local r = net.ReadUInt(8)
    local g = net.ReadUInt(8)
    local b = net.ReadUInt(8)

    if IsValid(ent) and ent:GetClass() == "school_board" then
        -- ограничения на рисование маркером
        if not CanModifyBoard(ply) then return end

        -- Проверяем, что игрок стоит рядом с доской
        if ply:GetPos():DistToSqr(ent:GetPos()) > 50000 then return end

        net.Start("SchoolBoard_DrawLine", true) -- unreliable пакет
        net.WriteEntity(ent)
        net.WriteFloat(x1)
        net.WriteFloat(y1)
        net.WriteFloat(x2)
        net.WriteFloat(y2)
        net.WriteUInt(r, 8)
        net.WriteUInt(g, 8)
        net.WriteUInt(b, 8)
        net.SendOmit(ply) -- Отправляем всем, кроме рисующего
    end
end)

-- Сохраняем позицию доски (смещение вектора)
net.Receive("SchoolBoard_SavePosition", function(len, ply)
    local ent = net.ReadEntity()
    local offsetX = net.ReadFloat()
    local offsetY = net.ReadFloat()
    local offsetZ = net.ReadFloat()
    
    if IsValid(ent) and ent:GetClass() == "school_board" then
        if not CanModifyBoard(ply) then
            if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end
            return
        end
        -- Проверка расстояния
        if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end
        
        ent.PositionOffset = Vector(offsetX, offsetY, offsetZ)
        
        -- логируем изменение позиции
        LogBoardChange(ply, ent, "changed position")
        
        -- Синхронизируем позицию со всеми
        net.Start("SchoolBoard_SyncPosition")
        net.WriteEntity(ent)
        net.WriteFloat(offsetX)
        net.WriteFloat(offsetY)
        net.WriteFloat(offsetZ)
        net.Broadcast()
    end
end)

-- Сохраняем поворот доски (смещение угла)
net.Receive("SchoolBoard_SaveRotation", function(len, ply)
    local ent = net.ReadEntity()
    local pitch = net.ReadFloat()
    local yaw = net.ReadFloat()
    local roll = net.ReadFloat()
    
    if IsValid(ent) and ent:GetClass() == "school_board" then
        if not CanModifyBoard(ply) then
            if IsValid(ply) then ply:ChatPrint("У вас нет прав для изменения доски.") end
            return
        end
        -- Проверка расстояния
        if ply:GetPos():DistToSqr(ent:GetPos()) > 500000 then return end
        
        ent.RotationOffset = Angle(pitch, yaw, roll)
        
        -- логируем изменение поворота
        LogBoardChange(ply, ent, "changed rotation")
        
        -- Синхронизируем поворот со всеми
        net.Start("SchoolBoard_SyncRotation")
        net.WriteEntity(ent)
        net.WriteFloat(pitch)
        net.WriteFloat(yaw)
        net.WriteFloat(roll)
        net.Broadcast()
    end
end)
