---------------------------
-- Initialization Script --
---------------------------
local AddOnName, nsVars = ...

--Cache global variables
local vehicleInUse
local CreateFrame, PlaySound = CreateFrame, PlaySound
local rawget, rawset = rawget, rawset
local UnitName, GetRealmName, HasPetSpells = UnitName, GetRealmName, HasPetSpells
local setmetatable, getmetatable = setmetatable, getmetatable
local table, pairs, type = table, pairs, type

function table.copy(t, deep, seen)
  seen = seen or {}
  if t == nil then return nil end
  if seen[t] then return seen[t] end

  local nt = {}
  for k, v in pairs(t) do
    if deep and type(v) == 'table' then
      nt[k] = table.copy(v, deep, seen)
    else
      nt[k] = v
    end
  end
  setmetatable(nt, table.copy(getmetatable(t), deep, seen))
  seen[t] = nt
  return nt
end

-- Prepare Addon variables
local SS = CreateFrame("Frame", "SSpheres_frame_addon");
nsVars.SS = SS
SS.dVars            = {};
SS.dVars["spheres"] = {};
SS.dVars["global"]  = {};

SS.pVars            = {};
SS.pVars["spheres"] = {};

SS._f = "SS_frame_"

-- Declare global function for remote asset importing
_G["SeamoreSpheresRemoteAssets"] = {
  newAssetCollection = function(collectionName, collection)
    -- TODO: Add validation to ensure proper values.
    SS.FillTextures.remoteAssets[collectionName] = collection
  end,
  newSwirlAssets = function(collectionName, collection)
    -- TODO: Add validation to ensure proper values.
    SS.FillTextures.textures.swirl[collectionName] = collection
  end,
}

function SS.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

function SS: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] = SS: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

function SS:Initialize()
  -- Load profile data
  Spheres        = Spheres        or {}
  Seamore        = Seamore        or {}
  SeamoreSpheres = SeamoreSpheres or {}

  self.global  = SS:SetMetatables(Seamore, self.dVars.global);
  self.profile = SS:SetMetatables(Spheres, self.dVars.spheres);

  -- Depricated
  if not self.global["SSProfile_Default"] then
    self.global["SSProfile_Default"] = {}
  end
  
  local player = UnitName("player") .. "-" .. GetRealmName()

  if not self.global["SSProfile_" .. player] and not self.global.characters[player] then
    if self.profile.health then
      self.global["SSProfile_" .. player] = self.profile
      self.profile = nil
      Spheres      = {}

      self.profile = SS:SetMetatables(Spheres, self.dVars.spheres);
    else
      self.global["SSProfile_" .. player] = {}
    end

    self.global.characters[player] = true
  elseif self.profile.profileName and self.profile.health then
    local profileName = self.profile.profileName
    self.profile      = nil
    Spheres           = {}

    self.profile             = SS:SetMetatables(Spheres, self.dVars.spheres);
    self.profile.profileName = profileName
    self.global.characters[player] = true
  end

  self.profile.profileName = self.profile.profileName or "SSProfile_" .. player
  self.MicroSphereData.load()

  if not self.global[self.profile.profileName] then
    self.profile.modeInfo = nil
  end

  self.global[self.profile.profileName] = self.global[self.profile.profileName] or {}
  self.db      = SS:SetMetatables(self.global[self.profile.profileName], self.pVars.spheres["SSProfile_Default"]);
  self.private = SS:SetMetatables(SeamoreSpheres, self.pVars.spheres);

  self:OnInitialized()
end

function SS:ResetSpheres()
  Seamore[Spheres.profileName] = {}
  Spheres                      = nil
  SeamoreSpheres               = nil

  ReloadUI()
end

-- Initialize event listeners
local eventFrame = CreateFrame("Frame", SS._f.."event_watcher")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PET_BATTLE_OPENING_START")
eventFrame:RegisterEvent("PLAYER_ENTERING_BATTLEGROUND")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
eventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
eventFrame:RegisterEvent("UNIT_ENTERED_VEHICLE")
eventFrame:RegisterEvent("UNIT_EXITED_VEHICLE")
eventFrame:RegisterEvent("UNIT_MAXPOWER")
eventFrame:RegisterEvent("UNIT_PET")
eventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
eventFrame:RegisterEvent("VARIABLES_LOADED")

function eventFrame:OnEvent(event,arg1, ...)
  if event == "ADDON_LOADED" then
    self:UnregisterEvent(event)
    SS  :Initialize()
  elseif event == "PET_BATTLE_CLOSE" then
    SS  :OnPetBattleEnd ()
    self:UnregisterEvent(event)
    self:RegisterEvent  ("PET_BATTLE_OPENING_START")
  elseif event == "PET_BATTLE_OPENING_START" then
    SS  :OnPetBattleStart()
    self:UnregisterEvent (event)
    self:RegisterEvent   ("PET_BATTLE_CLOSE")
  elseif event == "PLAYER_ENTERING_BATTLEGROUND" and not self.shouldRefresh then
    self.shouldRefresh = true
  elseif event == "PLAYER_ENTERING_WORLD" and self.shouldRefresh then
    -- Ensure the proper layout is active.
    SS:OnPlayerSpecializationChanged()
  
    if (SS.curClass == "hunter") or (SS.curClass == "warlock") or (SS.curClass == "mage") or (SS.curClass == "deathknight") then
      -- Check for pet information.
      local name = UnitName("pet")
      SS:OnPetAvailable(name, HasPetSpells() or 0)
    end
  
    self.shouldRefresh = nil
  elseif event == "PLAYER_LOGIN" then
    SS  :OnPlayerLoggedIn()
    self:UnregisterEvent(event)
  elseif event == "PLAYER_REGEN_DISABLED" then
    SS  :OnRegenDisabled()
    self:UnregisterEvent(event)
    self:RegisterEvent  ("PLAYER_REGEN_ENABLED")
  elseif event == "PLAYER_REGEN_ENABLED" then
    SS  :OnRegenEnabled()
    self:UnregisterEvent(event)
    self:RegisterEvent  ("PLAYER_REGEN_DISABLED")
  elseif event == "PLAYER_SPECIALIZATION_CHANGED" and arg1 == "player"  then
    SS:OnPlayerSpecializationChanged()
  elseif event == "UNIT_ENTERED_VEHICLE" and arg1 == "player" then
    local args = {...}
  
    if args[4] == true and args[7] == true then
      SS:OnPlayerEnteredVehicle()
    end
  elseif event == "UNIT_EXITED_VEHICLE" and arg1 == "player" then
    SS:OnPlayerExitedVehicle()
  elseif event == "UNIT_MAXPOWER" and arg1 == "player" then
    SS:OnUnitMaxPowerChanged()
  elseif event == "UNIT_PET" then
    local name = UnitName("pet")
    SS:OnPetAvailable(name, HasPetSpells() or 0)
  elseif event == "UNIT_SPELLCAST_SUCCEEDED" and arg1 == "player" then
    SS:OnSpellCastSuccess(event, ...)
  elseif event == "UPDATE_SHAPESHIFT_FORM" then
    SS:OnPlayerShapeshift()
  elseif event == "VARIABLES_LOADED" then
    SS  :OnVariablesLoaded()
    self:UnregisterEvent(event)
  end
end

function SS:AddEvent(e)
  eventFrame:RegisterEvent(e)
end

eventFrame:SetScript("OnEvent", eventFrame.OnEvent)
