-- ========================================================================= --
--                              EskaTracker 2                                --
--           https://www.curseforge.com/wow/addons/eskatracker-2             --
--                                                                           --
--                               Repository:                                 --
--                   https://github.com/Skamer/EskaTracker2                  --
--                                                                           --
-- ========================================================================= --
Syling                      "SylingTracker.Quests.QuestView"                 ""
-- ========================================================================= --
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
-- Check if the player is on the Shadowlands environment
IsOnShadowlands       = Utils.IsOnShadowlands
-- ========================================================================= --
ResetStyles           = Utils.ResetStyles
ShowContextMenu       = API.ShowContextMenu
ValidateFlags         = System.Toolset.validateflags
GameTooltip           = GameTooltip
-- ========================================================================= --
__Recyclable__ "SylingTracker_QuestView%d"
class "QuestView" (function(_ENV)
  inherit "Button" extend "IView"

  enum "Type" {
    Common    = 1,
    Dungeon   = 2,
    Raid      = 3,
    Legendary = 4,
  }

  __Flags__()
  enum "Flags" {
    NONE = 0,
    HAS_OBJECTIVES = 1,
    HAS_ITEM = 2
  }
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data)
    local header          = self:GetChild("Header")
    local name            = header:GetChild("Name")
    local levelBadge      = header:GetChild("Level")
    local levelTextBadge  = levelBadge:GetChild("Label")
    local tagIcon         = header:GetChild("Tag")

    -- Set the quest title 
    name:SetText(data.name)

    -- Set the level 
    if data.level then
      levelTextBadge:SetText(data.level)
      local difficultyColor = GetQuestDifficultyColor(data.level)
      if difficultyColor then 
        Style[levelTextBadge].textColor = Color(difficultyColor)
        Style[levelBadge].backdropColor = {
          r = difficultyColor.r,
          g = difficultyColor.g,
          b = difficultyColor.b, 
          a = 0.5
        }
      end
    end

    -- Update the context menu
    if data.questID then 
      self.OnClick = function(_, mouseButton)
        if mouseButton == "RightButton" then 
          ShowContextMenu("quest", self, data.questID)
        else
          if data.isAutoComplete and data.isComplete then
            AutoQuestPopupTracker_RemovePopUp(data.questID)
            if IsOnShadowlands() then 
              ShowQuestComplete(data.questID)
            else 
              ShowQuestComplete(data.questLogIndex)
            end
          else 
            QuestMapFrame_OpenToQuestDetails(data.questID)
          end
        end
      end
    end

    -- 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(name)
      ResetStyles(header)
      ResetStyles(tagIcon)

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

      -- Is the quest 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

    -- Tag 
    local tag = data.tag 
    if tag then
      local coords = QUEST_TAG_TCOORDS[tag.tagID]
      if coords then
        tagIcon:SetTexCoord(unpack(coords))
        tagIcon:Show() 
      else 
        tagIcon:Hide()
      end
    else 
      tagIcon:Hide()
    end

    -- Update the conditionnal children if exists
    local objectivesView = self.__objectivesView
    if objectivesView then
      if data.isAutoComplete and data.isComplete then
        objectivesView:UpdateView({
          [1] = { isCompleted = true, text = QUEST_WATCH_QUEST_COMPLETE},
          [2] = { isCompleted = false, text = QUEST_WATCH_CLICK_TO_COMPLETE}
        })
      else 
        objectivesView:UpdateView(data.objectives)
      end
    end

    local itemBadge = self.__itemBadge
    if itemBadge then
      Style[itemBadge].Icon.fileID = data.item.texture 
      if data.item.link then 
        itemBadge.OnLeave = function() GameTooltip:Hide() end
        itemBadge.OnEnter = function()
          GameTooltip:SetOwner(itemBadge, "ANCHOR_LEFT")
          GameTooltip:SetHyperlink(data.item.link)
          GameTooltip:Show()
        end
      end
    end

    self.Flags = flags
  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 AcquireItemBadge(self)
    local itemBadge = self:GetChild("Item")
    if not itemBadge then 
      itemBadge = IconBadge.Acquire()
      self.__previousItemBadgeName = itemBadge:GetName()

      itemBadge:SetParent(self)
      itemBadge:SetName("Item")
      -- REVIEW: Is Inst
      -- itemBadge:InstantApplyStyle()

      self:AdjustHeight()

      self.__itemBadge = itemBadge
    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()

      self.__itemBadge = nil
    end 
  end

  function AcquireObjectives(self)
    local objectives = self:GetChild("Objectives")
    if not objectives then 
      objectives = self.ObjectivesClass.Acquire()

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

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

      -- It's important to only style it once we have set its parent and its new
      -- name
      -- if self.Objectives then 
      --   Style[objectives] = self.Objectives 
      -- end

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

      self:AdjustHeight()

      self.__objectivesView = objectives
    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 after events have been unregistered for avoiding
      -- useless class 
      objectives:Release()

      self:AdjustHeight()

      self.__objectivesView = nil
    end
  end

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


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

    -- "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)


    -- -- REVIEW: Probably find a better way ?
    -- local objectives = self:GetChild("Objectives")
    -- objectives:ReleaseUnusedObjectives(0)

    -- self.Type = nil
  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 "Type" {
    type = QuestView.Type,
    default = QuestView.Type.Common
  }

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

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

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

  -- property "Objectives" {
  --   type = Table,
  -- }

  -- property "NewObjectives" {
  --   type = Table
  -- }
  -----------------------------------------------------------------------------
  --                            Constructors                                 --
  -----------------------------------------------------------------------------
  __Template__{
    -- Objectives = ObjectiveListView,
    Header = Frame,
    {
      Header = {
        Tag  = Texture,
        Name = SLTFontString,
        Level = TextBadge
      }
    }
  }
  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)

    -- local objectives = self:GetChild("Objectives")
    -- objectives.OnSizeChanged = objectives.OnSizeChanged + function(f, ...)
    --   self:AdjustHeight()
    -- end

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

    -- self:AdjustHeight()

    -- Experimental 
    self:SetClipsChildren(true)
  end 
end)


__Recyclable__ "SylingTracker_LegendaryQuestView%d"
class "LegendaryQuestView" { QuestView }

__Recyclable__ "SylingTracker_RaidQuestView%d"
class "RaidQuestView" { QuestView }

__Recyclable__ "SylingTracker_DungeonQuestView%d"
class "DungeonQuestView" { QuestView }


--- Manages the quests, if your view may have various quests, this is advised
-- using this class
__Recyclable__ "SylingTracker_QuestListView%d"
class "QuestListView" (function(_ENV)
  inherit "Frame" extend "IView"
  -----------------------------------------------------------------------------
  --                               Methods                                   --
  -----------------------------------------------------------------------------
  function OnViewUpdate(self, data, updater)
    local questIndex = 0

    wipe(self.questsID)
    wipe(self.questsOrder)
    
    for _, questData in pairs(data) do 
      tinsert(self.questsOrder, questData)
    end

    table.sort(self.questsOrder, function(a, b)
      local aDistance, bDistance = a.distance, b.distance
      if aDistance and bDistance then 
        return aDistance < bDistance 
      end

      return a.questID < b.questID
    end)

    local previousQuest
    for _, questData in ipairs(self.questsOrder) do
      questIndex = questIndex + 1

      local questID     = questData.questID
      local isLegendary = questData.isLegendary
      local isDungeon   = questData.isDungeon
      local isRaid      = questData.isRaid
      local type        = QuestView.Type.Common

      if isLegendary then 
        type = QuestView.Type.Legendary
      elseif isRaid then 
        type = QuestView.Type.Raid 
      elseif isDungeon then 
        type = QuestView.Type.Dungeon 
      end 

      local quest = self:AcquireQuest(questID, type)

      if questIndex > 1 then 
        quest:SetPoint("TOP", previousQuest, "BOTTOM", 0, -5) -- TODO: Add Spacing
        quest:SetPoint("LEFT", 0, 0) -- 4 
        quest:SetPoint("RIGHT", 0, 0) -- -4
      elseif questIndex == 1 then 
        quest:SetPoint("TOP")
        quest:SetPoint("LEFT", 0, 0) -- 4
        quest:SetPoint("RIGHT", 0, 0) -- -4
      end
      quest:UpdateView(questData, updater)

      previousQuest = quest

      self.questsID[questID] = true
    end

    self:ReleaseUnusedQuests()
  end

  __Static__() function IsCorrectObjectForType(obj, type)
    if type == QuestView.Type.Common and Class.IsObjectType(obj, QuestView) then 
      return true 
    elseif type == QuestView.Type.Dungeon and Class.IsObjectType(obj, DungeonQuestView) then 
      return true 
    elseif type == QuestView.Type.Raid and Class.IsObjectType(obj, RaidQuestView) then 
      return true 
    elseif type == QuestView.Type.Legendary and Class.IsObjectType(obj, LegendaryQuestView) then 
      return true 
    end 

    return false 
  end

  function AcquireQuest(self, id, type)
    local quest = self.questsCache[id]
    local new = false 

    if quest and not IsCorrectObjectForType(quest, type) then 
      quest:Release()
      self.questsCache[id] = nil 
      new = true 
    end

    if not quest or new then 
      if type == QuestView.Type.Legendary then 
        quest = LegendaryQuestView.Acquire() 
      elseif type == QuestView.Type.Raid then 
        quest = RaidQuestView.Acquire()
      elseif type == QuestView.Type.Dungeon then 
        quest = DungeonQuestView.Acquire()
      else 
        quest = QuestView.Acquire() 
      end
      quest:Show()

      quest:SetParent(self)

      quest.OnSizeChanged = quest.OnSizeChanged + self.OnQuestSizeChanged

      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.OnQuestSizeChanged
        quest:Release()
        self:AdjustHeight()
      end 
    end 
  end 

  function OnAdjustHeight(self)
    local height = 0
    local count = 0
    for _, child in IterateFrameChildren(self) do
      height = height + child:GetHeight() 

      count = count + 1
    end

    height = height + 5 * math.max(0, count-1)

    -- self:SetHeight(height)
    PixelUtil.SetHeight(self, height)
  end

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

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

    self:SetHeight(1)

    ResetStyles(self)
  end

  function OnAcquire(self)
    self:Show()
  end
  -----------------------------------------------------------------------------
  --                            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 quest, to be reused. 
    -- use: self.questsCache[questID] = questObject
    self.questsCache = setmetatable({}, { __mode = "v"})

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

    self.questsOrder = {}


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


    self:SetClipsChildren(true)
  end

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

    -- NormalTexture = {
    --   file = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]],
    --   vertexColor =  { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
    --   setAllPoints = true,
    -- },

    -- HighlightTexture = {
    --   file = [[Interface\AddOns\SylingTracker\Media\Textures\LinearGradient]],
    --   vertexColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.1},
    --   setAllPoints = true,
    -- },
    
    -- Header Child
    Header = {
      height = 24,
      location = {
        Anchor("TOPLEFT"),
        Anchor("TOPRIGHT")
      },
      
      Tag = {
        height = 18,
        width  = 18,
        file = QUEST_ICONS_FILE,
        location = {
          Anchor("LEFT", 3, 0)
        }
      },

      -- Header/Name child 
      Name = {
        location = {
          Anchor("TOP"),
          Anchor("LEFT", 0, 0, "Tag", "RIGHT"),
          Anchor("RIGHT", 0, 0, "Level", "LEFT"),
          Anchor("BOTTOM")
        },
        sharedMediaFont = FontType("DejaVuSansCondensed Bold", 10)
      },
      -- Header/Level child 
      Level = {
        height = 16,
        width  = 30,
        backdropColor = { r = 160/255, g = 160/255, b = 160/255, a = 0.5},
        location = {
          Anchor("RIGHT", -5, 0)
        },

        Label = {
          justifyH = "RIGHT"
        }
      },
    },

    FlagsStyles = {
      [QuestView.Flags.HAS_OBJECTIVES] = {
        Objectives = {
          spacing = 5,
          location = {
            Anchor("TOP", 0, -4, "Header", "BOTTOM"),
            Anchor("LEFT"),
            Anchor("RIGHT")
          }
        }
      },
      [QuestView.Flags.HAS_OBJECTIVES + QuestView.Flags.HAS_ITEM] = {
        Item = {
          height = 28,
          width  = 28,
          location = {
            Anchor("TOPLEFT", 4, -4, "Header", "BOTTOMLEFT")
          },

          Icon = {
            texCoords = RectType(0.07, 0.93, 0.07, 0.93)
          }
        },
        Objectives = {
          spacing = 5,
          location = {
            Anchor("TOP", 2, -4, "Header", "BOTTOM"),
            Anchor("LEFT", 2, 0, "Item", "RIGHT"),
            Anchor("RIGHT")            
          }
        }
      }
    }
  },
  -- [LegendaryQuestView] = {
  --   backdropColor = { r = 35/255, g = 40/255, b = 46/255, a = 0.73},
  -- },

  [RaidQuestView] = {
    backdropColor = { r = 0, g = 84/255, b = 2/255, a = 0.73}
  },
  [DungeonQuestView] = {
    backdropColor = { r = 0, g = 72/255, b = 124/255, a = 0.73 }
  }
})
