local gmod_version_required = 145;
if ( VERSION < gmod_version_required ) then
	error("CAF: Your gmod is out of date: found version ", VERSION, "required ", gmod_version_required)
end

local net = net

local net_pools = {"CAF_Addon_Construct", "CAF_Addon_Destruct", "CAF_Start_true", "CAF_Start_false", "CAF_Addon_POPUP"};
for _, v in pairs(net_pools) do
    print("Pooling ", v, " for net library");
    util.AddNetworkString(v)
end

-- Variable Declarations
local CAF2 = {}
CAF = CAF2;
local CAF3 = {}
CAF2.CAF3 = CAF3;

CAF2.StartingUp = false;

local DEBUG = true
CAF3.DEBUG = DEBUG;
local Addons = {}
CAF3.Addons = Addons

local addonlevel = {}
CAF3.addonlevel = addonlevel
addonlevel[1] = {}
addonlevel[2] = {}
addonlevel[3] = {}
addonlevel[4] = {}
addonlevel[5] = {}

local hooks = {}
CAF3.hooks = hooks;
hooks["think"] = {}
hooks["think2"] = {}
hooks["think3"] = {}
hooks["OnEntitySpawn"] = {}
hooks["OnAddonDestruct"] = {}
hooks["OnAddonConstruct"] = {}
hooks["OnAddonExtraOptionChange"] = {}
hooks["TOOL_Allow_Entity_Spawn"] = {}

function CAF2.AllowSpawn(type, sub_type, class, model)
	for k , v in pairs(hooks["TOOL_Allow_Entity_Spawn"]) do
		local ok, err = pcall(type, sub_type, class, model)
		if not (ok) then
			CAF2.WriteToDebugFile("CAF_Hooks", "TOOL_Allow_Entity_Spawn Error: " .. err .."\n")
		else
			if err == true then
				return true;
			elseif err == false then
				return false;
			end
		end
	end
	return true
end


local function ErrorOffStuff(String)
	Msg("----------------------------------------------------------------------\n")
	Msg("-----------Custom Addon Management Framework Error----------\n")
	Msg("----------------------------------------------------------------------\n")
	Msg(tostring(String).."\n")
end

AddCSLuaFile("autorun/client/cl_caf_autostart.lua")
CAF2.CAF3 = CAF3;
include("caf/core/shared/sh_general_caf.lua")
CAF2.CAF3 = nil;

if (not sql.TableExists("CAF_AddonStatus")) then

	sql.Query("CREATE TABLE IF NOT EXISTS CAF_AddonStatus ( id VARCHAR(50) PRIMARY KEY , status TINYINT(1));")

end

--function Declarations

--Local functions

local function UpdateAddonStatus(addon, status)
	if not addon or not status then return false, "Missing parameter(s)" end
	local id = sql.SQLStr(addon)
	local stat = sql.SQLStr(status)
	sql.Query("UPDATE CAF_AddonStatus SET status="..stat.." WHERE id="..id..";")
end

local function SaveAddonStatus(addon, status)
	if not addon or not status then return false, "Missing parameter(s)" end
	local id = sql.SQLStr(addon)
	local stat = sql.SQLStr(status)
	local data = sql.Query("INSERT INTO CAF_AddonStatus(id, status) VALUES("..id..", "..stat..");")
	if data then 
		Msg("Error making a profile for "..ply:Nick().."\n"..data.."\n") 
	end
end

local function LoadAddonStatus( addon, defaultstatus )
	if not addon then return false, "No Addon Given" end
	local id = sql.SQLStr(addon)
	local data = sql.Query("SELECT * FROM CAF_AddonStatus WHERE id = "..id..";")
	if defaultstatus == nil then
		defaultstatus = 1;
	else
		if defaultstatus then
			defaultstatus = 1
		else
			defaultstatus = 0
		end
	end
	if (not data) then
		SaveAddonStatus(addon, defaultstatus)
	else
		return util.tobool(data[1]["status"])
	end
	return util.tobool(defaultstatus);
end

local function OnEntitySpawn(ent , enttype , ply)
	if IsValid(ent) ~= true then
		return
	end
	ent.caf = ent.caf or {}
	ent.caf.custom = ent.caf.custom or {}
	if ent.caf.custom.canreceivedamage == nil then
		ent.caf.custom.canreceivedamage = true
	end
	if ent.caf.custom.canreceiveheatdamage == nil then
		ent.caf.custom.canreceiveheatdamage = true
	end
	for k , v in pairs(hooks["OnEntitySpawn"]) do
		local ok, err = pcall(v, ent , enttype , ply)
		if not (ok) then
			CAF2.WriteToDebugFile("CAF_Hooks", "OnEntitySpawn Error: " .. err .."\n")
		end
	end
end

local function  OnAddonDestruct(name)
	if not name then return end
	net.Start("CAF_Addon_Destruct")
		net.WriteString(name)
	net.Broadcast()
	if not CAF2.StartingUp then
		for k , v in pairs(hooks["OnAddonDestruct"]) do
			local ok, err = pcall(v, name)
			if not (ok) then
				CAF2.WriteToDebugFile("CAF_Hooks", "OnAddonDestruct Error: " .. err .. "\n")
			end
		end
	end
end

local function OnAddonConstruct(name)
	if not name then return end
    net.Start("CAF_Addon_Construct")
        net.WriteString(name)
    net.Broadcast()
	if not CAF2.StartingUp then
		for k , v in pairs(hooks["OnAddonConstruct"]) do
			local ok, err = pcall(v, name)
			if not (ok) then
				CAF2.WriteToDebugFile("CAF_Hooks", "OnAddonConstruct Error: " .. err .. "\n")
			end
		end
	end
end

local function OnAddonExtraOptionChange(AddonName, OptionName, NewStatus)
	if not AddonName or not OptionName then return nil, "Missing Argument" end
	for k , v in pairs(hooks["OnAddonExtraOptionChange"]) do
		local ok, err = pcall(v, AddonName, OptionName, NewStatus)
		if not (ok) then
			CAF2.WriteToDebugFile("CAF_Hooks", "OnAddonExtraOptionChange Error: " .. err .. "\n")
		end
	end
end

--Gmod Spawn Hooks

local function SpawnedSent( ply , ent )
	--Msg("Sent Spawned\n")
	OnEntitySpawn(ent , "SENT" , ply)
end

local function SpawnedVehicle( ply , ent)
	--Msg("Vehicle Spawned\n")
	OnEntitySpawn(ent , "VEHICLE" , ply)
end	

local function SpawnedEnt( ply , model , ent )
	--Msg("Prop Spawned\n")
	OnEntitySpawn(ent , "PROP" , ply)
end

local function PlayerSpawn(ply)
	--Msg("Prop Spawned\n")
	OnEntitySpawn(ply , "PLAYER" , ply)
end

local function NPCSpawn(ply, ent)
	--Msg("Prop Spawned\n")
	OnEntitySpawn(ent , "NPC" , ply)
end
hook.Add( "PlayerSpawnedNPC", "CAF NPC Spawn", NPCSpawn )
hook.Add( "PlayerInitialSpawn", "CAF PLAYER Spawn", PlayerSpawn )
hook.Add( "PlayerSpawnedProp", "CAF PROP Spawn", SpawnedEnt )
hook.Add( "PlayerSpawnedSENT", "CAF SENT Spawn", SpawnedSent )
hook.Add( "PlayerSpawnedVehicle", "CAF VEHICLE Spawn", SpawnedVehicle )

--Global function
--[[

]]

--[[
	WriteToDebugFile
	This function will write the selected message to 
		1) the console
		2) the specified file into the CAF_DEBUG/Server/ folder
			If the file doesn't exist it will be created
]]
function CAF2.WriteToDebugFile(filename, message)
	if not filename or not message then return nil , "Missing Argument" end
	if DEBUG then
		ErrorNoHalt("Filename: "..tostring(filename)..", Message: "..tostring(message).."\n")
	end
	local contents = file.Read("CAF_Debug/server/"..filename..".txt")
	contents = contents or "" 
	contents = contents .. message
	file.Write("CAF_Debug/server/"..filename..".txt", contents)
end

--[[
	ClearDebugFile
		This function will clear the given file in the debug folder
		It will return the content that was in the file before it got cleared
]]
function CAF2.ClearDebugFile(filename)
	if not filename then return nil , "Missing Argument" end
	local contents = file.Read("CAF_Debug/server/"..filename..".txt")
	contents = contents or "" 
	file.Write("CAF_Debug/server/"..filename..".txt", "")
	return content
end

--[[
	GetSavedAddonStatus
		This function will return the the status that was stored in the SQL file last time to make it easier so admins won't need to disable Addons again every time.
]]
function CAF2.GetSavedAddonStatus( addon, defaultstatus )
	return LoadAddonStatus( addon , defaultstatus)
end


--[[
	Start
		This function loads all the Custom Addons on Startup
]]
function CAF2.Start()
	Msg("Starting CAF Addons\n");
	CAF2.StartingUp = true
	net.Start("CAF_Start_true")
	net.Broadcast()
	CAF2.AddServerTag("CAF")
	for level, tab in pairs(addonlevel) do
		print("Loading Level "..tostring(level).." Addons\n")
		for k, v in pairs(tab) do
			if Addons[v] then
				print("-->", "Loading addon "..tostring(v).."\n")
				if Addons[v].AddResourcesToSend then
					local ok, err = pcall(Addons[v].AddResourcesToSend)
					if not ok then
						CAF2.WriteToDebugFile("CAF_ResourceSend", "AddResourcesToSend Error: " .. err .. "\n")
					end
				end
				if Addons[v].GetStatus  and not Addons[v].GetStatus() then
					local ok = true
					if Addons[v].GetRequiredAddons and Addons[v].GetRequiredAddons() then
						for l, w in pairs(Addons[v].GetRequiredAddons()) do
							if not Addons[w] then
								ok = false
							end
						end
					end
					if ok then
						local state = CAF2.GetSavedAddonStatus(v, Addons[v].DEFAULTSTATUS)
						if Addons[v].__AutoStart then
							local ok2 , err = pcall(Addons[v].__AutoStart, state)
							if not ok2 then
								CAF2.WriteToDebugFile("CAF_AutoStart", "Couldn't call AutoStart for "..v .. ": " .. err .. "\n")
							else
								OnAddonConstruct(v)
								print("-->", "Auto Started Addon: " .. v.."\n")
							end
						elseif state then 
							local ok2 , err = pcall(Addons[v].__Construct)
							if not ok2 then
								CAF2.WriteToDebugFile("CAF_Construct", "Couldn't call constructor for "..v .. ": " .. err .. "\n")
							else
								OnAddonConstruct(v)
								print("-->", "Loaded addon: " .. v.."\n")
							end
						end
					end
				end
			end
		end
	end
	CAF2.StartingUp = false
	net.Start("CAF_Start_false")
	net.Broadcast()
end
hook.Add( "InitPostEntity", "CAF_Start", CAF2.Start)

--[[
	This function will call the destruct function of an addon  and return if it's was succesfull or not (+ the errormessage)
]]
function CAF2.Destruct(addon)
	if not addon then return false, "No Addon Name Given" end
	if not Addons[addon] then return false, "No Addon Registered With This Name" end
	local ok, mes = Addons[addon].__Destruct()
	if ok then
		OnAddonDestruct(addon)
		UpdateAddonStatus(addon, 0)
	end
	return ok, mes
end

--[[
	This function will call the construct function of an addon  and return if it's was succesfull or not (+ the errormessage)
]]
function CAF2.Construct(addon)
	if not addon then return end
	if not Addons[addon] then return end
	local ok, mes = Addons[addon].__Construct()
	if ok then
		OnAddonConstruct(addon)
		UpdateAddonStatus(addon, 1)
	end
	return ok, mes
end

--[[
	This function will receive the construct info from the clientside VGUI menu
]]
local function AddonConstruct(ply, com, args)
	if not ply:IsAdmin() then ply:ChatPrint("You are not allowed to Construct a Custom Addon") return end
	if not args then ply:ChatPrint("You forgot to provide arguments") return end
	if not args[1] then ply:ChatPrint("You forgot to enter the Addon Name") return end
	if table.Count(args) > 1 then --Construct the Addon name if it had spaces in it
		for k , v in pairs(args) do
			if k ~= 1 then
				args[1] = args[1] .. " " .. v
			end
		end
	end
	local ok, mes = CAF2.Construct(args[1])
	if ok then
		ply:ChatPrint("Addon Succesfully Enabled")
	else
		ply:ChatPrint("Couldn't Enable the Addon for the following reason: "..tostring(mes))
	end
end
concommand.Add( "CAF_Addon_Construct", AddonConstruct ) 

--[[
	This function will receive the destruct info from the clientside VGUI menu
]]
local function AddonDestruct(ply, com, args)
	if not ply:IsAdmin() then ply:ChatPrint("You are not allowed to Destruct a Custom Addon") return end
	if not args then ply:ChatPrint("You forgot to provide arguments") return end
	if not args[1] then ply:ChatPrint("You forgot to enter the Addon Name") return end
	if table.Count(args) > 1 then --Construct the Addon name if it had spaces in it
		for k , v in pairs(args) do
			if k ~= 1 then
				args[1] = args[1] .. " " .. v
			end
		end
	end
	local ok, mes = CAF2.Destruct(args[1])
	if ok then
		ply:ChatPrint("Addon Succesfully Disabled")
	else
		ply:ChatPrint("Couldn't Disable the Addon for the following reason: "..tostring(mes))
	end
end
concommand.Add( "CAF_Addon_Destruct", AddonDestruct ) 

local kickgarry = false;
--[[
	This function will update the Client with all active addons
]]
function CAF2.PlayerSpawn(ply)
	if kickgarry then
		pcall(function()
			if ply:SteamID() == "STEAM_0:1:7099" then
				ply:Kick("We don't want you here!");
			end
		end);
	end
	
	ply:ChatPrint("This server is using the Custom Addon Framework\n")
	ply:ChatPrint("Report any bugs during the beta at https://github.com/spacebuild/spacebuild/issues\n")
	
	ply:ChatPrint("\n\nIf you have any suggestions for future versions of CAF, SB, LS, RD, ... please report them at https://github.com/spacebuild/spacebuild/issues\n\n")
	ply:ChatPrint("\n\nNOTE: If you encounter any issues with RD3.1 (alpha) report them at https://github.com/spacebuild/spacebuild/issues\n\n")
	
	timer.Simple(1, function()
		if not IsValid(ply) then return end
		
		for k, v in pairs(Addons) do
			if v.GetStatus and v.GetStatus() then
                net.Start("CAF_Addon_Construct")
                    net.WriteString(k)
                net.Send(ply)
			end
		end
	end)
end
hook.Add( "PlayerInitialSpawn", "CAF_In_Spawn", CAF2.PlayerSpawn )


local oldcreate = ents.Create

ents.Create = function(class)
	local ent = oldcreate(class)
	timer.Simple( 0.1, function() OnEntitySpawn( ent, "SENT" ) end)
	return ent;
end

--msg, location, color, displaytime
function CAF2.POPUP(ply, msg, location, color, displaytime)
	if msg then
		location = location or "top"
		color = color or CAF2.colors.white
		displaytime = displaytime or 1
		net.Start("CAF_Addon_POPUP")
			net.WriteString(msg)
			net.WriteString(location)
			net.WriteUInt(color.r, 8)
			net.WriteUInt(color.g, 8)
			net.WriteUInt(color.b, 8)
			net.WriteUInt(color.a, 8)
			net.WriteUInt(displaytime, 16)
		net.Send(ply)
	end
end

local servertags = nil;
function CAF2.AddServerTag(tag)
	if not servertags or not CAF2.StartingUp then
		servertags = GetConVarString("sv_tags")
	end
	if servertags == nil then
		RunConsoleCommand("sv_tags", tag)
	elseif not string.find(servertags, tag) then
		servertags = servertags .. ","..tag
		RunConsoleCommand("sv_tags", servertags)
	end
end

function CAF2.RemoveServerTag(tag)
	if not servertags or not CAF2.StartingUp then
		servertags = GetConVarString("sv_tags")
	end
	if servertags then
		servertags = string.Replace(servertags, ","..tag, "" )
		RunConsoleCommand("sv_tags", servertags)
	end
end

CAF = CAF2

--[[
	The following code sends the clientside and shared files to the client and includes CAF core code
]]
--Send Client and Shared files to the client and Include the ServerAddons

--Core files

local Files = file.Find( "caf/core/server/*.lua" , "LUA")
for k, File in ipairs(Files) do
	Msg("Loading: "..File.."...")
	local ErrorCheck, PCallError = pcall(include, "caf/core/server/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Loaded: Successfully\n")
	end
end

Files = file.Find("CAF/Core/client/*.lua", "LUA")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf/core/client/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.Find("CAF/Core/shared/*.lua", "LUA")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf/core/shared/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.Find("caf/languagevars/*.lua", "LUA")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf/languagevars/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(include, "caf/languagevars/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

--Main Addon
local Files = file.Find( "caf/addons/server/*.lua" , "LUA")
for k, File in ipairs(Files) do
	Msg("Loading: "..File.."...")
	local ErrorCheck, PCallError = pcall(include, "caf/addons/server/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Loaded: Successfully\n")
	end
end

Files = file.Find("caf/addons/client/*.lua", "LUA")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf/addons/client/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end

Files = file.Find("caf/addons/shared/*.lua", "LUA")
for k, File in ipairs(Files) do
	Msg("Sending: "..File.."...")
	local ErrorCheck, PCallError = pcall(AddCSLuaFile, "caf/addons/shared/"..File)
	if(not ErrorCheck) then
		ErrorOffStuff(PCallError)
	else
		Msg("Sent: Successfully\n")
	end
end
