local AddOnName, nsVars = ...
local ss = nsVars.SS

local setmetatable = setmetatable
local rawset, rawget = rawset, rawget
local table, pairs, type = table, pairs, type
local print, math, floor, tostring = print, math, floor, tostring
local IsShiftKeyDown = IsShiftKeyDown

local mtUtils = {}

--- Provides a sorted itterator for a table.
-- @param t The table.
-- @param f The sorting format.
-- @return The sorted itterator.
--
function mtUtils.pairsByKeys(t, f)
  local a = {}
  
  for n in pairs(t) do
    table.insert(a, n)
  end
  
  table.sort(a, f)
  local i = 0
  
  local iter = function()
    i = i + 1
    if a[i] == nil then
      return nil
    else
      return a[i], t[a[i]]
    end
  end
  
  return iter
end

--- Set's a table and it's children's metatables.
-- @param vars The existing table.
-- @param stored The next metatable.
-- @return The configured table.
--
function mtUtils:SetMetatables(vars, stored)
  setmetatable(vars, { __index = stored })
  
  for k,v in pairs(stored) do
    if (type(v) == "table") then
      if rawget(vars, k) and type(vars[k]) ~= "table" then
        rawset(vars, k, {})
      end
      
      vars[k] = self:SetMetatables(rawget(vars, k) or {}, v)
    elseif rawget(vars, k) and type(vars[k]) == "table" then
      rawset(vars, k, nil)
    end
  end
  
  return vars
end

--- Indexes an addon component.
-- @param index The addon index.
-- @param meta The metatable.
--
function mtUtils.addModule(index, meta)
  ss[index] = setmetatable({}, { __index = meta })
end

--- Formats a string with the given values.
-- @param string The unformatted string.
-- @param ... The replacement values.
-- @return The formatted string.
-- @see string:format().
--
function mtUtils:stringf(string, ...)
  return string:format(...)
end

--- Prints a formatted string.
-- @param string The unformatted string.
-- @param ... The replacement values.
-- @see string:format().
--
function mtUtils:printf(string, ...)
  print(string:format(...))
end

--- Rounds a number by a power of 10.
-- @param num The number to round.
-- @param idp The exponential value.
-- @return The rounded value.
--
function mtUtils:round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end

--- Creates or extracts values from color tables.
-- If RGB values are greater than 1, a 255 is used.
-- @param colorTable A table containing RGBA values, or the red color value when creating the color table.
-- @param g Green color value.
-- @param b Blue color value.
-- @param a Alpha value.
-- @return The color values.
--
function mtUtils:GetRGBAValues(colorTable, g, b, a)
  if not colorTable then return 1,1,1,1; end
  
  if g and b and a then
    -- Return the colors in a table.
    return { r=colorTable, g=g, b=b, a=a }
  elseif (colorTable.r and colorTable.r > 1) or (colorTable[1] and colorTable[1] > 1) then
    -- Return 255 based RGBA values.
    return (colorTable.r or colorTable[1]) / 255, (colorTable.g or colorTable[2]) / 255, (colorTable.b or colorTable[3]) / 255, (100 - (colorTable.a or colorTable[4])) / 100
  else
    -- Return RGBA values.
    return colorTable.r or colorTable[1], colorTable.g or colorTable[2], colorTable.b or colorTable[3], colorTable.a or colorTable[4]
  end
end

--- Formats a number for sphere display.
-- @param val The number to format
-- @param formatting The desired formatting. ("raw", "truncated" or "commaSeparated")
-- @param petOverride Flag to override the formatting type for pet values.
-- @return The formatted value.
--
function mtUtils:valueFormat(val, formatting, petOverride)
  local newVal = val
  if petOverride == true then
    if val > 10000 then
      newVal = (floor((val/1000)*10)/10).."k"
    end
    return newVal;
  elseif formatting == "truncated" then
    if val > 1000000 then
      newVal = (floor((val/1000000)*10)/10) .."m"
    elseif val > 999 then
      newVal = (floor((val/1000)*10)/10) .."k"
    end
    return newVal;
  elseif formatting == "commaSeparated" then
    newVal = tostring(val):reverse():gsub("%d%d%d", "%1,"):reverse():gsub("^,", "");
    return newVal;
  else
    return newVal;
  end
end

--- Makes a frame movable whike holding the selected key.
-- @param frame The target frame.
-- @param button The button to hold, defaults to "LeftButton".
-- @param listener optional callback for "OnDragStop".
--
function mtUtils:MakeFrameMovable(frame, button, listener)
  frame:SetClampedToScreen(true)
  frame:SetMovable(true)
  frame:EnableMouse(true)
  frame:RegisterForDrag(button or "LeftButton")
  frame:SetScript("OnDragStart", function(self)
    if IsShiftKeyDown() then
      self:StartMoving()
    end
  end)
  frame:SetScript("OnDragStop", function(self)
    self:StopMovingOrSizing()
    if listener then
      listener(self)
    end
  end)
end

ss.Utils = setmetatable({}, { __index = mtUtils })
