-- ========================================================================= --
--                              SylingTracker                                --
--           https://www.curseforge.com/wow/addons/sylingtracker             --
--                                                                           --
--                               Repository:                                 --
--                   https://github.com/Skamer/SylingTracker                 --
--                                                                           --
-- ========================================================================= --
Syling                   "SylingTracker.Tasks.Task"                         ""
-- ========================================================================= --
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
ShowContextMenu       = API.ShowContextMenu
ValidateFlags         = System.Toolset.validateflags
GameTooltip           = GameTooltip
-- ========================================================================= --
__Recyclable__ "SylingTracker_TaskView%d"
class "TaskView" (function(_ENV)
  inherit "Frame" extend "IView"

  __Flags__()
  enum "Flags" {
    NONE = 0,
    HAS_OBJECTIVES = 1,
    HAS_ITEM = 2
  }
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data, updater)
    local header = self:GetChild("Header")
    local objectives = self:GetChild("Objectives")
    local nameFS = header:GetChild("Name")

    -- Determines the flags 
    local flags = Flags.NONE 
    if data.objectives then 
      flags = flags + Flags.HAS_OBJECTIVES
    end

    if data.item then 
      flags = flags + Flags.HAS_ITEM
    end

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

      -- Is the task has objectives 
      if ValidateFlags(Flags.HAS_OBJECTIVES, flags) then 
        self:AcquireObjectives()
      else
        self:ReleaseObjectives() 
      end

      -- Is the task has an item quest 
      if ValidateFlags(Flags.HAS_ITEM, flags) then 
        self:AcquireItemBadge()
      else 
        self:ReleaseItemBadge()
      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 task name
    Style[nameFS].text = data.name

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

    -- Update the item if needed
    local itemData = data.item 
    if itemData then 
      local itemBadge = self:AcquireItemBadge() 
      Style[itemBadge].Icon.fileID = itemData.texture 

      if itemData.link then 
        itemBadge.OnLeave = function() GameTooltip:Hide() end
        itemBadge.OnEnter = function()
          GameTooltip:SetOwner(itemBadge, "ANCHOR_LEFT")
          GameTooltip:SetHyperlink(itemData.link)
          GameTooltip:Show()
        end
      end
    end

    self.Flags = flags
  end

  function OnAdjustHeight(self, useAnimation)
    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 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 it. 
      self.__PreviousObjectivesName = objectives:GetName() 

      objectives:SetParent(self)
      objectives:SetName("Objectives")
      objectives:InstantApplyStyle()

      -- 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
      -- Give its old name (generated by the recycle system) 
      objectives:SetName(self.__PreviousObjectivesName)
      self.__PreviousObjectivesName = nil

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

      -- It's better to release it after the event has been unregistered for avoiding
      -- useless call 
      objectives:Release()

      self:AdjustHeight()
    end
  end

  function AcquireItemBadge(self)
    local itemBadge = self:GetChild("Item")
    if not itemBadge then 
      itemBadge = IconBadge.Acquire()
      self.__PreviousItemBadgeName = itemBadge:GetName()

      itemBadge:SetParent(self)
      itemBadge:SetName("Item")
      itemBadge:InstantApplyStyle()

      self:AdjustHeight()
    end

    return itemBadge
  end

  function ReleaseItemBadge(self)
    local itemBadge = self:GetChild("Item")
    if itemBadge then 
      itemBadge:SetName(self.__PreviousItemBadgeName)
      self.__PreviousItemBadgeName = nil 

      itemBadge.OnLeave = nil 
      itemBadge.OnEnter = nil 

      itemBadge:Release() 

      self:AdjustHeight()
    end 
  end

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

    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.Type   = nil 
    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:AdjustHeight()

    self:Show()
  end 
  -----------------------------------------------------------------------------
  --                               Properties                                --
  -----------------------------------------------------------------------------
  property "PaddingBottom" {
    type    = Number,
    default = 5
  }

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

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

  property "FlagsStyles" {
    type = Table,
  }
  -----------------------------------------------------------------------------
  --                            Constructors                                 --
  -----------------------------------------------------------------------------
  __Template__{
    Header = Frame,
    {
      Header = {
        Name = SLTFontString
      }
    }
  }
  function __ctor(self)
    -- 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)

__Recyclable__ "SylingTracker_TaskListView%d"
class "TaskListView" (function(_ENV)
  inherit "Frame" extend "IView"
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data, updater)
    local questIndex = 0
    wipe(self.questsID)

    local previousQuest
    for _, questData in pairs(data) do 
      questIndex = questIndex + 1

      local questID =  questData.questID

    
      local quest = self:AcquireQuest(questID)

      if questIndex > 1 then 
        quest:SetPoint("TOP", previousQuest, "BOTTOM", 0, -self.TasksSpacing)
      elseif questIndex == 1 then 
        quest:SetPoint("TOP")
      end
      quest:Show()
      quest:UpdateView(questData, updater)

      previousQuest = quest

      self.questsID[questID] = true 
    end 

    self:ReleaseUnusedQuests()
  end

  function AcquireQuest(self, id)
    local quest = self.questsCache[id]
    if not quest then
      quest = TaskView.Acquire()
      quest:SetParent(self)
      quest:SetPoint("LEFT")
      quest:SetPoint("RIGHT")

      quest.OnSizeChanged = quest.OnSizeChanged + self.OnTaskSizeChanged
      
      self:AdjustHeight()

      self.questsCache[id] = quest
    end

    return quest
  end

  function ReleaseUnusedQuests(self)
    for questID, quest in pairs(self.questsCache) do 
      if not self.questsID[questID] then
        self.questsCache[questID] = nil

        quest.OnSizeChanged = quest.OnSizeChanged - self.OnTaskSizeChanged
        quest: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.TasksSpacing * math.max(0, count-1)

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

  function OnRelease(self)
    wipe(self.questsID)
    self:ReleaseUnusedQuests()

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

    self:SetHeight(1)
  end 

  function OnAcquire(self)
    self:Show()
    -- Important ! We need the frame is instantly styled as this may affect 
    -- its height.
    -- self:InstantApplyStyle()
  end
  -----------------------------------------------------------------------------
  --                               Properties                                --
  -----------------------------------------------------------------------------
  property "TasksSpacing" {
    type    = Number,
    default = 5
  }
  -----------------------------------------------------------------------------
  --                            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 bonus quest, to be reused. 
    -- use: self.questsCache[questID] = questObject
    self.questsCache = setmetatable({}, { __mode = "v"})

    -- Get the current bonus quest id's list. Used internally to release the 
    -- unused bonus quests
    -- use: self.questsID[questID] = true or nil
    self.questsID = {}

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


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

    -- Header Child
    Header = {
      height    = 24,
      location  = {
        Anchor("TOPLEFT"),
        Anchor("TOPRIGHT")
      },

      -- Header/Name child 
      Name = {
        setAllPoints    = true,
        sharedMediaFont =  FontType("DejaVuSansCondensed Bold", 10),
        textColor       = Color(1, 106/255, 0)
      },
    },

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

      [TaskView.Flags.HAS_OBJECTIVES + TaskView.Flags.HAS_ITEM] = {
        Item = {
          height    = 28,
          width     = 28,
          location  = {
            Anchor("TOPLEFT", 4, -4, "Header", "BOTTOMLEFT")
          },

          Icon = {
            texCoords = { top = 0.93, left = 0.07, bottom = 0.07, right = 0.93}
          }
        },

        Objectives = {
          spacing   = 5,
          location  = {
            Anchor("TOP", 2, -4, "Header", "BOTTOM"),
            Anchor("LEFT", 2, 0, "Item", "RIGHT"),
            Anchor("RIGHT")            
          }
        }
      }
    }
  }
})
