-- ========================================================================= --
--                              SylingTracker                                --
--           https://www.curseforge.com/wow/addons/sylingtracker             --
--                                                                           --
--                               Repository:                                 --
--                   https://github.com/Skamer/SylingTracker                 --
--                                                                           --
-- ========================================================================= --
Syling              "SylingTracker.Quests.QuestCategoryView"                 ""
-- ========================================================================= --
namespace                          "SLT"
-- ========================================================================= --
-- Iterator helper for ignoring the children are used for backdrop, and avoiding
-- they are taken as account for their parent height
IterateFrameChildren = Utils.IterateFrameChildren
ResetStyles           = Utils.ResetStyles
ValidateFlags         = System.Toolset.validateflags
-- ========================================================================= --
__Recyclable__ "SylingTracker_QuestCategoryView%d"
class "QuestCategoryView" (function(_ENV)
  inherit "Frame" extend "IView"

  __Flags__()
  enum "Flags" {
    NONE        = 0,
    HAS_QUESTS  = 1
  }
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data)
    local nameFS = self:GetChild("Name")

    -- Determines the flags 
    local flags = Flags.NONE 
    if data then 
      flags = Flags.HAS_QUESTS
    end

    if flags ~= self.Flags then 
      ResetStyles(self)
      ResetStyles(nameFS)

      -- Is there quests 
      if ValidateFlags(Flags.HAS_QUESTS, flags) then 
        self:AcquireQuests()
      else
        self:ReleaseQuests()
      end

      -- Styling stuff 
      if flags ~= Flags.NONE then 
        local styles = self.FlagsStyles and self.FlagsStyles[flags]
        if styles then 
          Style[self] = styles 
        end 
      end
    end

    if data then
      -- Update the category name
      local name 
      for _, v in pairs(data) do 
        name = v.category
        break
      end

      Style[nameFS].text = name


      local questsView = self:AcquireQuests()
      questsView:UpdateView(data)
    end

    self.Flags = flags
  end

  function AcquireQuests(self)
    local quests = self:GetChild("Quests")
    if not quests then 
      quests = QuestListView.Acquire()

      -- We need to keep the old name when we'll release it
      self.__PreviousQuestsName = quests:GetName()

      quests:SetParent(self)
      quests:SetName("Quests")
      quests:InstantApplyStyle()

      -- Register the events 
      quests.OnSizeChanged = quests.OnSizeChanged + self.OnQuestsSizeChanged

      self:AdjustHeight()
    end

    return quests 
  end

  function ReleaseQuests(self)
    local quests = self:GetChild("Quests")
    if quests then 
      -- Give its old name (generated by the recycle system)
      quests:SetName(self.__PreviousQuestsName)
      self.__PreviousQuestsName = nil

      -- Unregister the events 
      quests.OnSizeChanged = quests.OnSizeChanged - self.OnQuestsSizeChanged
      
      -- It's better to release it after the event has been unregistered for avoiding
      -- useless call
      quests:Release()

      self:AdjustHeight()
    end
  end
 
  function OnAdjustHeight(self, useAnimation)
    local height = 0
    local maxOuterBottom
    for childName, child in IterateFrameChildren(self) do

      local outerBottom = child:GetBottom()
      local outerTop = child:GetTop()
      if outerBottom then 
        if not maxOuterBottom or maxOuterBottom > outerBottom then 
            maxOuterBottom = outerBottom
            maxChild = child
        end
      end
    end

    if maxOuterBottom then 
      local computeHeight = (self:GetTop() - maxOuterBottom) + self.PaddingBottom
      if useAnimation then 
        self:SetAnimatedHeight(computeHeight)
      else 
        self:SetHeight(computeHeight)
      end
    end    
  end

  function OnRelease(self)
    -- Release first the children
    self:ReleaseQuests()

    self:Hide()
    self:ClearAllPoints()
    self:SetParent()

    -- "CancelAdjustHeight" and "CancelAnimatingHeight" wiil cancel the pending
    -- computing stuff for height, so they not prevent "SetHeight" here doing 
    -- its stuff.
    self:CancelAdjustHeight()
    self:CancelAnimatingHeight()
    self:SetHeight(1)

    -- Reset the class properties
    self.Flags = nil

    -- Will Remove all custom styles properties, so the  next time the object will
    -- be used, this one will be in a clean state
    ResetStyles(self)
  end

  function OnAcquire(self)
    -- Important ! We need the frame is instantly styled as this may affect 
    -- its height.
    self:InstantApplyStyle()

    self:Show()
    self:AdjustHeight()
  end
  -----------------------------------------------------------------------------
  --                               Properties                                --
  -----------------------------------------------------------------------------
  property "FlagsStyles" {
    type = Table
  }

  property "Flags" {
    type    = QuestCategoryView.Flags,
    default = QuestCategoryView.Flags.NONE
  }

  property "PaddingBottom" {
    type = Number,
    default = 0
  }
  -----------------------------------------------------------------------------
  --                            Constructors                                 --
  -----------------------------------------------------------------------------
  __Template__ {
    Name = SLTFontString
  }
  function __ctor(self)
    -- Important ! We need the frame is instantly styled as this may affect 
    -- its height.
    self:InstantApplyStyle()

    -- Important! As the frame ajusts its height depending of its children height
    -- we need to set its height when contructed for the event "OnSizechanged" of
    -- its children is triggered.
    self:SetHeight(1)

    self.OnQuestsSizeChanged = function() self:AdjustHeight() end

    self:SetClipsChildren(true)
  end

end)

__Recyclable__ "SylingTracker_QuestCategoryListView%d"
class "QuestCategoryListView" (function(_ENV)
  inherit "Frame" extend "IView"
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data)
    -- Clear the current achievement id list 
    wipe(self.categoriesID)
    wipe(self.categoriesRange)
    wipe(self.categoriesData)
    wipe(self.categoriesOrder)

    if data then 
      -- 1. Split the quests data in their respective category
      for questID, questData in pairs(data) do
        local categoryName = questData.category 
        local t = self.categoriesData[categoryName]
        if not t then 
          t = setmetatable({}, { __mode = "v"})
          self.categoriesData[categoryName] = t
          tinsert(self.categoriesOrder, categoryName)
        end

        local bestRange = self.categoriesRange[categoryName]
        if not bestRange or questData.distance < bestRange then 
          self.categoriesRange[categoryName] = questData.distance
        end

        t[questID] = questData
      end

      -- 2. Sort the category by their best range 
      table.sort(self.categoriesOrder, function(a, b)
        return self.categoriesRange[a] < self.categoriesRange[b]
      end)

      -- 3. Update the category with their quests data 
      local previousCategory
      for index, categoryName in ipairs(self.categoriesOrder) do 
        local category = self:AcquireCategory(categoryName)

        if index > 1 then 
          category:SetPoint("TOP", previousCategory, "BOTTOM", 0, -self.CategorySpacing)
        else 
          category:SetPoint("TOP")
        end

        -- Update our category with data 
        category:UpdateView(self.categoriesData[categoryName])

        -- Build the current quest id list 
        self.categoriesID[categoryName] = true 

        previousCategory = category
      end
    end

    self:ReleaseUnusedCategories()
  end

  function AcquireCategory(self, id)
    local category = self.categoriesCache[id]
    if not category then 
      category = QuestCategoryView.Acquire()
      category:SetParent(self)
      category:SetPoint("LEFT")
      category:SetPoint("RIGHT")

      category.OnSizeChanged = category.OnSizeChanged + self.OnCategorySizeChanged

      self.categoriesCache[id] = category 

      self:AdjustHeight()
    end
    
    return category
  end

  function ReleaseUnusedCategories(self)
    for categoryName, category in pairs(self.categoriesCache) do 
      if not self.categoriesID[categoryName] then 
        self.categoriesCache[categoryName] = nil 

        category.OnSizeChanged = category.OnSizeChanged - self.OnCategorySizeChanged

        category:Release()

        self:AdjustHeight()
      end 
    end 
  end

  function OnAdjustHeight(self, useAnimation)
    local height = 0
    local count  = 0

    for _, child in IterateFrameChildren(self) do 
      height  = height + child:GetHeight()
      count   = count + 1
    end
    
    height = height + self.CategorySpacing * math.max(0, count-1)

    if useAnimation then 
      self:SetAnimatedHeight(height)
    else
      self:SetHeight(height)
    end
  end

  function OnRelease(self)
    wipe(self.categoriesID)
    wipe(self.categoriesRange)
    wipe(self.categoriesData)
    wipe(self.categoriesOrder)
    self:ReleaseUnusedCategories()

    self:Hide()
    self:ClearAllPoints()
    self:SetParent()
    self:CancelAdjustHeight()
    self:CancelAnimatingHeight()

    self:SetHeight(1)
   
    ResetStyles(self)
  end

  function OnAcquire(self)
    -- Important ! We need the frame is instantly styled as this may affect 
    -- its height.
    self:InstantApplyStyle()

    self:Show()

    self:AdjustHeight()
  end 
  -----------------------------------------------------------------------------
  --                               Properties                                --
  -----------------------------------------------------------------------------
  property "CategorySpacing" {
    type      = Number,
    default   = 13
  }
  -----------------------------------------------------------------------------
  --                            Constructors                                 --
  -----------------------------------------------------------------------------
  function __ctor(self)
    -- Important ! We need the frame is instantly styled as this may affect 
    -- its height.
    self:InstantApplyStyle()

    -- Important! As the frame ajusts its height depending of its children height
    -- we need to set its height when contructed for the event "OnSizechanged" of
    -- its children is triggered.
    self:SetHeight(1) -- !important

    -- Keep in the cache the categories, to be reused. 
    -- use: self.categoriesCache[categoryName] = categoryObject
    self.categoriesCache = setmetatable({}, { __mode = "v"})

    -- Get the current categories id's list. Used internally to release the 
    -- unused categories
    -- use: self.achievementsID[categoryName] = true or nil
    self.categoriesID = {}

    self.categoriesRange  = {}
    self.categoriesData   = {}
    self.categoriesOrder  = {}

    self.OnCategorySizeChanged = function() self:AdjustHeight() end 

    self:SetClipsChildren(true)
  end
end)
-------------------------------------------------------------------------------
--                                Styles                                     --
-------------------------------------------------------------------------------
Style.UpdateSkin("Default", {
  [QuestCategoryView] = {
    Name = {
      sharedMediaFont = FontType("PT Sans Narrow Bold", 12),
      textColor = Color(1, 0.38, 0),
      textTransform = "UPPERCASE",
      justifyH = "LEFT",
      location = {
        Anchor("TOP"),
        Anchor("LEFT", 10, 0),
        Anchor("RIGHT")
      }
    },

    FlagsStyles = {
      [QuestCategoryView.Flags.HAS_QUESTS] = {
        Quests = {
          location = {
            Anchor("TOP", 0, -13, "Name", "BOTTOM"),
            Anchor("LEFT"),
            Anchor("RIGHT")
          }
        }
      }
    }
  }
})
