PS.ShopMenu = PS.ShopMenu
PS.ClientsideModels = PS.ClientsideModels or {}
PS.HoverModel = PS.HoverModel
PS.HoverModelCModel = PS.HoverModelCModel
PS.Ragdolls = PS.Ragdolls or {}
PS.LocalData = PS.LocalData or {}
PS.RequestData = PS.RequestData or true

PS.ModelPanelModifications = {
	["models/sal/owl.mdl"] = {
		CamPos = Vector(0.3, 0.3, 0.3),
		LookAt = 10,
	},
	["models/captainbigbutt/skeyler/accessories/glasses02.mdl"] = {
		CamPos = Vector(0.6, 0.6, 0.6),
	},
	["models/fallout 3/polish_beret.mdl"] = {
		CamPos = Vector(0.4, 0.4, 0.4),
		LookAt = 10,
	},
	["models/modified/hat03.mdl"] = {
		CamPos = Vector(0.1, 0.1, 0.1),
		LookAt = 100,
	},
	["models/gmod_tower/aviators.mdl"] = {
		CamPos = Vector(0.6, 0.6, 0.6),
	},
	["models/sal/penguin.mdl"] = {
		CamPos = Vector(0.15, 0.15, 0.15),
		LookAt = 100,
	},
	["models/gmod_tower/starglasses.mdl"] = {
		CamPos = Vector(0.6, 0.6, 0.6),
	},
	["models/sal/acc/fix/beerhat.mdl"] = {
		CamPos = Vector(0.15, 0.15, 0.15),
		LookAt = 100,
	},
	["models/sal/wolf.mdl"] = {
		CamPos = Vector(0.3, 0.3, 0.3),
		LookAt = 10,
	},
	["models/sal/acc/armor01.mdl"] = {
		CamPos = Vector(0.2, 0.1, 0.3),
		LookAt = 1.5,
	},
	["models/modified/backpack_1.mdl"] = {
		CamPos = Vector(0.175, 0.175, 0.175),
		LookAt = 100,
	},
	["models/modified/backpack_2.mdl"] = {
		CamPos = Vector(0.175, 0.175, 0.175),
		LookAt = 100,
	},
	["models/gmod_tower/3dglasses.mdl"] = {
		CamPos = Vector(0.6, 0.6, 0.6),
	},
	["models/modified/backpack_3.mdl"] = {
		CamPos = Vector(0.175, 0.175, 0.175),
		LookAt = 100,
	},
	["models/modified/bandana.mdl"] = {
		CamPos = Vector(0.175, 0.175, 0.175),
		LookAt = 100,
	},
}

local IsValid = IsValid

local function RequestData()
	net.Start("PS_AskForInformation")
	net.SendToServer()
end

hook.Add("InitPostEntity", "PS_ReceiveInformation", function()
	hook.Remove("InitPostEntity", "PS_ReceiveInformation")

	RequestData()

	timer.Create("PS_ReceiveInformation", 10, 0, function()
		if PS.RequestData then
			RequestData()
		else
			timer.Remove("PS_ReceiveInformation")
		end
	end)
end)

net.Receive("PS_SendAllClientsideModels", function(len)
	local leng = net.ReadUInt(16)
	local itms = net.ReadData(leng)
	itms = util.JSONToTable(util.Decompress(itms))

	PS.RequestData = false

	for userid, items in pairs(itms) do
		ply = Player(userid)
		if !IsValid(ply) then
			PS.RequestData = true
			continue
		end
		for _, item_id in pairs(items) do
			if PS.Items[item_id] then
				ply:PS_AddClientsideModel(item_id)
			end
		end
	end
end)

function PS:CloseAllExtraMenus()
	if IsValid(PS_Background) then
		PS_Background:Remove()
	end
	if IsValid(PS_ColorPanel) then
		PS_ColorPanel:Remove()
	end
	if IsValid(PS_SkinPanel) then
		PS_SkinPanel:Remove()
	end
	CloseDermaMenus()
end

function PS:ToggleMenu(npc)
	PS:CloseAllExtraMenus()
	local shop_menu = PS.ShopMenu
	if !IsValid(shop_menu) then
		PS.CurrentNPC = npc
		local windowed = PS.Style_Config.Windowed
		local bg
		if windowed then
			bg = vgui.Create("DFrame")
			bg:SetTitle("")
			bg:ShowCloseButton(false)
			bg:SetDraggable(false)
			bg:SetSize(ScrW() * PS.Style_Config.WindowSize[1] + 20, ScrH() * PS.Style_Config.WindowSize[2] + 20)
			bg:Center()
			bg:ParentToHUD()
			bg.Paint = function(slf, w, h)
				if !IsValid(PS.ShopMenu) then
					bg:Remove()
				else
					surface.SetDrawColor(PS.Style_Config.BGCol.MainTitle)
					surface.DrawRect(0, 0, w, h)
					surface.SetDrawColor(PS.Style_Config.Col.MN.BottomLine)
					surface.DrawRect(10, h - 10, w - 20, 1) -- bottom
					surface.DrawRect(9, 89, 1, h - 98) -- left
					surface.DrawRect(w - 10, 89, 1, h - 98) -- right
				end
			end
		end

		PS.ShopMenu = vgui.Create("DPointShopMenu")
		shop_menu = PS.ShopMenu
		shop_menu["npc"] = npc
		shop_menu:MakePopup()
		shop_menu:ParentToHUD()
		shop_menu.OnRemove = function(slf)
			if IsValid(bg) then
				bg:Remove()
			end
		end
		local ply = LocalPlayer()
		shop_menu.Think = function()
			if !IsValid(npc) or !ply:Alive() or ply:GetPos():DistToSqr(npc:GetPos()) > 62500 then
				PS.CurrentNPC = nil
				PS:CloseMenu()
			end
		end

		hook.Run("PS_OnToggleMenu", self, true)
	else
		shop_menu:Remove()
		hook.Run("PS_OnToggleMenu", self, false)
	end
end

function PS:CloseMenu()
	PS:CloseAllExtraMenus()
	if IsValid(PS.ShopMenu) then
		PS.ShopMenu:Remove()
	end
	hook.Run("PS_OnCloseMenu", self)
end

local PS_InfoPanel
function PS:ShowNotice(text)
	if IsValid(PS_InfoPanel) then PS_InfoPanel:Remove() end	
	PS_InfoPanel = vgui.Create("DPanel", PS.ShopMenu)
	PS_InfoPanel:SetSize(ScrW(), ScrH())
	PS_InfoPanel:MakePopup()
	PS_InfoPanel.Paint = function(slf, w, h)
		surface.SetDrawColor(Color(0, 0, 0, 200))
		surface.DrawRect(0, 0, w, h)
		draw.SimpleText(text, "PS_TrebOut_S35", ScrW() / 2, ScrH() / 2 - 100, color_white, TEXT_ALIGN_CENTER)
	end
	PS_InfoPanel.Think = function(slf)
	end
	local Button_Okay = vgui.Create("PS_DSWButton", PS_InfoPanel)
	Button_Okay:SetPos(PS_InfoPanel:GetWide() / 2 - 100, PS_InfoPanel:GetTall() / 3 * 2 - 100)
	Button_Okay:SetSize(200, 30)
	Button_Okay.Font = "PS_TrebOut_S25"
	Button_Okay:SetTexts("In Ordnung")
	Button_Okay.Click = function(slf)
		PS_InfoPanel:Remove()
	end
end

local PS_AskPanel
function PS:ShowAsker(txt, callback, denycallback)
	if IsValid(PS_AskPanel) then PS_AskPanel:Remove() end	
	PS_AskPanel = vgui.Create("DPanel", PS.ShopMenu)
	PS_AskPanel:SetSize(ScrW(), ScrH())
	PS_AskPanel:MakePopup()
	PS_AskPanel.Paint = function(self, w, h)
		surface.SetDrawColor(Color(0, 0, 0, 200))
		surface.DrawRect(0, 0, w, h)
		draw.SimpleText(txt, "PS_TrebOut_S35", ScrW() / 2, ScrH() / 2  - 100, color_white, TEXT_ALIGN_CENTER)
	end
	local Button_Accept = vgui.Create("PS_DSWButton", PS_AskPanel)
	Button_Accept:SetPos(PS_AskPanel:GetWide() / 2 - 220, PS_AskPanel:GetTall() / 3 * 2 - 100)
	Button_Accept:SetSize(200, 30)
	Button_Accept.Font = "PS_TrebOut_S25"
	Button_Accept:SetTexts("Ja")
	Button_Accept.Click = function()
		PS_AskPanel:Remove()
		if callback then
			callback()
		end
	end
	local Button_Decline = vgui.Create("PS_DSWButton", PS_AskPanel)
	Button_Decline:SetPos(PS_AskPanel:GetWide() / 2 + 20, PS_AskPanel:GetTall() / 3 * 2 - 100)
	Button_Decline:SetSize(200, 30)
	Button_Decline.Font = "PS_TrebOut_S25"
	Button_Decline:SetTexts("Nein")
	Button_Decline.Click = function()
		PS_AskPanel:Remove()
		if denycallback then
			denycallback()
		end
	end
end

local PS_AskPanel
function PS:ShowSearch(txt, callback, denycallback, alphabetic, value)
	if IsValid(PS_AskPanel) then PS_AskPanel:Remove() end	
	PS_AskPanel = vgui.Create("DFrame", PS.ShopMenu)
	PS_AskPanel:SetSize(ScrW(), ScrH())
	PS_AskPanel:SetTitle("")
	PS_AskPanel:ShowCloseButton(false)
	PS_AskPanel:SetDraggable(false)
	PS_AskPanel:MakePopup()
	PS_AskPanel.Paint = function(self, w, h)
		surface.SetDrawColor(Color(0, 0, 0, 200))
		surface.DrawRect(0, 0, w, h)
		draw.SimpleText(txt, "PS_TrebOut_S35", ScrW() / 2, ScrH() / 2  - 150, color_white, TEXT_ALIGN_CENTER)
	end
	local TextEntry = vgui.Create("DTextEntry", PS_AskPanel)
	TextEntry:SetPos(PS_AskPanel:GetWide() / 2 - 220, PS_AskPanel:GetTall() / 3 * 2 - 180)
	TextEntry:SetSize(440, 30)
	TextEntry:SetFont("PS_Treb_S25")
	if !alphabetic then 
		TextEntry:SetNumeric(true)
	end
	TextEntry:SetValue(value or "")
	TextEntry:RequestFocus()
	local Button_Accept = vgui.Create("PS_DSWButton", PS_AskPanel)
	Button_Accept:SetPos(PS_AskPanel:GetWide() / 2 - 220, PS_AskPanel:GetTall() / 3 * 2 - 100)
	Button_Accept:SetSize(200, 30)
	Button_Accept.Font = "PS_TrebOut_S25"
	Button_Accept:SetTexts("Bestätigen")
	Button_Accept.Click = function()
		PS_AskPanel:Remove()
		if callback then
			callback(TextEntry:GetValue())
		end
	end
	local Button_Decline = vgui.Create("PS_DSWButton", PS_AskPanel)
	Button_Decline:SetPos(PS_AskPanel:GetWide() / 2 + 20, PS_AskPanel:GetTall() / 3 * 2 - 100)
	Button_Decline:SetSize(200, 30)
	Button_Decline.Font = "PS_TrebOut_S25"
	Button_Decline:SetTexts("Abbrechen")
	Button_Decline.Click = function()
		PS_AskPanel:Remove()
		if denycallback then
			denycallback(TextEntry:GetValue())
		end
	end
end

function PS:SetHoverItem(item_id)
	local ITEM = PS.Items[item_id]
	local model = ITEM.Model
	if model then
		self.HoverModel = item_id
		self.HoverModelCModel = ClientsideModel(model, ITEM.RenderGroup or RENDERGROUP_OPAQUE)
		if !IsValid(self.HoverModelCModel) then return end
		self.HoverModelCModel:SetNoDraw(true)
	end
end

function PS:RemoveHoverItem()
	self.HoverModel = nil
	if IsValid(self.HoverModelCModel) then
		self.HoverModelCModel:Remove()
	end
	self.HoverModelCModel = nil
end

function PS:ShowColorChooser(item, modifications)
	local chooser = vgui.Create("DPointShopColorChooser")
	chooser:SetColor(modifications.color)
	chooser.OnChoose = function(color)
		modifications.color = color
		self:SendModifications(item.ID, modifications)
	end
end

function PS:ShowSkinChooser(item, modifications)
	local chooser = vgui.Create("DPointShopSkinChooser")
	chooser:SetModel(item.Model)
	if modifications.skin then
		chooser:SetSkin(modifications.skin)
	end
	if modifications.bodygroups then
		for k,v in pairs(modifications.bodygroups) do
			chooser:SetBodygroup(k, v)
		end
	end
	chooser.OnChoose = function(bodygroups, skin)
		modifications.skin = skin
		modifications.bodygroups = bodygroups
		self:SendModifications(item.ID, modifications)
	end
end

function PS:SendModifications(item_id, modifications)
	net.Start("PS_ModifyItem")
		net.WriteEntity(PS.CurrentNPC)
		net.WriteString(item_id)
		net.WriteTable(modifications)
	net.SendToServer()
end

net.Receive("PS_ToggleMenu", function(len)
	PS:ToggleMenu(net.ReadEntity())
end)

net.Receive("PS_SendItems", function(len)
	if !PS.Loaded then
		PS:LoadItems()
	end

	local leng = net.ReadUInt(16)
	local items = net.ReadData(leng)
	items = util.JSONToTable(util.Decompress(items))

	PS.LocalData = PS:ValidateItems(items)

	hook.Run("PS_ItemsAdjusted")
end)

net.Receive("PS_AddClientsideModel", function(len)
	local ply = net.ReadEntity()
	if !IsValid(ply) then return end
	local item_id = net.ReadString()

	ply:PS_AddClientsideModel(item_id)
end)

net.Receive("PS_RemoveClientsideModel", function(len)
	local ply = net.ReadEntity()
	if !IsValid(ply) then return end
	local item_id = net.ReadString()

	ply:PS_RemoveClientsideModel(item_id)
end)

net.Receive("PS_SendClientsideModels", function(len)
	local ply = net.ReadEntity()
	if !IsValid(ply) then return end
	local leng = net.ReadUInt(16)
	local items = net.ReadData(leng)
	items = util.JSONToTable(util.Decompress(items))

	ply:PS_RemoveAllClientsideModels()

	for _, item_id in pairs(items) do
		if PS.Items[item_id] then
			ply:PS_AddClientsideModel(item_id)
		end
	end
end)

net.Receive("PS_SendNotice", function(len)
	local str = net.ReadString()
	PS:ShowNotice(str)
end)

local reg = debug.getregistry()

local GetNoDraw = reg.Entity.GetNoDraw
local IsValid_ent = reg.Entity.IsValid

local LookupAttachment = reg.Entity.LookupAttachment
local GetAttachment = reg.Entity.GetAttachment

local LookupBone = reg.Entity.LookupBone
local GetBoneMatrix = reg.Entity.GetBoneMatrix
local GetBonePosition = reg.Entity.GetBonePosition

local GetTranslation = reg.VMatrix.GetTranslation
local GetAngles = reg.VMatrix.GetAngles

local SetRenderOrigin = reg.Entity.SetRenderOrigin
local SetRenderAngles = reg.Entity.SetRenderAngles
local SetupBones = reg.Entity.SetupBones
local DrawModel = reg.Entity.DrawModel

local function PostPlayerDraw(ply)
	local cl_models = PS.ClientsideModels[ply]
	if !cl_models or GetNoDraw(ply) then return end
	for item_id, model in pairs(cl_models) do
		if !IsValid_ent(model) then continue end
		local ITEM = PS.Items[item_id]
		local item_attachment = ITEM.Attachment
		local item_bone = ITEM.Bone
		if !item_attachment and !item_bone then
			PS.ClientsideModels[ply][item_id] = nil
			continue
		end
		local pos, ang
		if item_attachment then
			local attach_id = LookupAttachment(ply, item_attachment)
			if !attach_id then continue end
			local attach = GetAttachment(ply, attach_id)
			if !attach then continue end
			pos = attach.Pos
			ang = attach.Ang
		else
			local bone_id = LookupBone(ply, item_bone)
			if !bone_id then continue end
			local matrix = GetBoneMatrix(ply, bone_id)
			if matrix then
				pos = GetTranslation(matrix)
				ang = GetAngles(matrix)
			else
				pos, ang = GetBonePosition(ply, bone_id)
			end
		end
		if !pos and !ang then continue end
		model, pos, ang = ITEM:ModifyClientsideModel(ply, model, pos, ang)
		if ITEM.Skin and !model.SkinSet then
			model:SetSkin(ITEM.Skin)
			model.SkinSet = ITEM.Skin
		end
		SetRenderOrigin(model, pos)
		SetRenderAngles(model, ang)
		SetupBones(model)
		DrawModel(model)
		ITEM:PostModifyClientsideModel(ply, model, pos, ang)
	end
end
hook.Add("PostPlayerDraw", "PS_PostPlayerDraw", PostPlayerDraw)

hook.Add("NetworkEntityCreated", "PS_NetworkEntityCreated", function(ent)
	local class = ent:GetClass()
	if class == "prop_ragdoll" then
		PS.Ragdolls[ent] = true
	elseif class == "class C_HL2MPRagdoll" then
		PS.Ragdolls[ent] = true
	end
end)

local function CreateProceduralEntity(ent, tb, ITEM)
	local model = tb.PS_Models[ITEM.Model]
	if IsValid(model) then return model end
	local model = ClientsideModel(ITEM.Model, ITEM.RenderGroup or RENDERGROUP_OPAQUE)
	if IsValid(model) then
		model:SetNoDraw(true)
		ent:CallOnRemove(tostring(model:EntIndex()), function()
			if IsValid(model) then
				model:Remove()
			end
		end)
		tb.PS_Models[ITEM.ID] = model
	end
	return model
end

local local_ply
hook.Add("PostDrawOpaqueRenderables", "PS_PostDrawOpaqueRenderables", function(depth, sky)
	local_ply = local_ply or LocalPlayer()
	for ent in pairs(PS.Ragdolls) do
		if !IsValid(ent) then
			PS.Ragdolls[ent] = nil
			continue
		end
		if local_ply:GetObserverTarget() == ent and local_ply:GetObserverMode() == OBS_MODE_IN_EYE then
			continue
		end
		if ent:IsDormant() or ent:GetNoDraw() or ent:GetPos():DistToSqr(local_ply:EyePos()) > 1000000 then
			continue
		end
		local prop_ragdoll = ent:GetClass() == "prop_ragdoll"
		local owner = prop_ragdoll and ent:GetDTEntity(0) or ent:GetRagdollOwner()
		if owner:IsValid() and owner:IsPlayer() then
			if prop_ragdoll and owner == local_ply then continue end
			local tb = ent:GetTable()
			tb.PS_Models = tb.PS_Models or (PS.ClientsideModels[owner] and table.Copy(PS.ClientsideModels[owner]))
			local cl_models = tb.PS_Models
			if !cl_models then return end
			for item_id, model in pairs(cl_models) do
				local ITEM = PS.Items[item_id]
				if !IsValid(model) then
					model = CreateProceduralEntity(ent, tb, ITEM)
					if !IsValid(model) then continue end
				end
				local item_attachment = ITEM.Attachment
				local item_bone = ITEM.Bone
				if !item_attachment and !item_bone then
					continue
				end
				local pos, ang
				if item_attachment then
					local attach_id = LookupAttachment(ent, item_attachment)
					if !attach_id then continue end
					local attach = GetAttachment(ent, attach_id)
					if !attach then continue end
					pos = attach.Pos
					ang = attach.Ang
				else
					local bone_id = LookupBone(ent, item_bone)
					if !bone_id then continue end
					local matrix = GetBoneMatrix(ent, bone_id)
					if matrix then
						pos = GetTranslation(matrix)
						ang = GetAngles(matrix)
					else
						pos, ang = GetBonePosition(ent, bone_id)
					end
				end
				if !pos and !ang then continue end
				model, pos, ang = ITEM:ModifyClientsideModel(ent, model, pos, ang)
				model:SetRenderOrigin(pos)
				model:SetRenderAngles(ang)
				model:SetupBones()
				model:DrawModel()
				ITEM:PostModifyClientsideModel(ent, model, pos, ang)
			end
		end
	end
end)