-- ========================================================================= --
--                              EskaTracker 2                                --
--           https://www.curseforge.com/wow/addons/eskatracker-2             --
--                                                                           --
--                               Repository:                                 --
--                   https://github.com/Skamer/EskaTracker2                  --
--                                                                           --
-- ========================================================================= --
Syling                "SylingTracker.Achievements.AchievementView"           ""
-- ========================================================================= --
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
-- ========================================================================= --
ShowContextMenu       = API.ShowContextMenu
wipe                  = wipe 
ValidateFlags         = System.Toolset.validateflags
ResetStyles           = Utils.ResetStyles
-- ========================================================================= --
__Recyclable__ "SylingTracker_AchievementView%d"
class "AchievementView" (function(_ENV)
  inherit "Button" extend "IView"

  __Flags__()
  enum "Flags" {
    NONE = 0,
    HAS_OBJECTIVES = 1,
    HAS_PROGRESS_BAR = 2
  }

  function OnViewUpdate(self, data)
    local nameFS = self:GetChild("Name")
    local descFS = self:GetChild("Description")
    local iconBadge = self:GetChild("IconBadge")

    -- Determines the flags
    local flags = Flags.NONE 
    local hasProgressBar = false 
    local objectivesData = data.objectives
    local firstObjectiveData
    if objectivesData then 
      local firstObjectiveData = objectivesData[1]
      if firstObjectiveData then 
        hasProgressBar = firstObjectiveData.hasProgressBar 
      end 

      if hasProgressBar then 
        flags = Flags.HAS_PROGRESS_BAR + Flags.HAS_OBJECTIVES
      else 
        flags = Flags.HAS_OBJECTIVES
      end
    end

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

      -- Is the achievement has objectives
      if ValidateFlags(Flags.HAS_OBJECTIVES, flags) then 
        self:AcquireObjectives()
      else
        self:ReleaseObjectives()
      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 

    -- Update the achievement name 
    Style[nameFS].text = data.name 

    -- Update the description 
    Style[descFS].text = data.description 

    -- Update the icon 
    Style[iconBadge].Icon.fileID = data.icon

    -- Update the context menu 
    if data.achievementID then 
      self.OnClick = function() 
        ShowContextMenu("achievement", self, data.achievementID)
      end 
    end

    -- Update the objectives if needed 
    if objectivesData then 
      local objectivesView = self:AcquireObjectives() 
      objectivesView:UpdateView(objectivesData)
    end 

    self.Flags = flags 
  end 

  function OnAdjustHeight(self, useAnimation)
    local maxOuterBottom
    for childName, child in IterateFrameChildren(self) do
      local outerBottom = child:GetBottom()
      if outerBottom then 
        if not maxOuterBottom or maxOuterBottom > outerBottom then 
            maxOuterBottom = outerBottom
        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 AcquireObjectives(self)
    local objectives = self:GetChild("Objectives")
    if not objectives then
      objectives = ObjectiveListView.Acquire() 

      -- We need to keep the old name when we'll release the objective list 
      self.__PreviousObjectivesName = objectives:GetName()

      objectives:SetParent(self)
      objectives:SetName("Objectives")
      objectives:InstantApplyStyle()
      
      -- if self.Objectives then 
      --   Style[objectives] = self.Objectives 
      -- end

      -- Register the events 
      objectives.OnSizeChanged = objectives.OnSizeChanged + self.OnObjectivesSizeChanged

      self:AdjustHeight()
    end

    return objectives
  end

  function ReleaseObjectives(self)
    local objectives = self:GetChild("Objectives")
    if objectives then 
      objectives:SetName(self.__PreviousObjectivesName)
      self.__PreviousObjectivesName = nil 

      -- Unregister the events 
      objectives.OnSizeChanged = objectives.OnSizeChanged - self.OnObjectivesSizeChanged

      objectives:Release() 

      self:AdjustHeight()
    end
  end

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

    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    = AchievementView.Flags,
    default = AchievementView.Flags.NONE
  }

  property "ObjectivesClass" {
    type    = ClassType,
    default = ObjectiveListView
  }

  property "PaddingBottom" {
    type = Number,
    default = 5
  }

  property "Objectives" {
    type = Table
  }
  -----------------------------------------------------------------------------
  --                            Constructors                                 --
  -----------------------------------------------------------------------------
  __Template__ {
    Name          = SLTFontString,
    Description   = SLTFontString,
    IconBadge     = IconBadge
  }
  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.OnObjectivesSizeChanged = function() self:AdjustHeight() end

    self:SetClipsChildren(true)
  end 
end)

--- Manages the achievements, if your view may have various achievements, this
-- is advised using this class 
__Recyclable__ "SylingTracker_AchievementListView%d"
class "AchievementListView" (function(_ENV)
  inherit "Frame" extend "IView"
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data)
    local achievementIndex = 0

    -- Clear the current achievement id list 
    wipe(self.achievementsID)

    local previousAchievement
    for achievementID, achievementData in pairs(data) do 
      achievementIndex = achievementIndex + 1

      local achievement = self:AcquireAchievement(achievementID)

      -- NOTE: Don't put other anchor could modify the width (e.g, LEFT or RIGHT) as
      -- it will trigger useless "OnSizeChanged" event, breaking animation stuff.
      if achievementIndex > 1 then 
        achievement:SetPoint("TOP", previousAchievement, "BOTTOM", 0, -self.AchievementSpacing)
      else 
        achievement:SetPoint("TOP")
      end

      -- Update our achievement with data 
      achievement:UpdateView(achievementData, updater)

      -- Build the current achievement id list 
      self.achievementsID[achievementID] = true 

      previousAchievement = achievement 
    end

    self:ReleaseUnusedAchievements()
  end

  function AcquireAchievement(self, id)
    local achievement = self.achievementsCache[id]
    if not achievement then 
      achievement = AchievementView.Acquire()
      achievement:SetParent(self)
      achievement:SetPoint("LEFT")
      achievement:SetPoint("RIGHT")

      achievement.OnSizeChanged = achievement.OnSizeChanged + self.OnAchievementSizeChanged

      self.achievementsCache[id] = achievement

      self:AdjustHeight() 
    end 

    return achievement
  end

  function ReleaseUnusedAchievements(self)
    for achievementID, achievement in pairs(self.achievementsCache) do 
      if not self.achievementsID[achievementID] then
        self.achievementsCache[achievementID] = nil

        achievement.OnSizeChanged = achievement.OnSizeChanged - self.OnAchievementSizeChanged

        achievement: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.AchievementSpacing * math.max(0, count-1)

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

  function OnRelease(self)
    wipe(self.achievementsID)
    self:ReleaseUnusedAchievements()

    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 "AchievementSpacing" {
    type      = Number,
    default   = 10
  }
  -----------------------------------------------------------------------------
  --                            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 achievement, to be reused. 
    -- use: self.achievementsCache[achievementID] = achievementObject
    self.achievementsCache = setmetatable({}, { __mode = "v"} )

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

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

    self:SetClipsChildren(true)
  end
end)
-------------------------------------------------------------------------------
--                                Styles                                     --
-------------------------------------------------------------------------------
Style.UpdateSkin("Default", {
  [AchievementView] = {
    width = 300,
    backdrop = {
      bgFile = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]]
    },
    backdropColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
    registerForClicks = { "RightButtonDown" },

    Name = {
      sharedMediaFont = FontType("DejaVuSansCondensed Bold", 10),
      height = 26,
      location = {
        Anchor("TOP"),
        Anchor("LEFT"),
        Anchor("RIGHT")
      }
    },

    IconBadge = {
      height = 46,
      width = 46,
      location = {
        Anchor("TOPLEFT", 5, -5, "Name", "BOTTOMLEFT")
      },
      Icon = {
        texCoords = { top = 0.93, left = 0.07, bottom = 0.07, right = 0.93}
      }
    },

    Description = {
      height = 46,
      sharedMediaFont = FontType("PT Sans Bold", 11),
      textColor = Color(1, 1, 1),
      justifyH = "LEFT",
      justifyV = "TOP",
      location = {
        Anchor("TOP", 0, -5, "Name", "BOTTOM"),
        Anchor("LEFT", 5, 0, "IconBadge", "RIGHT"),
        Anchor("RIGHT")
      }
    },

    FlagsStyles = {
      [AchievementView.Flags.HAS_OBJECTIVES] = {
        Objectives = {
          spacing = 5,
          location = {
            Anchor("TOP", 0, -10, "IconBadge", "BOTTOM"),
            Anchor("LEFT"),
            Anchor("RIGHT")
          }
        }
      },

      [AchievementView.Flags.HAS_OBJECTIVES + AchievementView.Flags.HAS_PROGRESS_BAR] = {
        Description = {
          visible = false 
        },

        Objectives = {
          spacing = 5,
          location = {
            Anchor("TOP", 0, -5, "Name", "BOTTOM"),
            Anchor("LEFT", 5, 0, "IconBadge", "RIGHT"),
            Anchor("RIGHT", -5, 0)
          }
        }
      }
    }
  }
})
