if SERVER then
	AddCSLuaFile()
end

local meta = FindMetaTable("Player")

local reg = debug.getregistry()
local GetNW2Entity = reg.Entity.GetNW2Entity

local CuffEntity = "CuffEntity"

function meta:IsHandcuffed()
	return GetNW2Entity(self, CuffEntity) != NULL
end

hook.Add("Cuffs_CanHandcuff", "Cuffs_CuffGodPlayers", function(ply, targ)
	if targ:HasGodMode() then
		return false
	end
end)

hook.Add("PlayerSwitchWeapon", "Cuffs_PlayerSwitchWeapon", function(ply, o, n)
	if ply:IsHandcuffed() and n:GetClass() != "weapon_handcuffed" then
		return true
	end
end)

hook.Add("Cuffs_OnPlayerHandcuffing", "Cuffs_ReduceMovementSpeed", function(ply, target, rag, first)
	if !rag then
		target:SetNW2Bool("Cuffs_SpeedPenalty", true)
		target:SetNW2Float("Cuffs_SpeedTime", CurTime() + 1)
	end
end)

hook.Add("Cuffs_OnPlayerHandcuffed", "Cuffs_RestoreMovementSpeed", function(ply, target)
	target:SetNW2Bool("Cuffs_SpeedPenalty", false)
	target:SetNW2Float("Cuffs_SpeedTime", 0)
end)

local HitGroundSounds = {
	Sound("physics/body/body_medium_impact_soft1.wav"),
	Sound("physics/body/body_medium_impact_soft2.wav"),
	Sound("physics/body/body_medium_impact_soft3.wav"),
	Sound("physics/body/body_medium_impact_soft4.wav"),
	Sound("physics/body/body_medium_impact_soft5.wav"),
	Sound("physics/body/body_medium_impact_soft6.wav"),
	Sound("physics/body/body_medium_impact_soft7.wav")
}

hook.Add("OnPlayerHitGround", "Cuffs_PlayerHitGround", function(ply, _, _, speed)
	if !ply:IsHandcuffed() then return end
	ply.CuffsNextJumpTime = CurTime() + 1
	if CLIENT and IsFirstTimePredicted() then
		sound.Play(HitGroundSounds[math.random(#HitGroundSounds)], ply:GetPos(), 75, 100, math.Clamp((speed ^ 2) / 1000, 0, 0.5))
	end
end)

local ForceJump = {}
local function ForceJumpHook()
	for ply in pairs(ForceJump) do
		if !IsValid(ply) or !ply:OnGround() then
			ForceJump[ply] = nil
			if table.IsEmpty(ForceJump) then
				hook.Remove("Think", "Cuffs_ForceJump")
			end
			return
		end
		local pos = ply:GetPos()
		local hull_mins, hull_maxs = ply:GetHull()
		local tr = util.TraceHull({
			start = pos,
			endpos = pos + Vector(0, 0, 10),
			filter = ply,
			mins = hull_mins,
			maxs = hull_maxs
		})
		if tr.Hit then return end
		ply:SetPos(ply:GetPos() + Vector(0, 0, 5))
		ForceJump[ply] = nil
	end
end

local penalty, time, cur_time
local clamp = 50
local vclamp = 20
local accel = 200

local reg = debug.getregistry()
local IsHandcuffed = reg.Player.IsHandcuffed
local GetNW2Bool = reg.Entity.GetNW2Bool
local GetNW2Float = reg.Entity.GetNW2Float
local Cuffs_SpeedPenalty = "Cuffs_SpeedPenalty"
local Cuffs_SpeedTime = "Cuffs_SpeedTime"

local isArrested = reg.Player.isArrested
timer.Simple(0, function()
	isArrested = reg.Player.isArrested
end)
local function MovePenalty(ply, mv)
	penalty = GetNW2Bool(ply, Cuffs_SpeedPenalty)
	if !IsHandcuffed(ply) and !penalty then return end
	time, cur_time = GetNW2Float(ply, Cuffs_SpeedTime, false), CurTime()
	if time and time <= cur_time then
		ply:SetNW2Bool(Cuffs_SpeedPenalty, false)
		ply:SetNW2Float(Cuffs_SpeedTime, 0)
	end
	if cur_time < (ply.CuffsNextJumpTime or 0) or penalty then
		mv:SetButtons(bit.band(mv:GetButtons(), bit.bnot(IN_JUMP)))
	end
	if penalty then
		mv:SetButtons(bit.band(mv:GetButtons(), bit.bnot(IN_DUCK)))
	end
	if !ply:isArrested() or penalty then
		mv:SetMaxClientSpeed(mv:GetMaxClientSpeed() * (penalty and 0.3 or 0.4))
	end
	local cuffs = ply:GetActiveWeapon()
	if !cuffs:IsValid() or !cuffs.GetRopeLength or cuffs:GetRopeLength() <= 0 then return end
	local kidnapper = cuffs:GetKidnapper()
	if !kidnapper:IsValid() or kidnapper == ply then return end
	local target_pt = (kidnapper:IsPlayer() and kidnapper:GetShootPos()) or kidnapper:GetPos()
	local move_dir = (target_pt - ply:GetPos()):GetNormal()
	local shoot_pos = ply:GetShootPos() + Vector(0, 0, (ply:Crouching() and 16 or 0))
	local distance = cuffs:GetRopeLength()
	local dist_from_target = shoot_pos:Distance(target_pt)
	if dist_from_target <= (distance + 5) then return end
	if ply:InVehicle() then
		if SERVER and dist_from_target > (distance * 3) then
			ply:ExitVehicle()
		end
		return
	end
	local target_pos = target_pt - (move_dir * distance)
	local x_dif = math.abs(shoot_pos[1] - target_pos[1])
	local y_dif = math.abs(shoot_pos[2] - target_pos[2])
	local z_dif = math.abs(shoot_pos[3] - target_pos[3])
	local speed_mult = 3 + ((x_dif + y_dif) * 0.5) ^ 1.01
	local vert_mult = math.max((math.max(300 - (x_dif + y_dif), -10) * 0.08) ^ 1.01  + (z_dif / 2), 0)
	if kidnapper:GetGroundEntity() == ply then
		vert_mult = -vert_mult
	end
	local target_vel = (target_pos - shoot_pos):GetNormal() * 10
	target_vel[1] = target_vel[1] * speed_mult
	target_vel[2] = target_vel[2] * speed_mult
	target_vel[3] = target_vel[3] * vert_mult
	local dir = mv:GetVelocity()
	local vaccel = 30 * (vert_mult / 50)
	dir[1] = (dir[1] > target_vel[1] - clamp or dir[1] < target_vel[1] + clamp) and math.Approach(dir[1], target_vel[1], accel) or dir[1]
	dir[2] = (dir[2] > target_vel[2] - clamp or dir[2] < target_vel[2] + clamp) and math.Approach(dir[2], target_vel[2], accel) or dir[2]
	if shoot_pos[3] < target_pos[3] then
		dir[3] = (dir[3] > target_vel[3] - vclamp or dir[3] < target_vel[3] + vclamp) and math.Approach(dir[3], target_vel[3], vaccel) or dir[3]
		if SERVER and vert_mult > 0 then
			if table.IsEmpty(ForceJump) then
				hook.Add("Think", "Cuffs_ForceJump", ForceJumpHook)
			end
			ForceJump[ply] = true
		end
	end
	mv:SetVelocity(dir)
end
hook.Add("SetupMove", "Cuffs_MovePenalty", MovePenalty)

if CLIENT then
	local CuffsAll, cuff, tr, ent = {}
	local function GetTrace(ply, tr_override)
		tr = tr_override or ply:GetEyeTrace()
		ent = tr.Entity
		if IsValid(ent) and ent:IsPlayer() then
			if ent:IsHandcuffed() and tr.HitPos:DistToSqr(ply:EyePos()) <= 10000 then
				return tr, ent:GetActiveWeapon()
			end
		end
	end

	local Col = {Text = Color(255, 255, 255), TextShadow = Color(0, 0, 0), Rope = Color(255, 255, 255), BoxOutline = Color(0, 0, 0), BoxBackground = Color(255, 255, 255, 20), BoxLeft = Color(255, 0, 0), BoxRight = Color(0, 255, 0)}
	local mat_grad = Material("gui/gradient")
	local local_ply, NextCuffsTableUpdate = nil, 0
	local function DrawCuffedInteract()
		local_ply = local_ply or LocalPlayer()
		if local_ply:IsHandcuffed() then return end
		local w, h = (ScrW() / 2), (ScrH() / 2)
		local text_pos = h - 40
		local tr = local_ply:GetEyeTrace()
		if tr.Hit then
			for i=1, #CuffsAll do
				cuff = CuffsAll[i]
				if cuff.GetKidnapper and cuff.GetRopeLength and cuff:GetKidnapper() == local_ply and cuff:GetRopeLength() > 0 then
					local ent = tr.Entity
					if ent:IsPlayer() and ent:IsHandcuffed() then break end
					if tr.HitPos:DistToSqr(local_ply:EyePos()) > 10000 then break end
					local trans = (input.LookupBinding("+attack") or "[PRIMÄR]"):upper()
					if trans == "MOUSE1" then
						trans = "Linksklick"
					end
					local str = string.format("[%s + %s] um den %s", (input.LookupBinding("+use") or "[BENUTZEN]"):upper(), trans, ent:IsPlayer() and "Gefangenen zu übergeben" or "Gefangenen festzubinden")
					draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
					draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
					text_pos = text_pos - 30
					break
				end
			end
		end
		tr, cuff = GetTrace(local_ply, tr)
		if !tr or !IsValid(cuff) or cuff:GetClass() != "weapon_handcuffed" then return end
		surface.SetDrawColor(Col.BoxOutline)
		surface.DrawOutlinedRect(w - 101, text_pos - 1, 202, 22)
		surface.SetDrawColor(Col.BoxBackground)
		surface.DrawRect(w - 100, text_pos, 200, 20)
		render.SetScissorRect(w - 100, text_pos, (w - 100) + ((cuff:GetCuffBroken() / 100) * 200), text_pos + 20, true)
			surface.SetDrawColor(Col.BoxRight)
			surface.DrawRect(w - 100, text_pos, 200, 20)
			surface.SetMaterial(mat_grad)
			surface.SetDrawColor(Col.BoxLeft)
			surface.DrawTexturedRect(w - 100, text_pos, 200,20)
		render.SetScissorRect(0, 0, 0, 0, false)
		text_pos = text_pos - 25
		if IsValid(cuff:GetFriendBreaking()) then
			draw.SimpleText("Gefangener wird freigelassen...", "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
			draw.SimpleText("Gefangener wird freigelassen...", "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
			text_pos = text_pos - 20
		else
			local str = string.format("%s zum Freilassen", (input.LookupBinding("+use") or "[BENUTZEN]"):upper())
			draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
			draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
			text_pos = text_pos - 20
		end
		if cuff:GetRopeLength() > 0 then
			if IsValid(cuff:GetKidnapper()) then
				if cuff:GetKidnapper() == local_ply then
					local str = string.format("%s, um Gefangenen loszulassen", (input.LookupBinding("+reload") or "[NACHLADEN]"):upper())
					draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
					draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
					text_pos = text_pos - 20
				end
			elseif !cuff:GetDragRestricted() or local_ply:isCP() then
				local str = string.format("%s, um Gefangenen hinter dir herzuziehen", (input.LookupBinding("+reload") or "[NACHLADEN]"):upper())
				draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
				draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
				text_pos = text_pos-20
			end
		end
		if cuff:GetCanBlind() then
			local trans = (input.LookupBinding("+attack2") or "[SEKUNDÄR]"):upper()
			if trans == "MOUSE2" then
				trans = "RM"
			end
			local str = string.format("[ALT + %s] um %s", trans, cuff:GetIsBlind() and "Gefangenem die Augenbinde zu entfernen" or "Gefangenem die Augen zu verbinden")
			draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
			draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
			text_pos = text_pos - 20
		end
		if cuff:GetCanGag() then
			local trans = (input.LookupBinding("+attack") or "[PRIMÄR]"):upper()
			if trans == "MOUSE1" then
				trans = "LM"
			end
			local str = string.format("[ALT + %s] um %s", trans, cuff:GetIsGagged() and "Gefangenem den Knebel zu entfernen" or "Gefangenen zu knebeln")
			draw.SimpleText(str, "HandcuffsText", w + 1, text_pos + 1, Col.TextShadow, TEXT_ALIGN_CENTER)
			draw.SimpleText(str, "HandcuffsText", w, text_pos, Col.Text, TEXT_ALIGN_CENTER)
			text_pos = text_pos - 20
		end
	end

	local function InteractWithCuffed(ply, bind, pressed)
		if !pressed or ply != LocalPlayer() or !ply:Alive() or ply:IsHandcuffed() then return end
		if bind == "+use" then
			local tr, cuffs = GetTrace(ply)
			if tr then
				net.Start("Cuffs_FreePlayer")
					net.WriteEntity(tr.Entity)
				net.SendToServer()
				return true
			else
				local ent = util.TraceLine({start = ply:EyePos(), endpos = ply:EyePos() + (ply:EyeAngles():Forward() * 100), filter = ply}).Entity
				if IsValid(ent) and ent:GetNW2Bool("Cuffs_TieHook") then
					net.Start("Cuffs_UntiePlayers")
					net.SendToServer()
				end
			end
		elseif bind == "+attack" then
			if ply:KeyDown(IN_USE) and ply:OnGround() then
				local is_dragging
				for i=1, #CuffsAll do
					cuff = CuffsAll[i]
					if cuff.GetRopeLength and cuff.GetKidnapper and cuff:GetRopeLength() > 0 and cuff:GetKidnapper() == ply then
						is_dragging = true
						break
					end
				end
				if is_dragging then
					net.Start("Cuffs_TiePlayers")
					net.SendToServer()
					return true
				end
			end
		elseif bind == "+reload" then
			local tr, cuffs = GetTrace(ply)
			if tr and cuffs.GetRopeLength and cuffs:GetRopeLength() > 0 then
				net.Start("Cuffs_DragPlayer")
					net.WriteEntity(tr.Entity)
					net.WriteBit(LocalPlayer() != cuffs:GetKidnapper())
				net.SendToServer()
				return true
			end
		end
		if !input.IsKeyDown(KEY_LALT) then return end
		if bind == "+attack" then
			local tr, cuffs = GetTrace(ply)
			if tr and cuffs.GetCanGag and cuffs:GetCanGag() then
				net.Start("Cuffs_GagPlayer")
					net.WriteEntity(tr.Entity)
					net.WriteBit(!cuffs:GetIsGagged())
				net.SendToServer()
				return true
			end
		elseif bind == "+attack2" then
			local tr, cuffs = GetTrace(ply)
			if tr and cuffs.GetCanBlind and cuffs:GetCanBlind() then
				net.Start("Cuffs_BlindPlayer")
					net.WriteEntity(tr.Entity)
					net.WriteBit(!cuffs:GetIsBlind())
				net.SendToServer()
				return true
			end
		end
	end

	local DragBone = "ValveBiped.Bip01_R_Hand"
	local LeashBone = "Bip01 Neck1"
	local LeashHolder = "ValveBiped.Bip01_R_Hand"
	local DefaultRope = Material("cable/rope")

	local owner
	local vector_minus = Vector(0, 0, 5)
	local function DragRope()
		for i=1, #CuffsAll do
			cuff = CuffsAll[i]
			owner = cuff:GetOwner()
			owner = IsValid(owner.RagdollPlayer) and owner.RagdollPlayer or owner
			if !IsValid(owner) or !cuff.GetRopeLength or cuff:GetRopeLength() <= 0 or !cuff.GetKidnapper or !IsValid(cuff:GetKidnapper()) then continue end
			local kidnapper = cuff:GetKidnapper()
			local kidpos = kidnapper:IsPlayer() and kidnapper:GetShootPos() - vector_minus or kidnapper:GetPos()
			local pos = owner:GetPos()
			if cuff:GetIsCollar() then
				local bone = owner:LookupBone(LeashBone)
				if bone then
					--[[local matrix = owner:GetBoneMatrix(bone)
					if matrix then
						pos = matrix:GetTranslation()
					else
						pos = owner:GetBonePosition(bone)
					end]]
					pos = owner:GetBonePosition(bone)
					if !pos or (pos.x == 0 and pos.y == 0 and pos.z == 0) then
						pos = owner:GetPos()
					end
				end
				if kidnapper != LocalPlayer() or LocalPlayer():ShouldDrawLocalPlayer() then
					local lbone = kidnapper:LookupBone(LeashHolder)
					if lbone then
						--[[local matrix = kidnapper:GetBoneMatrix(lbone)
						local newpos
						if matrix then
							newpos = matrix:GetTranslation()
						else
							newpos = kidnapper:GetBonePosition(lbone)
						end]]
						local newpos = kidnapper:GetBonePosition(lbone)
						if newpos and (newpos.x != 0 and newpos.y != 0 and newpos.z != 0) then
							kidpos = newpos
						end
					end
				end
			else
				local bone = owner:LookupBone(DragBone)
				if bone then
					--[[local matrix = owner:GetBoneMatrix(bone)
					if matrix then
						pos = matrix:GetTranslation()
					else
						pos = owner:GetBonePosition(bone)
					end]]
					pos = owner:GetBonePosition(bone)
					if !pos or (pos.x == 0 and pos.y == 0 and pos.z == 0) then
						pos = owner:GetPos()
					end
				end
			end
			local rope_mat = cuff:GetRopeMaterial()
			if rope_mat != cuff.LastMatStr then
				local mat = Material(rope_mat)
				cuff.RopeMat = mat
				cuff.LastMatStr = mat
			end
			if !cuff.RopeMat then
				cuff.RopeMat = DefaultRope
			end
			render.SetMaterial(cuff.RopeMat)
			render.DrawBeam(kidpos, pos, 0.7, 0, 5, Col.Rope)
		end
	end

	local DefaultRope = Material("cable/rope")
	local HeadBone = "ValveBiped.Bip01_Head1"
	local RenderPos = {
		Blind = {Vector(3.2, 5, 2.6), Vector(3.4, 6, 0), Vector(3.2, 5.5, -2.8), Vector(2.1, -2, -3.8), Vector(1.2, -4.5, 0), Vector(2.1, -2, 3.8)},
		Gag = {Vector(0.5, 5, 3), Vector(0.5, 7.5, -0.1), Vector(0.5, 5.5, -3), Vector(0, 0, -3.4), Vector(-0.5, -3, 0), Vector(0, 0, 3.4)}
	}

	local function DrawGag(ply)
		if !ply:IsHandcuffed() then return end
		cuff = ply:GetActiveWeapon()
		if !cuff:IsValid() or cuff:GetClass() != "weapon_handcuffed" then return end
		render.SetMaterial(DefaultRope)
		if cuff:GetIsBlind() then
			local pos, ang
			local bone = cuff.Owner:LookupBone(HeadBone)
			if bone then
				local matrix = cuff.Owner:GetBoneMatrix(bone)
				if matrix then
					pos, ang = matrix:GetTranslation(), matrix:GetAngles()
				else
					pos, ang = cuff.Owner:GetBonePosition(bone)
				end
			end
			if pos and ang then
				local firstpos = pos + (ang:Forward() * RenderPos.Blind[1].x) + (ang:Right() * RenderPos.Blind[1].y) + (ang:Up() * RenderPos.Blind[1].z)
				local lastpos = firstpos
				for i=2, #RenderPos.Blind do
					local newpos = pos + (ang:Forward() * RenderPos.Blind[i].x) + (ang:Right() * RenderPos.Blind[i].y) + (ang:Up() * RenderPos.Blind[i].z)
					render.DrawBeam(newpos, lastpos, 1.5, 0, 1, Col.Rope)
					lastpos = newpos
				end
				render.DrawBeam(lastpos, firstpos, 1.5, 0, 1, Col.Rope)
			end
		end
		if cuff:GetIsGagged() then
			local pos, ang
			local bone = cuff.Owner:LookupBone(HeadBone)
			if bone then
				local matrix = cuff.Owner:GetBoneMatrix(bone)
				if matrix then
					pos, ang = matrix:GetTranslation(), matrix:GetAngles()
				else
					pos, ang = cuff.Owner:GetBonePosition(bone)
				end
			end
			if pos and ang then
				local firstpos = pos + (ang:Forward() * RenderPos.Gag[1].x) + (ang:Right() * RenderPos.Gag[1].y) + (ang:Up() * RenderPos.Gag[1].z)
				local lastpos = firstpos
				for i=2, #RenderPos.Gag do
					local newpos = pos + (ang:Forward() * RenderPos.Gag[i].x) + (ang:Right() * RenderPos.Gag[i].y) + (ang:Up() * RenderPos.Gag[i].z)
					render.DrawBeam(newpos, lastpos, 1.5, 0, 1, Col.Rope)
					lastpos = newpos
				end
				render.DrawBeam(lastpos, firstpos, 1.5, 0, 1, Col.Rope)
			end
		end
	end

	hook.Add("NetworkEntityCreated", "Cuffs_All", function(ent)
		if ent:GetClass() == "weapon_handcuffed" then
			if table.IsEmpty(CuffsAll) then
				hook.Add("HUDPaint", "Cuffs_CuffedInteractPrompt", DrawCuffedInteract)
				hook.Add("PostPlayerDraw", "Cuffs_DrawGag", DrawGag)
				hook.Add("PostDrawOpaqueRenderables", "Cuffs_DragRope", DragRope)
				hook.Add("PlayerBindPress", "Cuffs_CuffedInteract", InteractWithCuffed)
			end
			table.insert(CuffsAll, ent)
		end
	end)

	hook.Add("EntityRemoved", "Cuffs_All", function(ent)
		if ent:GetClass() == "weapon_handcuffed" then
			table.RemoveByValue(CuffsAll, ent)
			if table.IsEmpty(CuffsAll) then
				hook.Remove("HUDPaint", "Cuffs_CuffedInteractPrompt")
				hook.Remove("PostPlayerDraw", "Cuffs_DrawGag")
				hook.Remove("PostDrawOpaqueRenderables", "Cuffs_DragRope")
				hook.Remove("PlayerBindPress", "Cuffs_CuffedInteract")
			end
		end
	end)
end