zrush = zrush or {}
zrush.f = zrush.f or {}
zrush.utility = zrush.utility or {}

local EntityActionClasses = {
	["zrush_drilltower"] = true,
	["zrush_burner"] = true,
	["zrush_pump"] = true,
	["zrush_refinery"] = true
}

hook.Add("canLockpick", "a.zrush.LockpickUtility", function(ply, ent)
	if IsValid(ent) and EntityActionClasses[ent:GetClass()] and ent.PhysgunDisabled then
		return true
	end
end)

local stealtime = 20
hook.Add("lockpickTime", "a.zrush.LockpickUtility", function(ply, ent)
	if IsValid(ent) and EntityActionClasses[ent:GetClass()] and ent.PhysgunDisabled then
		return stealtime
	end
end, HOOK_MONITOR_HIGH or -2)

if SERVER then
	hook.Add("onLockpickCompleted", "a.zrush.LockpickUtility", function(ply, success, ent)
		if !IsValid(ent) or !success then return end
		local class = ent:GetClass()
		if !EntityActionClasses[class] or !ent.PhysgunDisabled then return end
		if class == "zrush_drilltower" then
			zrush.f.DrillTower_DeConstruct(ent)
		elseif class == "zrush_burner" then
			zrush.f.Burner_DetachBurner(ent)
		elseif class == "zrush_pump" then
			zrush.f.Pump_DetachPump(ent)
		else
			zrush.f.Refinery_DeConstruct(ent)
		end
		hook.Run("zrush_LockpickMachine", ply, ent)
	end)
end

////////////////////////////////////////////
/////////////// DEFAULT ////////////////////
////////////////////////////////////////////

// These are the attachment points IDs the machine have
zrush.utility.MachineSockets = zrush.utility.MachineSockets or {}
zrush.utility.MachineSockets["Drill"] = {1, 2, 3}
zrush.utility.MachineSockets["Burner"] = {1, 2, 3}
zrush.utility.MachineSockets["Pump"] = {1, 2, 3}
zrush.utility.MachineSockets["Refinery"] = {1, 2, 3, 4}

if SERVER then
	// Basic notify function
	function zrush.f.Notify(ply, msg, ntfType)
		if IsValid(ply) then
			DarkRP.notify(ply, ntfType, 8, msg)
		end
	end

	//Used to fix the Duplication Glitch
	function zrush.f.CollisionCooldown(ent)
		if ent.zrush_CollisionCooldown == nil then
			ent.zrush_CollisionCooldown = true

			timer.Simple(0.1,function()
				if IsValid(ent) then
					ent.zrush_CollisionCooldown = false
				end
			end)

			return false
		else
			if ent.zrush_CollisionCooldown then
				return true
			else
				ent.zrush_CollisionCooldown = true

				timer.Simple(0.1,function()
					if IsValid(ent) then
						ent.zrush_CollisionCooldown = false
					end
				end)
				return false
			end
		end
	end

	// This updates the Machine UI
	util.AddNetworkString("zrush_UpdateMachineUI_net")
	function zrush.f.UpdateMachineUI(ent, UpdateShop)
		timer.Simple(0.1, function()
			if IsValid(ent) then
				zrush.f.Debug("zrush.f.UpdateMachineUI")

				net.Start("zrush_UpdateMachineUI_net")
				net.WriteEntity(ent)
				net.WriteTable(zrush.f.Machine_ReturnInstalledModules(ent))
				net.WriteBool(UpdateShop)
				net.Broadcast()
			end
		end)
	end

	// This Updates the Machine Sound
	util.AddNetworkString("zrush_UpdateMachineSound_net")
	function zrush.f.UpdateMachineSound(ent)
		timer.Simple(0.1, function()
			if (IsValid(ent)) then
				net.Start("zrush_UpdateMachineSound_net")
				net.WriteEntity(ent)
				net.SendPVS(ent:GetPos())
			end
		end)
	end

	// This gets called when we change the state of a entity
	function zrush.f.SetMachineState(state, ent)
		if (state ~= ent:GetState()) then
			zrush.f.Debug("(" .. ent:EntIndex() .. ") Machine(" .. ent:GetClass() .. ") State: " .. state)
			ent:SetState(state)
		end
	end

	// This creates a explosion for a entity
	function zrush.f.EntityExplosion(ent, EntityID, DoesDamage)
		if ent.Exploded then return end
		ent.Exploded = true

		zrush.f.Timer_Remove("zrush_working_" .. ent:EntIndex())
		zrush.f.Timer_Remove("zrush_explosiontimer_" .. ent:EntIndex())

		local pos = ent:GetPos()
		if (DoesDamage) then
			util.BlastDamage(ent, ent, pos, zrush.config.Machine[EntityID].OverHeat_Radius or 100, zrush.config.Machine[EntityID].OverHeat_Damage or 25)
		end
		util.ScreenShake(pos, 256, 255, 1.5, 150)
		local edata = EffectData()
		edata:SetOrigin(pos)
		util.Effect(ent:WaterLevel() > 1 and "WaterSurfaceExplosion" or "Explosion", edata, true, true)
		util.Decal("Scorch", pos, pos - Vector(0, 0, 250), ent)

		ent:Remove()
	end

	// A function for created module sockets
	function zrush.f.CreateSockets(ent)
		local count = math.Clamp(zrush.config.Machine[ent.MachineID].Module_Sockets, 1, table.Count(zrush.utility.MachineSockets[ent.MachineID]))
		ent.ModuleSocket = ent.ModuleSocket or {}

		for i = 1, count do
			local aPoint = zrush.utility.MachineSockets[ent.MachineID][i]

			local atable = {
				ModuleID = -1,
				Module = nil,
				attachpoint = aPoint
			}

			table.insert(ent.ModuleSocket, atable)
		end
	end
else
	local zrush_fuelbuyer_notifictations = {}
	local zrush_interface_notifictations = {}

	// This moves our notify
	local function NotificationMover()
		if #zrush_interface_notifictations <= 0 then
			hook.Remove("Think", "a.zrush.NotificationsMover")
			return
		end
		for k, v in ipairs(zrush_interface_notifictations) do
			if (IsValid(v) and v.y > 80) then
				v:SetPos(wMod * 360, hMod * (v.y - (0.25 * FrameTime() ) ) )
			end
		end
	end

	// This creates a custom fuel buyer notification
	function zrush.f.FuelBuyerNotify(npc, msg, time)
		local wMod = ScrW() / 1920
		local hMod = ScrH() / 1080

		local npcName = zrush.config.FuelBuyer.Name
		local NotifyPanel
		// Notification panel
		NotifyPanel = vgui.Create("DNotify")
		NotifyPanel:SetPos(wMod * 355, hMod * 240)
		NotifyPanel:SetSize(200 * wMod, 400 * hMod)
		NotifyPanel:SetLife(time)
		NotifyPanel:SizeToContentsY(3)
		NotifyPanel:ParentToHUD()
		table.insert(zrush_fuelbuyer_notifictations, NotifyPanel)
		// Gray background panel
		local bg = vgui.Create("DPanel", NotifyPanel)
		bg:Dock(FILL)
		bg:SetBackgroundColor(Color(64, 64, 64))
		bg.Paint = function(self, w, h)
			surface.SetDrawColor(125, 125, 125, 255)
			surface.SetMaterial(zrush.default_materials["ui_machineshop_panel"])
			surface.DrawTexturedRect(0, 0, w, h)
		end
		// Image ( parented to background panel )
		local img = vgui.Create("SpawnIcon", bg)
		img:SetPos(11 * wMod, 11 * hMod)
		img:SetSize(177 * wMod, 177 * hMod)
		img:SetModel(zrush.config.FuelBuyer.NotifyImage)
		img:SetMouseInputEnabled(false)
		// A label message ( parented to background panel )
		local lbl = vgui.Create("DLabel", bg)
		lbl:SetPos(11 * wMod, 190 * hMod)
		lbl:SetSize(128 * wMod, 50 * hMod)
		lbl:SetText(npcName .. ": ")
		lbl:SetTextColor(Color(125, 125, 255))
		lbl:SetFont("zrush_npc_font04")
		lbl:SetWrap(true)
		lbl:SetContentAlignment(7)
		// A label message ( parented to background panel )
		local lblmsg = vgui.Create("DLabel", bg)
		lblmsg:SetPos(11 * wMod, 220 * hMod)
		lblmsg:SetSize(190 * wMod, 300 * hMod)
		lblmsg:SetText(msg)
		lblmsg:SetTextColor(zrush.default_colors["white01"])
		lblmsg:SetFont("zrush_npc_font05")
		lblmsg:SetWrap(true)
		lblmsg:SetContentAlignment(7)
		// Add the background panel to the notification
		NotifyPanel:AddItem(bg)
	end

	// This destroys all active notifications
	function zrush.f.FuelBuyerNotify_RemoveAll()
		for k, v in ipairs(zrush_fuelbuyer_notifictations) do
			if (IsValid(v)) then
				v:Remove()
			end
		end
		for k, v in ipairs(zrush_interface_notifictations) do
			if (IsValid(v)) then
				v:Remove()
			end
		end
	end

	net.Receive("zrush_UpdateMachineSound_net", function(len)
		local ent = net.ReadEntity()

		if IsValid(ent) and zrush.f.FunctionValidater(ent.UpdateSoundInfo) then
			ent:UpdateSoundInfo()
		end
	end)

	// This adds a Custom Notify for the Machine Interface
	function zrush.f.InterfaceNotify(msg, time,type)
		local wMod = ScrW() / 1920
		local hMod = ScrH() / 1080

		local NotifyPanel

		NotifyPanel = vgui.Create("DNotify")
		NotifyPanel:SetPos(wMod * 360, hMod * 550)
		NotifyPanel:SetSize(1200 * wMod, 50 * hMod)
		NotifyPanel:SetLife(time)
		NotifyPanel:SizeToContentsX(3)
		NotifyPanel:SizeToContentsY(3)

		table.insert(zrush_interface_notifictations, NotifyPanel)
		hook.Add("Think", "a.zrush.NotificationsMover", NotificationMover)

		local bg = vgui.Create("DPanel", NotifyPanel)
		bg:Dock(FILL)
		bg:SetBackgroundColor(Color(55, 55, 55))

		local lblmsg

		if (type == 0) then
			surface.PlaySound("common/warning.wav")

			local lbl = vgui.Create("DLabel", bg)
			lbl:SetPos(11 * wMod, 5 * hMod)
			lbl:SetSize(300 * wMod, 100 * hMod)
			lbl:SetText("Attention! ")
			lbl:SetTextColor(Color(255, 125, 125))
			lbl:SetFont("zrush_notify_font01")
			lbl:SetWrap(true)
			lbl:SetContentAlignment(7)
			lblmsg = vgui.Create("DLabel", bg)
			lblmsg:SetPos(200 * wMod, 5 * hMod)
			lblmsg:SetSize(1500 * wMod, 300 * hMod)
			lblmsg:SetText(msg)
			lblmsg:SetTextColor(zrush.default_colors["white01"])
			lblmsg:SetFont("zrush_notify_font01")
			lblmsg:SetWrap(true)
			lblmsg:SetContentAlignment(7)
		elseif (type == 1) then
			surface.PlaySound("common/bugreporter_succeeded.wav")

			local lbl = vgui.Create("DLabel", bg)
			lbl:SetPos(11 * wMod, 5 * hMod)
			lbl:SetSize(300 * wMod, 100 * hMod)
			lbl:SetText("Info! ")
			lbl:SetTextColor(Color(255, 255, 125))
			lbl:SetFont("zrush_notify_font01")
			lbl:SetWrap(true)
			lbl:SetContentAlignment(7)

			lblmsg = vgui.Create("DLabel", bg)
			lblmsg:SetPos(100 * wMod, 5 * hMod)
			lblmsg:SetSize(1500 * wMod, 300 * hMod)
			lblmsg:SetText(msg)
			lblmsg:SetTextColor(zrush.default_colors["white01"])
			lblmsg:SetFont("zrush_notify_font01")
			lblmsg:SetWrap(true)
			lblmsg:SetContentAlignment(7)
		end

		NotifyPanel:AddItem(bg)
	end

	function zrush.f.LoopedSound(ent, soundfile, shouldplay, pitch)
		if shouldplay and zrush.f.InDistance(LocalPlayer():GetPos(), ent:GetPos(), 1000) then
			if ent.Sounds == nil then
				ent.Sounds = {}
			end

			if ent.Sounds[soundfile] == nil then
				ent.Sounds[soundfile] = CreateSound(ent, soundfile)
			end

			// If the sound is not playing or it should be updated then start/restart the sound
			if ent.Sounds[soundfile]:IsPlaying() == false or ent.UpdateSound then
				ent.Sounds[soundfile]:Play()
				ent.Sounds[soundfile]:ChangeVolume(1, 0)
				ent.Sounds[soundfile]:ChangePitch(pitch, 1)

				ent.UpdateSound = false
			end
		else
			if ent.Sounds == nil then
				ent.Sounds = {}
			end

			if ent.Sounds[soundfile] ~= nil and ent.Sounds[soundfile]:IsPlaying() == true then
				ent.Sounds[soundfile]:ChangeVolume(0, 0)
				ent.Sounds[soundfile]:Stop()
				ent.Sounds[soundfile] = nil
			end
		end
	end

	// Used for Color Lerp
	function zrush.f.LerpColor(t, c1, c2)
		local c3 = Color(0, 0, 0)
		c3.r = Lerp(t, c1.r, c2.r)
		c3.g = Lerp(t, c1.g, c2.g)
		c3.b = Lerp(t, c1.b, c2.b)
		c3.a = Lerp(t, c1.a, c2.a)

		return c3
	end
end

// Tells us if the function is valid
function zrush.f.FunctionValidater(func)
	if (type(func) == "function") then return true end
	return false
end

// Does the player have the correct Rank?
function zrush.f.HasAllowedRank(ply, AllowedRanks)
	if IsValid(ply) and AllowedRanks then
		if !table.IsEmpty(AllowedRanks) then
			if AllowedRanks[zrush.f.GetPlayerRank(ply)] then
				return true
			else
				return false
			end
		else
			return true
		end
	else
		return false
	end
end

// Does the player have the correct Job?
function zrush.f.HasAllowedJob(ply, AllowedJobs)
	if IsValid(ply) and AllowedJobs then
		if !table.IsEmpty(AllowedJobs) then
			return AllowedJobs[zrush.f.GetPlayerJobName(ply)]
		else
			return true
		end
	else
		return false
	end
end

// Checks if the distance between pos01 and pos02 is smaller then dist
local dist_check = {}
function zrush.f.InDistance(pos01, pos02, dist)
	dist_check[dist] = dist_check[dist] or dist * dist
	return pos01:DistToSqr(pos02) < dist_check[dist]
end

// Used for Debug
function zrush.f.Debug(mgs)
	if (zrush.config.Debug) then
		if istable(mgs) then
			print("[	DEBUG	] Table Start >")
			PrintTable(mgs)
			print("[	DEBUG	] Table End <")
		else
			print("[	DEBUG	] " .. mgs)
		end
	end
end

// Used to concat the keys of a table
function zrush.f.KeyTableConcat(_tbl, concator)
	local str = ""

	local count = 1
	for k, v in pairs(_tbl) do
		str = str .. tostring(k)
		if count < table.Count(_tbl) then
			str = str .. concator
		end
		count = count + 1
	end

	return str
end
////////////////////////////////////////////
////////////////////////////////////////////





////////////////////////////////////////////
///////////////// OWNER ////////////////////
////////////////////////////////////////////
if SERVER then
	// This saves the owners SteamID
	function zrush.f.SetOwner(ent, ply)
		if IsValid(ply) then

			ent:Setowning_ent(ply)
		end
	end
end

// This checks if the player is a admin
function zrush.f.IsAdmin(ply)
	if IsValid(ply) then
		if MG_DeveloperGroups[zrush.f.GetPlayerRank(ply)] then
			return true
		else
			return false
		end
	else
		return false
	end
end

// This returns the owner
function zrush.f.GetOwner(ent)
	if IsValid(ent) then
		local ply = ent:Getowning_ent()
			if IsValid(ply) then
			return ply
		else
			return false
		end
	else
		return false
	end
end

// This returns true if the input is the owner
function zrush.f.IsOwner(ply, ent)
	if IsValid(ent) and IsValid(ply) then
		if ent.Getowning_ent and ply == ent:Getowning_ent() then
			return true
		else
			return false
		end
	else
		return false
	end
end
////////////////////////////////////////////
////////////////////////////////////////////



////////////////////////////////////////////
///////////////// Timer ////////////////////
////////////////////////////////////////////
if zrush_TimerList == nil then
	zrush_TimerList = {}
end

function zrush.f.Timer_Create(timerid, time, rep, func)
	if zrush.f.FunctionValidater(func) then
		timer.Create(timerid, time, rep, func)
		table.insert(zrush_TimerList, timerid)
	end
end

function zrush.f.Timer_Remove(timerid)
	if timer.Exists(timerid) then
		timer.Remove(timerid)
		table.RemoveByValue(zrush_TimerList, timerid)
	end
end
////////////////////////////////////////////
////////////////////////////////////////////



////////////////////////////////////////////
////////////// Rank / Job //////////////////
////////////////////////////////////////////
// Returns the player rank / usergroup
function zrush.f.GetPlayerRank(ply)
	if SG then
		return ply:GetSecondaryUserGroup() or ply:GetUserGroup()
	else
		return ply:GetUserGroup()
	end
end

// Returns the players job
function zrush.f.GetPlayerJob(ply)
	return ply:Team()
end

// Returns the players job
function zrush.f.GetPlayerJobName(ply)
	return team.GetName( zrush.f.GetPlayerJob(ply) )
end
////////////////////////////////////////////
////////////////////////////////////////////



////////////////////////////////////////////
//////////////// CUSTOM ////////////////////
////////////////////////////////////////////

function zrush.f.CatchMachinesByModuleType(m_type)
	local machines = {}

	if (m_type == "speed") then
		machines = {
			["Drill"] = true,
			["Burner"] = true,
			["Pump"] = true,
			["Refinery"] = true,
		}
	elseif (m_type == "production") then
		machines = {
			["Burner"] = true,
			["Pump"] = true,
			["Refinery"] = true,
		}
	elseif (m_type == "antijam") then
		machines = {
			["Drill"] = true,
			["Pump"] = true,
		}
	elseif (m_type == "cooling") then
		machines = {
			["Burner"] = true,
			["Refinery"] = true,
		}
	elseif (m_type == "refining") then
		machines = {
			["Refinery"] = true,
		}
	elseif (m_type == "pipes") then
		machines = {
			["Drill"] = true,
		}
	end

	return machines
end

// This returns the module with the specified ID
function zrush.f.FindModuleDataByType(atype)
	local tModule

	for k, v in pairs(zrush.AbilityModules) do
		if (v.type == atype) then
			tModule = v
			break
		end
	end

	return tModule
end

// This returns the machine with the specified MachineID
function zrush.f.FindMachineDataByID(MachineID)
	local tMachine

	for k, v in pairs(zrush.MachineShop) do
		if (v.machineID == MachineID) then
			tMachine = v
			break
		end
	end

	return tMachine
end

// This returns the total sell value of the machine
function zrush.f.ReturnMachineCrateValue(machinecrate, InstalledModules)
	local totalValue = 0

	if (InstalledModules and !table.IsEmpty(InstalledModules)) then
		for k, v in pairs(InstalledModules) do
			local mData = zrush.AbilityModules[v]
			local earning = mData.price * zrush.config.SellValue
			totalValue = totalValue + earning
		end
	end

	local machineData = zrush.f.FindMachineDataByID(machinecrate:GetMachineID())
	totalValue = totalValue + machineData.price * zrush.config.SellValue

	return totalValue
end

// This returns the Final Boost Value
function zrush.f.ReturnBoostValue(machineID, BoostType, machine)
	if !IsValid(machine) then return end
	local boostValue = 0

	if BoostType == "speed" and zrush.f.FunctionValidater(machine.GetSpeedBoost) then
		boostValue = math.Clamp(zrush.config.Machine[machineID].Speed * (1 - machine:GetSpeedBoost()), 0.1, 100)
	elseif BoostType == "production" and zrush.f.FunctionValidater(machine.GetProductionBoost) then
		boostValue = zrush.config.Machine[machineID].Amount * (1 + machine:GetProductionBoost())
	elseif BoostType == "antijam" and zrush.f.FunctionValidater(machine.GetAntiJamBoost) then
		if IsValid(machine) then
			local hole = machine:GetHole()

			if IsValid(hole) and zrush.f.FunctionValidater(hole.GetChaosEventBoost) then
				boostValue = math.Clamp((zrush.config.Machine[machineID].JamChance + hole:GetChaosEventBoost()) * (1 - machine:GetAntiJamBoost()), 0, 100)
			end
		end
	elseif BoostType == "cooling" and zrush.f.FunctionValidater(machine.GetCoolingBoost) then
		local chaoeseventBoost = 0

		if (machine:GetClass() == "zrush_burner") then
			local hole = machine:GetHole()

			if IsValid(hole) and zrush.f.FunctionValidater(hole.GetChaosEventBoost) then
				chaoeseventBoost = hole:GetChaosEventBoost()
			end
		end

		boostValue = math.Clamp((zrush.config.Machine[machineID].OverHeat_Chance + chaoeseventBoost) * (1 - machine:GetCoolingBoost()), 0, 100)
	elseif BoostType == "pipes" and zrush.f.FunctionValidater(machine.GetExtraPipes) then
		boostValue = zrush.config.Machine[machineID].MaxHoldPipes + machine:GetExtraPipes()
	elseif BoostType == "refining" and zrush.f.FunctionValidater(machine.GetRefineBoost) and zrush.f.FunctionValidater(machine.GetFuelTypeID) then
		boostValue = math.Clamp(zrush.Fuel[machine:GetFuelTypeID()].refineoutput * (1 + machine:GetRefineBoost()), 0.05, 1)
	end

	boostValue = math.Round(boostValue, 2)

	return boostValue
end

////////////////////////////////////////////
////////////////////////////////////////////
