
/*
    Addon id: e7709ac2-cc0b-41b1-845d-7528015000ee
    Version: v1.7.8 (stable)
*/

Realistic_Police = Realistic_Police or {}

function Realistic_Police.GetSpawnPoint(ply)
    local ent = hook.Run("PlayerSelectSpawn", ply, false)

    if ent && isfunction(ent.GetPos) then
        return ent:GetPos()
    elseif DarkRP then
        local spawnVector = DarkRP.retrieveTeamSpawnPos(ply:Team()) or {}

        local randomSpawn = math.random(#spawnVector)

        if isvector(spawnVector[randomSpawn]) then
            return spawnVector[randomSpawn]
        end
    end
end

-- Send a notification to a player 
function Realistic_Police.SendNotify(ply, msg)
    if not IsValid(ply) or not ply:IsPlayer() then return end

    net.Start("RealisticPolice:Open")
        net.WriteString("Notify")
        net.WriteString(msg)
    net.Send(ply)
end 

-- This function generate a random plate 
function Realistic_Police.GeneratePlate(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} 
    local String = Realistic_Police.Letters().." "..Realistic_Police.Number().." "..Realistic_Police.Letters()
    
    -- Check if the plate does not exist and check if the player don't have already a data 
    if not Realistic_Police.CheckExist(String) && not Realistic_Police.PlayerData(ply) then 
        RealisticPoliceTab[ply:SteamID()] = {
            ["Plate"] = String,
            ["CustomPlate"] = "", 
        }
        file.Write("realistic_police/rpt_plate.txt", util.TableToJSON(RealisticPoliceTab, true))
    elseif not Realistic_Police.PlayerData(ply) then 
        Realistic_Police.GeneratePlate(ply)
    end
end 

function Realistic_Police.ChangePlate(ply, plateName, syncVehc)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} 

    RealisticPoliceTab[ply:SteamID()] = {
        ["Plate"] = plateName,
        ["CustomPlate"] = "", 
    }
    file.Write("realistic_police/rpt_plate.txt", util.TableToJSON(RealisticPoliceTab, true))

    if IsValid(syncVehc) then Realistic_Police.SetInfoVehc(syncVehc) end
end 

-- Check if the Plate already exist 
function Realistic_Police.CheckExist(Plate)
    if not isstring(Plate) then return end 

    local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} 

    for k,v in pairs(RealisticPoliceTab) do 
        if v.Plate == Plate then 
            return true 
        end 
    end 
    return false 
end 

-- Check if the player have already a license plate 
function Realistic_Police.PlayerData(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 
    
    local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} 
    
    return istable(RealisticPoliceTab[ply:SteamID()]) 
end 

-- Get the Plate of the player by his steamid 
function Realistic_Police.GetPlate(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/rpt_plate.txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {} 

    if not istable(RealisticPoliceTab[ply:SteamID()]) then return end 

    return RealisticPoliceTab[ply:SteamID()]["Plate"] 
end 

-- Compress the Report table and Send the table to client 
function Realistic_Police.SendReport(RPTSteamID64, ply) 
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/report/"..RPTSteamID64..".txt", "DATA") or ""
    local CompressTable = util.Compress(RealisticPoliceFil)
    
    net.Start("RealisticPolice:Report")
        net.WriteInt(CompressTable:len(), 32)
        net.WriteData(CompressTable, CompressTable:len() )
    net.Send(ply)
end 

-- Compress the Criminal Record table and Send the table to client 
function Realistic_Police.SendRecord(RPTSteamID64, ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 
    if not IsValid(player.GetBySteamID64(RPTSteamID64)) or not player.GetBySteamID64(RPTSteamID64):IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/record/"..RPTSteamID64..".txt", "DATA") or ""
    local CompressTable = util.Compress(RealisticPoliceFil)

    net.Start("RealisticPolice:CriminalRecord")
        net.WriteInt(CompressTable:len(), 32)
        net.WriteData(CompressTable, CompressTable:len())
        net.WriteEntity(player.GetBySteamID64(RPTSteamID64))
    net.Send(ply)
end 

-- Random letters for the license plate
function Realistic_Police.Letters()
    return string.char( math.random(65,90 ))..string.char( math.random(65,90 )) 
end 

-- Random numbers for the license plate 
function Realistic_Police.Number()
    return tostring(math.random(1, 9)..math.random(1, 9)..math.random(1, 9)) 
end 

function Realistic_Police.CompatibilitiesATM(ply, plyMoney, amountToPay)
    if not IsValid(ply) then return end

    local neededAmountFromBank = (amountToPay - plyMoney)
    if neededAmountFromBank > 0 then
        if CH_ATM && (CH_ATM.GetMoneyBankAccount(ply) >= neededAmountFromBank) then             
            return true
        elseif GlorifiedBanking && (GlorifiedBanking.GetPlayerBalance(ply) >= neededAmountFromBank) then
            return true
        end
    end
end

function Realistic_Police.PayWithBank(ply, amountToPay)
    if not IsValid(ply) then return end

    if CH_ATM then
        CH_ATM.TakeMoneyFromBankAccount(ply, amountToPay)
    elseif GlorifiedBanking then
        GlorifiedBanking.RemovePlayerBalance(ply, amountToPay)
    end
end

function Realistic_Police.SetInfoVehc(ent)
    -- Set the License plate of the player on the vehicle
    if not IsValid(ent) or not ent:IsVehicle() && ent:GetClass() != "gmod_sent_vehicle_fphysics_base" then return end

    local owner = ent:GetOwner()

    if ent.CPPIGetOwner && not IsValid(owner) then
        owner = ent:CPPIGetOwner()
    end
    
    if not IsValid(owner) then return end

    ent:SetNWString("rpt_plate", Realistic_Police.GetPlate(owner))
end 

-- Reset position of all bones for surrender and cuffed 
function Realistic_Police.ResetBonePosition(Table, ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    for k,v in pairs(Table) do 
        if isnumber(ply:LookupBone( k )) then 
		    ply:ManipulateBoneAngles(ply:LookupBone( k ), Angle(0,0,0))
        end 
	end 
end 

-- Check if the name of the fine is on the configuration 
function Realistic_Police.CheckValidFine(String)
    for k,v in pairs(Realistic_Police.FiningPolice) do 
        if v.Name == String then 
            return true 
        end 
    end 
end
function Realistic_Police.AddCriminalRecord(ply, Table)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    local RealisticPoliceFil = file.Read("realistic_police/record/"..ply:SteamID64()..".txt", "DATA") or ""
    local RealisticPoliceTab = util.JSONToTable(RealisticPoliceFil) or {}
    
    -- Check if RealisticPoliceTab contains more criminal record than the max
    if #RealisticPoliceTab >= Realistic_Police.MaxCriminalRecord then 
        table.remove(RealisticPoliceTab, 1)
    end 

    RealisticPoliceTab[#RealisticPoliceTab + 1] = Table 
    file.Write("realistic_police/record/"..ply:SteamID64()..".txt", util.TableToJSON(RealisticPoliceTab, true))
end

local whitelistVar = {
    ["Energy"] = true,
}
function Realistic_Police.SaveLoadInfo(ply, bool, info)
    if bool then 
        -- Save the weapon of the player 
        local WeaponSave = {}
        for k, v in pairs(ply:GetWeapons()) do
            local class = v:GetClass()

            WeaponSave[class] = {
                ["Clip1"] = v:Clip1(),
                ["Clip2"] = v:Clip2(),
            }
        end 
        
        -- Save some information of the player ( Weapon , Health & Armor ) 
        ply.RPTInfo = {
            RArmor = ply:Armor(), 
            RWeapon = WeaponSave,
            Ammo = ply:GetAmmo(), 
            RModel = info and ply:GetModel() or nil, 
            RHealth = ply:Health(),
            RPos = info and ply:GetPos() or nil, 
            RDarkrpVar = info and table.Copy(ply.DarkRPVars) or nil, 
        }
    else 
        -- SetPos the player 
        if isvector(ply.RPTInfo["RPos"]) then 
            ply:SetPos(ply.RPTInfo["RPos"])
        end 

        -- Give back the armor of the player 
        ply:SetArmor((ply.RPTInfo["RArmor"] or 0))
        
        -- Set the model of the player    
        if isstring(ply.RPTInfo["RModel"]) then
            ply:SetModel(ply.RPTInfo["RModel"])
        end 
        
        if istable(ply.RPTInfo["RDarkrpVar"]) then 
            for k,v in pairs(ply.RPTInfo["RDarkrpVar"]) do 
                if not whitelistVar[k] then continue end
                
                ply:setDarkRPVar(k, v)
            end      
        end 
        
        -- Give back the health of the player
        ply:SetHealth((ply.RPTInfo["RHealth"] or 100))

        -- Give back his weapons 
        ply:StripWeapons()

        for k,v in pairs(ply.RPTInfo["RWeapon"]) do 
            local wep = ply:Give(k, true)

            wep:SetClip1(v["Clip1"])
            wep:SetClip2(v["Clip2"])
        end 

        for k,v in pairs(ply.RPTInfo["Ammo"]) do 
            ply:SetAmmo(v,k)
        end
        
        ply.RPTInfo = nil 

        if ply.WeaponRPT["Surrender"] then
            ply:SelectWeapon("weapon_rpt_surrender")
        end

        if ply.WeaponRPT["Cuff"] then
            ply:SelectWeapon("weapon_rpt_cuffed")
        end
    end 
end 

function Realistic_Police.Tazz(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 
    if timer.Exists("rpt_stungun_unfreeze"..ply:EntIndex()) then return end 

    -- Save info of the player 
    Realistic_Police.SaveLoadInfo(ply, true, true)

    -- Create the Ragdoll Player 
    ply:CreateRagdoll()
    timer.Simple(0, function()
        local entSpectate = ply:GetRagdollEntity()
        if not IsValid(entSpectate) then entSpectate = ply end

        ply:Spectate(OBS_MODE_CHASE)
        ply:SpectateEntity(entSpectate)
        ply:StripWeapons()
        ply:StripAmmo()
    end) 
if IsValid(ply.WeaponRPT["DragedBy"]) then 
        ply.WeaponRPT["DragedBy"] = nil 
    end
    
    -- Send the animation of the corpse 
    timer.Create("rpt_stungun_send"..ply:EntIndex(), 1, 1, function()
        if IsValid(ply) then 
            net.Start("RealisticPolice:StunGun")
                net.WriteEntity(ply)
            net.Broadcast()
        end 
    end) 

    -- Timer when the player is not stun 
    timer.Create("rpt_stungun"..ply:EntIndex(), 5, 1, function()
        if IsValid(ply) then 

            -- Unspectacte and Freeze player 
            ply:UnSpectate()
            ply:Spawn()

            if IsValid(ply:GetRagdollEntity()) then 
                ply:GetRagdollEntity():Remove()
            end 

            Realistic_Police.SaveLoadInfo(ply)
            
            ply:Freeze(true)

            timer.Simple(1, function()
                ply.RPTStunTable = {}
            end) 

            timer.Create("rpt_stungun_unfreeze"..ply:EntIndex(), 7, 1, function()
                if IsValid(ply) then  
                    ply:Freeze(false)
                end 
            end)
        end 
    end) 
end 

function Realistic_Police.Cuff(ply, officer)
    if not IsValid(ply) or not IsValid(officer) then return end 
    if ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end 

    if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then
        timer.Remove("rpt_timerarrest"..ply:EntIndex())
    end 

    -- Check if the Police Man Can Cuff the player
    if not Realistic_Police.CanCuff[Realistic_Police.GetPlayerJob(officer)] then 
        Realistic_Police.SendNotify(officer, Realistic_Police.GetSentence("noGoodJobToArrestuser")) 
        return 
    end 
    
    -- Check if the player can be cuff or not 
    if Realistic_Police.CantBeCuff[Realistic_Police.GetPlayerJob(ply)] then 
        Realistic_Police.SendNotify(officer, Realistic_Police.GetSentence("cantArrestPlayer")) 
        return
    end

    hook.Run("RealisticPolice:OnCuff", ply)
    
    if ply.WeaponRPT["Surrender"] then 
        ply:StripWeapon("weapon_rpt_surrender")
        ply.WeaponRPT["Surrender"] = false 
Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneSurrender, ply)
    end 
    
    local wep, oldWeapon = ply:GetActiveWeapon()
    if IsValid(wep) then
        oldWeapon = wep:GetClass()
    end

    -- Cuff the Player 
    ply:Give("weapon_rpt_cuffed")
    ply:SelectWeapon("weapon_rpt_cuffed")

    ply.WeaponRPT = {
        Cuff = true, 
        EnterExit = true,
        oldWeapon = oldWeapon,
    }
    
    ply.WeaponRPT["processCuff"] = false

    ply:Freeze(false)
    officer:Freeze(false)

    hook.Run("RealisticPolice:Cuffed", ply, officer )
end 

function Realistic_Police.UnCuff(ply, officer)
    if not IsValid(ply) then return end 
    if IsValid(officer) && ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end  

    if ply.WeaponRPT["Cuff"] then 
        
        if timer.Exists("rpt_timerarrest"..ply:EntIndex()) && not Realistic_Police.UseDefaultArrest then
            timer.Remove("rpt_timerarrest"..ply:EntIndex())
        end 

        ply.WeaponRPT["Cuff"] = false
        Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply)
        
        ply:Freeze(false)
        
        if IsValid(officer) then
            officer:Freeze(false)
        end
        
        ply:StripWeapon("weapon_rpt_cuffed")
        
        if isstring(ply.WeaponRPT["oldWeapon"]) && ply:HasWeapon(ply.WeaponRPT["oldWeapon"]) then
            ply:SelectWeapon(ply.WeaponRPT["oldWeapon"])

            ply.WeaponRPT["oldWeapon"] = nil

            if IsValid(ply.WeaponRPT["DragedBy"]) then
                ply.WeaponRPT["DragedBy"].WeaponRPT["Drag"] = nil
                ply.WeaponRPT["DragedBy"] = nil
            end
        end

        hook.Run("RealisticPolice:UnCuffed", ply, officer)

        if IsValid(officer) then
            if istable(officer.WeaponRPT) && IsValid(officer.WeaponRPT["Drag"]) then
                if officer.WeaponRPT["Drag"] == ply then
                    officer.WeaponRPT["Drag"] = nil
                end
            end
        end

        ply.WeaponRPT["EnterExit"] = nil
    end 
end 

-- Check if there's a jail position. 
function Realistic_Police.CheckJailPos()
    local directory = file.Read("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", "DATA") or ""
    local RptTable = util.JSONToTable(directory) or {}

    if #RptTable > 0 then 
        return true 
    end 
end 

-- Find an empty jail place
function Realistic_Police.GetPlaceArrest()
    local directory = file.Read("realistic_police/" .. game.GetMap() .. "_rpt_jailpos" .. ".txt", "DATA") or ""
    local RptTable = util.JSONToTable(directory) or {}

    local RPTid = 1 
    for k,v in pairs(ents.FindInSphere(RptTable[RPTid]["Pos"], 50)) do 
        if v:IsPlayer() then 
            RPTid = RPTid + 1 
        end 
    end 
    return RptTable[RPTid]["Pos"]
end 
 
-- Arrest the player if he is cuffed 
function Realistic_Police.ArresPlayer(ply, Time, officer) 
    if not IsValid(ply) or not ply:IsPlayer() then return end 
    
    if Realistic_Police.UseDefaultArrest then 
        timer.Create("rpt_timerarrest"..ply:EntIndex(), Time, 1, function() end)
        
        hook.Run("RealisticPolice:Jailed", ply, Time, officer)
        ply:SetNWInt("rpt_arrest_time", CurTime() + Time)
        
        Realistic_Police.UnCuff(ply, officer)

        ply:arrest(Time, "Arrested by the Jailer")
    else
        if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then
            timer.Remove("rpt_timerarrest"..ply:EntIndex())
        end 
        
        if ply.WeaponRPT["Cuff"] then 
            ply:SetPos(Realistic_Police.GetPlaceArrest())
            ply.WeaponRPT["DragedBy"] = nil
            
            ply:SetNWInt("rpt_arrest_time", CurTime() + Time)
hook.Run("RealisticPolice:Jailed", ply, Time, officer)
            -- Create the Jail Timer 
            timer.Create("rpt_timerarrest"..ply:EntIndex(), Time, 1, function()
                if IsValid(ply) then 
                    -- ply:StripWeapons()
                    Realistic_Police.UnArrest(ply, officer)
                end 
            end)
        end 
    end

    if Realistic_Police.SetModelWhenJail then
        local randomModel = Realistic_Police.ModelWhenJail[math.random(1, #Realistic_Police.ModelWhenJail)]

        if isstring(randomModel) then
            ply:SetModel(Realistic_Police.ModelWhenJail)

            ply.RPS = ply.RPS or {}
            ply.RPS["oldModelBeforeArrest"] = ply:GetModel()
        end
    end
end 

function Realistic_Police.UnArrest(ply)
    if not IsValid(ply) or not ply:IsPlayer() then return end 

    -- Check if the timer Exist 
    if timer.Exists("rpt_timerarrest"..ply:EntIndex()) then
        -- Reset Variables / Bone / Speed of the player 
        ply:StripWeapon("weapon_rpt_cuffed")
        Realistic_Police.ResetBonePosition(Realistic_Police.ManipulateBoneCuffed, ply)

        -- UnCuff the Player ( For give weapons )
        ply.WeaponRPT["Cuff"] = false

        hook.Run("RealisticPolice:UnJailed", ply)

        ply.WeaponRPT = {}

        local spawnPoint = Realistic_Police.GetSpawnPoint(ply)
        ply:SetPos(spawnPoint)

        -- Remove the arrest timer 
        timer.Remove("rpt_timerarrest"..ply:EntIndex())
    end
    
    if Realistic_Police.UseDefaultArrest then
        if DarkRP then
            ply:unArrest()
        end
    end

    ply.RPS = ply.RPS or {}
    if isstring(ply.RPS["oldModelBeforeArrest"]) then
        ply:SetModel(ply.RPS["oldModelBeforeArrest"])
    end
end 

function Realistic_Police.Drag(ply, officer)
    if not IsValid(ply) or not IsValid(officer) then return end 
    if ply:GetPos():DistToSqr(officer:GetPos()) > 15625 then return end  

    if ply.WeaponRPT["Cuff"] then 
        if not IsValid(ply.WeaponRPT["DragedBy"]) && not IsValid(officer.WeaponRPT["Drag"]) then 
            ply.WeaponRPT["DragedBy"] = officer
            officer.WeaponRPT["Drag"] = ply
        else 
            ply.WeaponRPT["DragedBy"] = nil
            officer.WeaponRPT["Drag"] = nil
        end 
    end 
end 

function Realistic_Police.PlaceVehicle(officer, vehc)
	local RPTPlace = nil 
	local DistancePlace = 1000
    
	-- Get the Place with vcmod 
	if istable(vehc:VC_getSeatsAvailable()) then 
		for k,v in ipairs(table.Reverse(vehc:VC_getSeatsAvailable())) do 
			if v:GetPos():DistToSqr(officer:GetPos()) < DistancePlace ^ 2 then 
                if IsValid(v:GetDriver()) then continue end 
				RPTPlace = v 
                break 
			end 
		end 
	end 
	return RPTPlace 
end 

function Realistic_Police.GetPoliceProps(ply)
    local Count = 0 
    for k,v in pairs(ents.FindByClass("prop_physics")) do 
        if v:CPPIGetOwner() == ply then 
            if istable(Realistic_Police.Trunk[v:GetModel()]) then 
                Count = Count + 1 
            end 
        end 
    end 
    return Count 
end 

function Realistic_Police.CountRepairMan()
	local Count = 0 
	for k,v in pairs(player.GetAll()) do 
		if Realistic_Police.CameraWorker[Realistic_Police.GetPlayerJob(v)] then 
			Count = Count + 1 
		end 
	end 
	return Count 
end 

function Realistic_Police.BrokeCamera(ent)
    ent:ResetSequence("desactiv")
    ent:SetSequence("desactiv")
    ent:SetRptCam(true)
    ent:SetRptRotate("nil")  
    ent.DestroyCam = true 

    -- Restore the health and the animation of the camera 
    timer.Create("camera_respawn"..ent:EntIndex(), Realistic_Police.CameraRestart, 0, function()
        if IsValid(ent) then 
            if ent:GetRptCam() then 
                if Realistic_Police.CountRepairMan() == 0 then 
                    ent:ResetSequence("active")
                    ent:SetSequence("active")
                    ent:SetRptCam(false)
                    ent.HealthEntity = Realistic_Police.CameraHealth 
                    ent.DestroyCam = false 
                    timer.Remove("camera_respawn"..ent:EntIndex())
                end  
            else 
                timer.Remove("camera_respawn"..ent:EntIndex()) 
            end 
        end
    end) 
end 


