--[[
    This module contains a lot of basic functions that modules will want to do.
--]]

local CoreLib = {};

--[[
    PRINTING FUNCTIONS
--]]

-- returns a string of all the args passed to it
local function stringify(...)
	local num = select("#", ...);
    
	if (num == 1) then
		return tostring(...);
	else
		return tostring((select(1, ...))), stringify(select(2, ...));
	end 
end

-- prints the args passed to it.  nothing special
function CoreLib:Print(...)
	if (DEFAULT_CHAT_FRAME) then
		DEFAULT_CHAT_FRAME:AddMessage(strconcat(stringify(...))); 
	end
end

-- prints a colored message!
function CoreLib:ColorPrint(r, g, b, ...)
	if (DEFAULT_CHAT_FRAME) then
		DEFAULT_CHAT_FRAME:AddMessage(strconcat(stringify(...)), r, g, b);
	end
end

-- prints a message to a specific frame
function CoreLib:FramePrint(frame, ...)
	if (frame) then
		frame:AddMessage(strconcat(stringify(...)));
	end
end

-- prints a message, but begins it with a header of the module's name
function CoreLib:HeaderPrint(...)
	self:Print("|cff00ffff", self:GetName(), "|r: ", ...);
end

-- prints out a debugging message, if debugging messages are enabled
function CoreLib:Debug(...)
	local debugchannel = self:Get("debug")
    
	if (debugchannel and not (debugchannel == "" or debugchannel == "off")) then
		local frame = getglobal("ChatFrame"..debugchannel);
        
		if ((not (debugchannel == "on")) and frame) then 
            self:FramePrint(frame, ...);
		else 
            self:Print(...);
		end
	end
end

--[[
    SETTINGS FUNCTIONS
--]]
-- adds in default settings into the settings
function CoreLib:AddDefaultSettings(newSettings)
    local currSettings = self.libraries["CoreLib"].defaultSettings;
    for i, v in pairs(newSettings) do
        currSettings[i] = v;
    end
end

-- clears out the settings, leaving the default ones
function CoreLib:ClearSettings()
    local settings = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if settings then
    	self:ClearTable(settings);
    	self:UpdateSettings();
    end
end

function CoreLib:Get(...)
    local setting = self:GetUserSet(...);
    if setting == nil then
        return self:GetDefault(...);
    else
        return setting;
    end
end

function CoreLib:GetUserSet(...)
    local setting = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if setting then
        for i = 1, select("#", ...) do
            setting = setting[select(i, ...)];
            if (setting == nil) then
                break;
            end
        end
    end
    
    return setting;
end

function CoreLib:GetDefault(...)
    local setting = self.libraries["CoreLib"].defaultSettings;
    for i = 1, select("#", ...) do
        setting = setting[select(i, ...)];
        if (setting == nil) then
            break;
        end
    end
    
    return setting;
end

function CoreLib:Set(...)
    local setting = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if (not setting) then
        setting = {};
        AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()] = setting;
    end
    
    local num = select("#", ...);
    if type(select(1, ...)) == "table" and num == 3 then
        setting = select(1, ...);
    else
        for i = 1, num - 2 do
            local val = select(i, ...);
            setting[val] = setting[val] or {};
            setting = setting[val];
        end
    end
    
    local ind = select(num - 1, ...);
    local val = select(num, ...);
    
    setting[ind] = val;
    self:UpdateSettings();
end

-- calls any functions that have registered to settings changes
function CoreLib:UpdateSettings()
	if (self.libraries["CoreLib"].updateScripts) then
		for index,value in ipairs(self.libraries["CoreLib"].updateScripts) do 
            value(self);
        end
	end
end

-- adds a function to listen to settings changes
function CoreLib:AddSettingsUpdateScript(func)
	self.libraries["CoreLib"].updateScripts = self.libraries["CoreLib"].updateScripts or {};
	table.insert(self.libraries["CoreLib"].updateScripts, func);
end

--[[
    MISC FUNCTIONS
--]]

-- returns the module's name
function CoreLib:GetName()
	return self.libraries["CoreLib"].name;
end

-- sets a script!
function CoreLib:SetScript(script, func)
	self.libraries["CoreLib"].scripts = self.libraries["CoreLib"].scripts or {};
	self.libraries["CoreLib"].scripts[script] = func;
end

-- gets a script!
function CoreLib:GetScript(script)
	return self.libraries["CoreLib"].scripts and self.libraries["CoreLib"].scripts[script];
end

-- calls a script!
function CoreLib:CallScript(script, ...)
    local func = self:GetScript(script);
    if (func) then
        func(self, ...)
    end
end

-- registers an event!
function CoreLib:RegisterEvent(event, func)
    local lib = AsheylaLib;
    lib.events[event] = lib.events[event] or {};
    lib.events[event][self] = func or 1;
end

-- unregisters an event!
function CoreLib:UnregisterEvent(event)
    local lib = AsheylaLib;
    lib.events[event] = lib.events[event] or {};
    lib.events[event][self] = nil;
end

-- checks if an event is registered!
function CoreLib:IsRegisteredEvent(event)
    return AsheylaLib.events[event] and AsheylaLib.events[event][self];
end

-- makes a /command!
function CoreLib:MakeSlashCmd(...)
	local global = string.upper(self:GetName());
    
	SlashCmdList[global] = function(msg) 
        if (self.Commands) then 
            self:Commands(msg);  
        end 
    end;
    
	for i = 1, select("#", ...) do
		local slash = select(i, ...);
		setglobal("SLASH_" .. global .. i, slash);
	end
end

-- prints out information about me!
function CoreLib:AddAuthorInfo()
	self:Print("|cff00ffffDoTimer Version/Author Info:|r");
	self:Print("|cff00ff00Current version|r: ",AsheylaLib:ReturnVersion());
	self:Print("|cff00ff00Date Uploaded|r: ",AsheylaLib:ReturnDateUploaded());
	self:Print("|cff00ff00Author|r: Asheyla <Vindication>, Shattered Hand (Horde)");
	self:Print("|cff00ff00Author's Alt|r: Xanido <Hearts>, Anetheron (Alliance)");
	self:Print("|cff00ff00Email|r: ross456@gmail.com");
	self:Print("|cff00ff00AIM|r: IBerian3209");
	self:Print("|cff00ff00Paypal Donations|r: ross456@gmail.com");
end

--[[
    TABLE FUNCTIONS
--]]

-- returns a copy of the table passed to it
function CoreLib:CopyTable(oldtable, suppress, tabledata)
	tabledata = tabledata or {};
	local newtable = {};
    
	if (not tabledata[oldtable]) then
		tabledata[oldtable] = newtable;
	end
    
	for index,value in pairs(oldtable) do
		if (type(index) == "table") then
			if (tabledata[index]) then
				index = tabledata[index]
			else
				index = CoreLib:CopyTable(index, suppress, tabledata);
			end
		end
        
		if (type(value) == "table") then
			if (tabledata[value]) then
				newtable[index] = tabledata[value]
			else
				newtable[index] = CoreLib:CopyTable(value, suppress, tabledata);
			end
		else
			newtable[index] = value;
		end
	end
    
	if (not suppress) then
		local meta = getmetatable(oldtable);
		if (meta) then 
            setmetatable(newtable, meta);
        end
	end
    
	return newtable;
end

-- combines two tables, giving precedence to table1, without worry of infinite recursion (be careful!)
function CoreLib:CombineTables(table1, table2)
	for index,value in pairs(table2) do
		if (table1[index]) then
			if (type(table1[index]) == "table" and type(value) == "table") then 
				table1[index] = self:CombineTables(table1[index], value);
			end
		else
			table1[index] = value;
		end
	end
    
	return table1;
end

-- this next bit of code deals with creating temporary tables.  it recycles them!

local tablepool = {}
local currenttables = {}
local setupdate

-- acquire a table!  'auto' means that you only need it for 1 frame
function CoreLib:AcquireTable(auto)
	local t = table.remove(tablepool,1) or {}
	if auto then
		table.insert(currenttables,t)
		setupdate(true)
	end
	self:ClearTable(t)
	return t
end

-- release multiple tables at a time
function CoreLib:ReleaseTable(...)
	for i = 1,select("#",...) do
		local t = select(i, ...);
		if (type(t) == "table") then
			self:ClearTable(t);
			setmetatable(t, nil);
			table.insert(tablepool, t);
		end
	end
end

-- nils out a table
function CoreLib:ClearTable(t)
	for index,value in pairs(t) do 
        t[index] = nil;
    end
end

local f = CreateFrame("Frame");

local updatefunc = function()
	if (#currenttables > 0) then
		CoreLib:ReleaseTable(unpack(currenttables));
		for i = #currenttables,1,-1 do 
            table.remove(currenttables, i);
        end
		setupdate(false);
	end
end

function setupdate(scanning)
	if (scanning) then
		f:SetScript("OnUpdate", updatefunc);
	else
		f:SetScript("OnUpdate", nil);
	end
end

AsheylaLib:CreateLibrary("CoreLib", CoreLib)
