-------------------------------------------------------------------------------------
-- Title: MSBT Table Recycler
-- Author: Mik
-- Credits:
--  Thanks to tekkub, the author of compostLib.  I adapted much of his code.
-------------------------------------------------------------------------------------

-- Create module namespace.
MSBTTableRecycler = {};


-------------------------------------------------------------------------------------
-- Private constants.
-------------------------------------------------------------------------------------

local MAX_ENTRIES = 50;


-------------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------------

 -- Table to hold the cache entries.
local primaryCache = {};

-- Variables to hold stats.
local numNew = 0;
local numRecycled = 0;
local numErased = 0;
local numReclaimed = 0;
local amountMemFreed = 0;

-- Create a table to hold overflow entries.  If more tables are reclaimed
-- than the size of the max primary cache can hold, they will be reclaimed
-- into the overflow cache which is a weak table.  This allows for spikes
-- in cache usage to still be optimized while not preventing garbage
-- collection on the extra data once there are no other references to them.
local overflowCache = {};
setmetatable(overflowCache, {__mode = "v"});


-------------------------------------------------------------------------------------
-- Recycling functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Checks if there are any tables in the overflow cache.
-- **********************************************************************************
function MSBTTableRecycler.TablesInOverflow()
 -- Loop through all items in the overflow cache.
 for i in pairs(overflowCache) do
  return true; 
 end
end


-- **********************************************************************************
-- Removes a table from the cache and returns it.  If there are no tables available
-- in the cache a new one is returned.
-- **********************************************************************************
function MSBTTableRecycler.AcquireTable()
 -- Check if there are any tables available in the primary cache and if so
 -- return one.
 if (#primaryCache > 0) then
  -- Increment the number of recycled tables.
  numRecycled = numRecycled + 1;

  return table.remove(primaryCache);

 -- Check if there are any tables available in the overflow cache and if so
 -- return one.
 elseif MSBTTableRecycler.TablesInOverflow() then
  -- Increment the number of recycled tables.
  numRecycled = numRecycled + 1;

  -- Loop through the tables in the overflow and return one.
  for i in pairs(overflowCache) do
   return table.remove(overflowCache, i);
  end

 -- No tables available so return a new one.
 else
  -- Increment the number of new tables created.
  numNew = numNew + 1;
  return {};
 end
end


-- **********************************************************************************
-- Reclaims a table into the cache.
-- **********************************************************************************
function MSBTTableRecycler.ReclaimTable(t)
 -- Check if the passed value isn't valid.
 if (type(t) ~= "table") then
  return;
 end

 -- Erase the passed table.
 MSBTTableRecycler.EraseTable(t);

 -- Check if the primary cache is already full.
 if (#primaryCache >= MAX_ENTRIES) then
  -- Insert the table into the overflow cache.
  table.insert(overflowCache, t);
 else
  -- Insert the table into the primary cache.
  table.insert(primaryCache, t);
 end

 -- Increment the number of reclaimed tables.
 numReclaimed = numReclaimed + 1;
end


-- **********************************************************************************
-- Erases the passed table.  Subtables are NOT erased.
-- **********************************************************************************
function MSBTTableRecycler.EraseTable(t)
 -- Get the amount of memory used before the clear.
 local currentMem = gcinfo();

 -- Loop through all the keys in the table and clear it.
 for key in pairs(t) do
  t[key] = nil;
 end

 -- Increment the amount of memory freed.
 amountMemFreed = amountMemFreed + math.abs(gcinfo() - currentMem);

 -- Increment the number of tables erased.
 numErased = numErased + 1;
end


-- **********************************************************************************
-- Prints stats.
-- **********************************************************************************
function MSBTTableRecycler.PrintStats()
 local overflowSize = 0;
 for i in pairs(overflowCache) do
  overflowSize = overflowSize + 1;
 end

 DEFAULT_CHAT_FRAME:AddMessage(string.format(
  "|cff00ff00New Tables: %d|r  |cffffff00Recycled Tables: %d|r  |cff00ffffCached Tables: %d|r  |cffff0000Overflow Tables: %d|r  |cff888888Erased Tables: %d|r  |cffff00ffMemory Saved: %d KiB|r  |cffff0088Tables Lost to GC: %d",
  numNew,
  numRecycled,
  #primaryCache,
  overflowSize,
  numErased,
  amountMemFreed + ((32/1024) * numRecycled),
  numReclaimed - numRecycled - #primaryCache));
end


-------------------------------------------------------------------------------------
-- Utility functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Copies a table.
-- **********************************************************************************
function MSBTTableRecycler.CopyTable(srcTable)
 -- Create a new table.
 local newTable = {};

 -- Loop through all of the entires in the table.
 for key, value in pairs(srcTable) do
  -- Check if it's a nested table.
  if (type(value) == "table") then
   -- Recursively call the function to copy the nested table.
   value = MSBTTableRecycler.CopyTable(value);
  end

  -- Make a copy of the value into the new table.
  newTable[key] = value;
 end

 -- Return the new table.
 return newTable;
end