if not WeakAuras.IsCorrectVersion() then return end
local AddonName, Private = ...

local SharedMedia = LibStub("LibSharedMedia-3.0");
local L = WeakAuras.L
local MSQ, MSQ_Version = LibStub("Masque", true);
if MSQ then
  if MSQ_Version <= 80100 then
    MSQ = nil
   WeakAuras.prettyPrint(L["Please upgrade your Masque version"])
  else
    MSQ:AddType("WA_Aura", {"Icon", "Cooldown"})
  end
end

-- WoW API
local _G = _G

local default = {
  icon = true,
  desaturate = false,
  iconSource = -1,
  inverse = false,
  width = 64,
  height = 64,
  color = {1, 1, 1, 1},
  selfPoint = "CENTER",
  anchorPoint = "CENTER",
  anchorFrameType = "SCREEN",
  xOffset = 0,
  yOffset = 0,
  zoom = 0,
  keepAspectRatio = false,
  frameStrata = 1,
  cooldown = false,
  cooldownEdge = false
};

WeakAuras.regionPrototype.AddAlphaToDefault(default);

local screenWidth, screenHeight = math.ceil(GetScreenWidth() / 20) * 20, math.ceil(GetScreenHeight() / 20) * 20;

local properties = {
  desaturate = {
    display = L["Desaturate"],
    setter = "SetDesaturated",
    type = "bool",
  },
  width = {
    display = L["Width"],
    setter = "SetRegionWidth",
    type = "number",
    min = 1,
    softMax = screenWidth,
    bigStep = 1,
    default = 32
  },
  height = {
    display = L["Height"],
    setter = "SetRegionHeight",
    type = "number",
    min = 1,
    softMax = screenHeight,
    bigStep = 1,
    default = 32
  },
  color = {
    display = L["Color"],
    setter = "Color",
    type = "color"
  },
  inverse = {
    display = L["Inverse"],
    setter = "SetInverse",
    type = "bool"
  },
  cooldownEdge = {
    display = { L["Cooldown"], L["Edge"]},
    setter = "SetCooldownEdge",
    type = "bool",
  },
  zoom = {
    display = L["Zoom"],
    setter = "SetZoom",
    type = "number",
    min = 0,
    max = 1,
    step = 0.01,
    default = 0,
    isPercent = true
  },
  iconSource = {
    display = {L["Icon"], L["Source"]},
    setter = "SetIconSource",
    type = "list",
    values = {}
  },
  displayIcon = {
    display = {L["Icon"], L["Fallback"]},
    setter = "SetIcon",
    type = "icon",
  }
};

WeakAuras.regionPrototype.AddProperties(properties, default);

local function GetProperties(data)
  local result = CopyTable(properties)
  result.iconSource.values = Private.IconSources(data)
  return result
end

local function GetTexCoord(region, texWidth, aspectRatio)
  region.currentCoord = region.currentCoord or {}
  local usesMasque = false
  if region.MSQGroup then
    local db = region.MSQGroup.db
    if db and not db.Disabled then
      usesMasque = true
      region.currentCoord[1], region.currentCoord[2], region.currentCoord[3], region.currentCoord[4], region.currentCoord[5], region.currentCoord[6], region.currentCoord[7], region.currentCoord[8] = region.icon:GetTexCoord()
    end
  end
  if (not usesMasque) then
    region.currentCoord[1], region.currentCoord[2], region.currentCoord[3], region.currentCoord[4], region.currentCoord[5], region.currentCoord[6], region.currentCoord[7], region.currentCoord[8] = 0, 0, 0, 1, 1, 0, 1, 1;
  end

  local xRatio = aspectRatio < 1 and aspectRatio or 1;
  local yRatio = aspectRatio > 1 and 1 / aspectRatio or 1;
  for i, coord in ipairs(region.currentCoord) do
    local aspectRatio = (i % 2 == 1) and xRatio or yRatio;
    region.currentCoord[i] = (coord - 0.5) * texWidth * aspectRatio + 0.5;
  end

  return unpack(region.currentCoord)
end

local function AnchorSubRegion(self, subRegion, anchorType, selfPoint, anchorPoint, anchorXOffset, anchorYOffset)
  if anchorType == "area" then
    WeakAuras.regionPrototype.AnchorSubRegion(selfPoint == "region" and self or self.icon, subRegion, anchorType, selfPoint, anchorPoint, anchorXOffset, anchorYOffset)
  else
    subRegion:ClearAllPoints()
    anchorPoint = anchorPoint or "CENTER"
    local anchorRegion = self.icon
    if anchorPoint:sub(1, 6) == "INNER_" then
      if not self.inner then
        self.inner = CreateFrame("FRAME", nil, self)
        self.inner:SetPoint("CENTER")
        self.UpdateInnerOuterSize()
      end
      anchorRegion = self.inner
      anchorPoint = anchorPoint:sub(7)
    elseif anchorPoint:sub(1, 6) == "OUTER_" then
      if not self.outer then
        self.outer = CreateFrame("FRAME", nil, self)
        self.outer:SetPoint("CENTER")
        self.UpdateInnerOuterSize()
      end
      anchorRegion = self.outer
      anchorPoint = anchorPoint:sub(7)
    end
    anchorXOffset = anchorXOffset or 0
    anchorYOffset = anchorYOffset or 0

    if not Private.point_types[selfPoint] then
      selfPoint = "CENTER"
    end

    if not Private.point_types[anchorPoint] then
      anchorPoint = "CENTER"
    end

    subRegion:SetPoint(selfPoint, anchorRegion, anchorPoint, anchorXOffset, anchorYOffset)
  end
end

local function setDesaturated(self, desaturated, ...)
  self.isDesaturated = desaturated and 1 or 0
  return self._SetDesaturated(self, desaturated, ...)
end

local function setTexture(self, ...)
  local apply = self._SetTexture(self, ...)
  if self.isDesaturated ~= nil then
    self:_SetDesaturated(self.isDesaturated)
  end
  return apply
end

local function cooldown_onUpdate(self, e)
  if self._duration then
    if self._delay then
      if not self._paused then
        self._delay = self._delay - e
      end
      self:_SetCooldown(GetTime(), self._duration)
      if self._delay <= 0 then
        self._delay = nil
        if not self._paused then
          self:SetScript("OnUpdate", nil)
        end
      end
      return
    end
    if self._paused then
      self:_SetCooldown(GetTime() - self._paused, self._duration)
    end
  end
end

local function cooldown_pause(self)
  if not self._paused then
    self._paused = GetTime() - (self._start or 0)
    self:SetScript("OnUpdate", cooldown_onUpdate)
  end
end

local function cooldown_resume(self)
  if self._paused then
    self._start = GetTime() - self._paused
  end
  self._paused = nil
  self:SetScript("OnUpdate", nil)
end

local function cooldown_setCooldown(self, start, duration, ...)
  if self._start == start and self._duration == duration then
    return
  end
  self._start = start
  self._duration = duration
  local getTime = GetTime()
  local delay = start - getTime
  if delay > 0 then
    self._delay = delay
    self:SetScript("OnUpdate", cooldown_onUpdate)
  end
  if self._paused then
    self._paused = getTime - self._start
  end
  return self._SetCooldown(self, math.min(getTime, start), duration, ...)
end

local function create(parent, data)
  local font = "GameFontHighlight";

  local region = CreateFrame("FRAME", nil, parent);
  region.regionType = "icon"
  region:SetMovable(true);
  region:SetResizable(true);
  region:SetMinResize(1, 1);

  function region.UpdateInnerOuterSize()
    local width = region.width * math.abs(region.scalex);
    local height = region.height * math.abs(region.scaley);

    local iconWidth
    local iconHeight

    if MSQ then
      iconWidth = region.button:GetWidth()
      iconHeight = region.button:GetHeight()
    else
      iconWidth = region:GetWidth()
      iconHeight = region:GetHeight()
    end

    if region.inner then
      region.inner:SetSize(iconWidth - 0.2 * width, iconHeight - 0.2 * height)
    end
    if region.outer then
      region.outer:SetSize(iconWidth + 0.1 * width, iconHeight + 0.1 * height)
    end
  end

  local button
  if MSQ then
    button = CreateFrame("Button", nil, region)
    button.data = data
    region.button = button;
    button:EnableMouse(false);
    button:Disable();
    button:SetAllPoints();
  end

  local icon = region:CreateTexture(nil, "BACKGROUND");
  if MSQ then
    icon:SetAllPoints(button);
    button:SetScript("OnSizeChanged", region.UpdateInnerOuterSize);
  else
    icon:SetAllPoints(region);
    region:SetScript("OnSizeChanged", region.UpdateInnerOuterSize);
  end
  region.icon = icon;
  icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark");

  icon._SetDesaturated = icon.SetDesaturated
  icon.SetDesaturated = setDesaturated
  icon._SetTexture = icon.SetTexture
  icon.SetTexture = setTexture

  --This section creates a unique frame id for the cooldown frame so that it can be created with a global reference
  --The reason is so that WeakAuras cooldown frames can interact properly with OmniCC (i.e., put on its ignore list for timer overlays)
  local id = data.id;
  local frameId = id:lower():gsub(" ", "_");
  if(_G["WeakAurasCooldown"..frameId]) then
    local baseFrameId = frameId;
    local num = 2;
    while(_G["WeakAurasCooldown"..frameId]) do
      frameId = baseFrameId..num;
      num = num + 1;
    end
  end
  region.frameId = frameId;

  local cooldown = CreateFrame("COOLDOWN", "WeakAurasCooldown"..frameId, region, "CooldownFrameTemplate");
  region.cooldown = cooldown;
  cooldown:SetAllPoints(icon);

  cooldown._SetCooldown = cooldown.SetCooldown;
  cooldown.SetCooldown = cooldown_setCooldown;
  cooldown.Pause = cooldown_pause;
  cooldown.Resume = cooldown_resume;

  local SetFrameLevel = region.SetFrameLevel;

  function region.SetFrameLevel(self, level)
    SetFrameLevel(region, level);
    cooldown:SetFrameLevel(level);
    if button then
      button:SetFrameLevel(level);
    end
  end

  WeakAuras.regionPrototype.create(region);

  region.AnchorSubRegion = AnchorSubRegion

  return region;
end

local function modify(parent, region, data)
  -- Legacy members stacks and text2
  region.stacks = nil
  region.text2 = nil

  WeakAuras.regionPrototype.modify(parent, region, data);

  local button, icon, cooldown = region.button, region.icon, region.cooldown;

  region.iconSource = data.iconSource
  region.displayIcon = data.displayIcon

  if MSQ then
    local masqueId = data.id:lower():gsub(" ", "_");
    if region.masqueId ~= masqueId then
      region.masqueId = masqueId
      region.MSQGroup = MSQ:Group("WeakAuras", region.masqueId, data.uid);
      region.MSQGroup:SetName(data.id)
      region.MSQGroup:AddButton(button, {Icon = icon, Cooldown = cooldown}, "WA_Aura", true);
      button.data = data
    end
  end

  function region:UpdateSize()
    local width = region.width * math.abs(region.scalex);
    local height = region.height * math.abs(region.scaley);
    region:SetWidth(width);
    region:SetHeight(height);
    if MSQ then
      button:SetWidth(width);
      button:SetHeight(height);
      button:SetAllPoints();
    end
    region:UpdateTexCoords();
  end

  function region:UpdateTexCoords()
    local mirror_h = region.scalex < 0;
    local mirror_v = region.scaley < 0;

    local texWidth = 1 - 0.5 * region.zoom;
    local aspectRatio
    if not region.keepAspectRatio then
      aspectRatio = 1;
    else
      local width = region.width * math.abs(region.scalex);
      local height = region.height * math.abs(region.scaley);

      if width == 0 or height == 0 then
        aspectRatio = 1;
      else
        aspectRatio = width / height;
      end
    end

    if region.MSQGroup then
      region.MSQGroup:RemoveButton(button)
      region.MSQGroup:AddButton(button, {Icon = icon, Cooldown = cooldown}, "WA_Aura", true)
    end

    local ulx, uly, llx, lly, urx, ury, lrx, lry = GetTexCoord(region, texWidth, aspectRatio)

    if(mirror_h) then
      if(mirror_v) then
        icon:SetTexCoord(lrx, lry, urx, ury, llx, lly, ulx, uly)
      else
        icon:SetTexCoord(urx, ury, lrx, lry, ulx, uly, llx, lly)
      end
    else
      if(mirror_v) then
        icon:SetTexCoord(llx, lly, ulx, uly, lrx, lry, urx, ury)
      else
        icon:SetTexCoord(ulx, uly, llx, lly, urx, ury, lrx, lry)
      end
    end
  end

  region.width = data.width;
  region.height = data.height;
  region.scalex = 1;
  region.scaley = 1;
  region.keepAspectRatio = data.keepAspectRatio;
  region.zoom = data.zoom;
  region:UpdateSize()

  icon:SetDesaturated(data.desaturate);

  local tooltipType = Private.CanHaveTooltip(data);
  if(tooltipType and data.useTooltip) then
    if not region.tooltipFrame then
      region.tooltipFrame = CreateFrame("frame", nil, region);
      region.tooltipFrame:SetAllPoints(region);
      region.tooltipFrame:SetScript("OnEnter", function()
        Private.ShowMouseoverTooltip(region, region);
      end);
      region.tooltipFrame:SetScript("OnLeave", Private.HideTooltip);
    end
    region.tooltipFrame:EnableMouse(true);
  elseif region.tooltipFrame then
    region.tooltipFrame:EnableMouse(false);
  end

  cooldown:SetReverse(not data.inverse);

  function region:Color(r, g, b, a)
    region.color_r = r;
    region.color_g = g;
    region.color_b = b;
    region.color_a = a;
    if (r or g or b) then
      a = a or 1;
    end
    icon:SetVertexColor(region.color_anim_r or r, region.color_anim_g or g, region.color_anim_b or b, region.color_anim_a or a);
    if region.button then
      region.button:SetAlpha(region.color_anim_a or a or 1);
    end
  end

  function region:ColorAnim(r, g, b, a)
    region.color_anim_r = r;
    region.color_anim_g = g;
    region.color_anim_b = b;
    region.color_anim_a = a;
    if (r or g or b) then
      a = a or 1;
    end
    icon:SetVertexColor(r or region.color_r, g or region.color_g, b or region.color_b, a or region.color_a);
    if MSQ then
      region.button:SetAlpha(a or region.color_a or 1);
    end
  end

  function region:GetColor()
    return region.color_r or data.color[1], region.color_g or data.color[2],
      region.color_b or data.color[3], region.color_a or data.color[4];
  end

  region:Color(data.color[1], data.color[2], data.color[3], data.color[4]);

  function region:SetIcon(iconPath)
    if self.displayIcon == iconPath then
      return
    end
    self.displayIcon = iconPath
    self:UpdateIcon()
  end

  function region:SetIconSource(source)
    if self.iconSource == source then
      return
    end

    self.iconSource = source
    self:UpdateIcon()
  end

  function region:UpdateIcon()
    local iconPath
    if self.iconSource == -1 then
      iconPath = self.state.icon
    elseif self.iconSource == 0 then
      iconPath = self.displayIcon
    else
      local triggernumber = self.iconSource
      if triggernumber and self.states[triggernumber] then
        iconPath = self.states[triggernumber].icon
      end
    end

    iconPath = iconPath or self.displayIcon or "Interface\\Icons\\INV_Misc_QuestionMark"
    icon:SetTexture(iconPath)
  end

  function region:Scale(scalex, scaley)
    if region.scalex == scalex and region.scaley == scaley then
      return
    end
    region.scalex = scalex;
    region.scaley = scaley;
    region:UpdateSize();
  end

  function region:SetDesaturated(b)
    icon:SetDesaturated(b);
  end

  function region:SetRegionWidth(width)
    region.width = width
    region:UpdateSize();
  end

  function region:SetRegionHeight(height)
    region.height = height
    region:UpdateSize();
  end

  function region:SetInverse(inverse)
    cooldown:SetReverse(not inverse);
    if (cooldown.expirationTime and cooldown.duration and cooldown:IsShown()) then
      -- WORKAROUND SetReverse not applying until next frame
      cooldown:SetCooldown(0, 0);
      cooldown:SetCooldown(cooldown.expirationTime - cooldown.duration, cooldown.duration);
    end
  end

  function region:SetCooldownEdge(cooldownEdge)
    region.cooldownEdge = cooldownEdge;
    cooldown:SetDrawEdge(cooldownEdge);
  end

  region:SetCooldownEdge(data.cooldownEdge)

  function region:SetZoom(zoom)
    region.zoom = zoom;
    region:UpdateTexCoords();
  end

  cooldown.expirationTime = nil;
  cooldown.duration = nil;
  cooldown:Hide()
  if(data.cooldown) then
    function region:SetValue(value, total)
      cooldown.value = value
      cooldown.total = total
      if (value >= 0 and value <= total) then
        cooldown:Show()
        cooldown:SetCooldown(GetTime() - (total - value), total)
      else
        cooldown:Hide();
      end
    end

    function region:SetTime(duration, expirationTime)
      if (duration > 0 and expirationTime > GetTime()) then
        cooldown:Show();
        cooldown.expirationTime = expirationTime;
        cooldown.duration = duration;
        cooldown:SetCooldown(expirationTime - duration, duration);
      else
        cooldown.expirationTime = expirationTime;
        cooldown.duration = duration;
        cooldown:Hide();
      end
    end

    function region:PreShow()
      if (cooldown.duration and cooldown.duration > 0.01) then
        cooldown:Show();
        cooldown:SetCooldown(cooldown.expirationTime - cooldown.duration, cooldown.duration);
        cooldown:Resume()
      end
    end

    function region:Update()
      local state = region.state
      if state.progressType == "timed" then
        local expirationTime
        if state.paused == true then
          if not region.paused then
            region:Pause()
          end
          cooldown:Pause()
          expirationTime = GetTime() + (state.remaining or 0)
        else
          if region.paused then
            region:Resume()
          end
          cooldown:Resume()
          expirationTime = state.expirationTime and state.expirationTime > 0 and state.expirationTime or math.huge;
        end

        local duration = state.duration or 0
        if region.adjustedMinRelPercent then
          region.adjustedMinRel = region.adjustedMinRelPercent * duration
        end
        local adjustMin = region.adjustedMin or region.adjustedMinRel or 0;

        local max
        if duration == 0 then
          max = 0
        elseif region.adjustedMax then
          max = region.adjustedMax
        elseif region.adjustedMaxRelPercent then
          region.adjustedMaxRel = region.adjustedMaxRelPercent * duration
          max = region.adjustedMaxRel
        else
          max = duration
        end

        region:SetTime(max - adjustMin, expirationTime - adjustMin, state.inverse);
      elseif state.progressType == "static" then
        local value = state.value or 0;
        local total = state.total or 0;
        if region.adjustedMinRelPercent then
          region.adjustedMinRel = region.adjustedMinRelPercent * total
        end
        local adjustMin = region.adjustedMin or region.adjustedMinRel or 0;
        local max = region.adjustedMax or region.adjustedMaxRel or total;
        region:SetValue(value - adjustMin, max - adjustMin);
        cooldown:Pause()
      else
        region:SetTime(0, math.huge)
      end

      region:UpdateIcon()
    end
  else
    region.SetValue = nil
    region.SetTime = nil

    function region:Update()
      local state = region.state
      region:UpdateIcon()
    end
  end

  -- Backwards compability function
  function region:SetGlow(glow)
    for index, subRegion in ipairs(self.subRegions) do
      if subRegion.type == "subglow" then
        subRegion:SetVisible(glow)
      end
    end
  end

  WeakAuras.regionPrototype.modifyFinish(parent, region, data);

  --- WORKAROUND
  -- This fixes a issue with barmodels not appearing on icons if the
  -- icon is shown delayed
  region:SetWidth(region:GetWidth())
  region:SetHeight(region:GetHeight())
end

local function validate(data)
  Private.EnforceSubregionExists(data, "subbackground")
end

WeakAuras.RegisterRegionType("icon", create, modify, default, GetProperties, validate)
