local WeakAuras = WeakAuras;
local L = WeakAuras.L;

WeakAuras.regionPrototype = {};

-- Adjusted Duration

function WeakAuras.regionPrototype.AddAdjustedDurationToDefault(default)
  default.useAdjustededMax = false;
  default.useAdjustededMin = false;
end

function WeakAuras.regionPrototype.AddAdjustedDurationOptions(options, data, order)
  options.useAdjustededMin = {
    type = "toggle",
    name = L["Set Minimum Progress"],
    desc = L["Values/Remaining Time below this value are displayed as no progress."],
    order = order
  };

  options.adjustedMin = {
    type = "range",
    min = 0,
    softMax = 200,
    bigStep = 1,
    order = order + 0.01,
    name = L["Minimum"],
    hidden = function() return not data.useAdjustededMin end,
  };

  options.useAdjustedMinSpacer = {
    type = "description",
    width = "normal",
    name = "",
    order = order + 0.02,
    hidden = function() return not (not data.useAdjustededMin and data.useAdjustededMax) end,
  };

  options.useAdjustededMax = {
    type = "toggle",
    name = L["Set Maximum Progress"],
    desc = L["Values/Remaining Time above this value are displayed as full progress."],
    order = order + 0.03
  };

  options.adjustedMax = {
    type = "range",
    min = 0,
    softMax = 200,
    bigStep = 1,
    order = order + 0.04,
    name = L["Maximum"],
    hidden = function() return not data.useAdjustededMax end,
  };

  options.useAdjustedMaxSpacer = {
    type = "description",
    width = "normal",
    name = "",
    order = order + 0.05,
    hidden = function() return not (data.useAdjustededMin and not data.useAdjustededMax) end,
  };

  return options;
end

-- Sound / Chat Message / Custom Code

function WeakAuras.regionPrototype.AddProperties(properties)
  properties["sound"] = {
    display = L["Sound"],
    action = "SoundPlay",
    type = "sound",
  };
  properties["chat"] = {
    display = L["Chat Message"],
    action = "SendChat",
    type = "chat",
  };
  properties["customcode"] = {
    display = L["Run Custom Code"],
    action = "RunCode",
    type = "customcode"
  }
end

local function SoundRepeatStop(self)
  if (self.soundRepeatTimer) then
    WeakAuras.timer:CancelTimer(self.soundRepeatTimer);
    self.soundRepeatTimer = nil;
  end
end

local function SoundStop(self)
  if (self.soundHandle) then
    StopSound(self.soundHandle);
  end
end

local function SoundPlayHelper(self)
  local options = self.soundOptions;
  self.soundHandle = nil;
  if (options.sound_type == "Stop") then
    return;
  end

  if (WeakAuras.IsOptionsOpen()) then
    return;
  end

  if (options.sound == " custom") then
    if (options.sound_path) then
      local _, handle = PlaySoundFile(options.sound_path, options.sound_channel or "Master");
      self.soundHandle = handle;
    end
  elseif (options.sound == " KitID") then
    if (options.sound_kit_id) then
      local _, handle = PlaySoundKitID(options.sound_kit_id, options.sound_channel or "Master");
      self.soundHandle = handle;
    end
  else
    local _, handle = PlaySoundFile(options.sound, options.sound_channel or "Master");
    self.soundHandle = handle;
  end
end

local function SoundPlay(self, options)
  self:SoundStop();
  self:SoundRepeatStop();

  self.soundOptions = options;
  SoundPlayHelper(self);

  local loop = options.do_loop or options.sound_type == "Loop";
  if (loop and options.sound_repeat) then
    self.soundRepeatTimer = WeakAuras.timer:ScheduleRepeatingTimer(SoundPlayHelper, options.sound_repeat, self);
  end
end

local function SendChat(self, options)
  WeakAuras.HandleChatAction(options.message_type, options.message, options.message_dest, options.message_channel, options.r, options.g, options.b, self, options.message_custom);
end

local function RunCode(self, func)
  if func then
    WeakAuras.ActivateAuraEnvironment(self.id, self.cloneId, self.state);
    func();
    WeakAuras.ActivateAuraEnvironment(nil);
  end
end

function WeakAuras.regionPrototype.create(region)
  region.SoundPlay = SoundPlay;
  region.SoundStop = SoundStop;
  region.SoundRepeatStop = SoundRepeatStop;
  region.SendChat = SendChat;
  region.RunCode = RunCode;
end

-- SetDurationInfo

function WeakAuras.regionPrototype.modify(parent, region, data)
  local defaultsForRegion = WeakAuras.regionTypes[data.regionType] and WeakAuras.regionTypes[data.regionType].default;
  local hasAdjustedMin = defaultsForRegion and defaultsForRegion.useAdjustededMin ~= nil and data.useAdjustededMin;
  local hasAdjustedMax = defaultsForRegion and defaultsForRegion.useAdjustededMax ~= nil and data.useAdjustededMax;

  region.adjustedMin = hasAdjustedMin and data.adjustedMin and data.adjustedMin > 0 and data.adjustedMin;
  region.adjustedMax = hasAdjustedMax and data.adjustedMax and data.adjustedMax > 0 and data.adjustedMax;
end

local function SetProgressValue(region, value, total)
  region.values.progress = value;
  region.values.duration = total;

  local adjustMin = region.adjustedMin or 0;
  local max = region.adjustedMax or total;

  region:SetValue(value - adjustMin, max - adjustMin);
end

local function UpateRegionValues(region)
  local remaining  = region.expirationTime - GetTime();
  local duration  = region.duration;

  local remainingStr     = "";
  if remaining == math.huge then
    remainingStr     = " ";
  elseif remaining > 60 then
    remainingStr     = string.format("%i:", math.floor(remaining / 60));
    remaining       = remaining % 60;
    remainingStr     = remainingStr..string.format("%02i", remaining);
  elseif remaining > 0 then
    -- remainingStr = remainingStr..string.format("%."..(data.progressPrecision or 1).."f", remaining);
    if region.progressPrecision == 4 and remaining <= 3 then
      remainingStr = remainingStr..string.format("%.1f", remaining);
    elseif region.progressPrecision == 5 and remaining <= 3 then
      remainingStr = remainingStr..string.format("%.2f", remaining);
    elseif (region.progressPrecision == 4 or region.progressPrecision == 5) and remaining > 3 then
      remainingStr = remainingStr..string.format("%d", remaining);
    else
      remainingStr = remainingStr..string.format("%."..(region.progressPrecision or 1).."f", remaining);
    end
  else
    remainingStr     = " ";
  end
  region.values.progress   = remainingStr;

  -- Format a duration time string
  local durationStr     = "";
  if duration > 60 then
    durationStr     = string.format("%i:", math.floor(duration / 60));
    duration       = duration % 60;
    durationStr     = durationStr..string.format("%02i", duration);
  elseif duration > 0 then
    -- durationStr = durationStr..string.format("%."..(data.totalPrecision or 1).."f", duration);
    if region.totalPrecision == 4 and duration <= 3 then
      durationStr = durationStr..string.format("%.1f", duration);
    elseif region.totalPrecision == 5 and duration <= 3 then
      durationStr = durationStr..string.format("%.2f", duration);
    elseif (region.totalPrecision == 4 or region.totalPrecision == 5) and duration > 3 then
      durationStr = durationStr..string.format("%d", duration);
    else
      durationStr = durationStr..string.format("%."..(region.totalPrecision or 1).."f", duration);
    end
  else
    durationStr     = " ";
  end
  region.values.duration   = durationStr;
end

function WeakAuras.TimerTick(region)
  UpateRegionValues(region);
  region:TimerTick();
end

function WeakAuras.regionPrototype.AddSetDurationInfo(region)
  if (region.SetValue and region.SetTime and region.TimerTick) then
    region.generatedSetDurationInfo = true;
    region.SetValueFromCustomValueFunc = function()
      local value, total = region.customValueFunc(region.state.trigger);
      value = type(value) == "number" and value or 0
      total = type(value) == "number" and total or 0
      SetProgressValue(region, value, total);
    end

    region.SetDurationInfo = function(self, duration, expirationTime, customValue, inverse)
      if duration <= 0 or duration > self.duration or not region.stickyDuration then
        self.duration = duration;
      end
      self.expirationTime = expirationTime;
      self.inverse = inverse;

      if customValue then
        if type(customValue) == "function" then
          local value, total = customValue(region.state.trigger);
          value = type(value) == "number" and value or 0
          total = type(value) == "number" and total or 0
          if total > 0 and value < total then
            self.customValueFunc = customValue;
            self:SetScript("OnUpdate", region.SetValueFromCustomValueFunc);
          else
            SetProgressValue(region, duration, expirationTime);
            self:SetScript("OnUpdate", nil);
          end
        else
          SetProgressValue(region, duration, expirationTime);
          self:SetScript("OnUpdate", nil);
        end
      else
        UpateRegionValues(region);
        local adjustMin = region.adjustedMin or 0;
        region:SetTime((region.adjustedMax or duration) - adjustMin, expirationTime - adjustMin, inverse);
        if duration > 0 then
          self:SetScript("OnUpdate", function() WeakAuras.TimerTick(region) end);
        else
          self:SetScript("OnUpdate", nil);
        end
      end
    end
  elseif (region.generatedSetDurationInfo) then
    region.generatedSetDurationInfo = nil;
    region.SetDurationInfo = nil;
    region.SetValueFromCustomValueFunc = nil;
    region:SetScript("OnUpdate", nil);
  end
end

-- Expand/Collapse function

function WeakAuras.regionPrototype.AddExpandFunction(data, region, id, cloneId, parent, parentRegionType)
  local indynamicgroup = parentRegionType == "dynamicgroup";
  local ingroup = parentRegionType == "group";

  local startMainAnimation = function()
    WeakAuras.Animate("display", data, "main", data.animation.main, region, false, nil, true, cloneId);
  end

  local hideRegion;
  if(indynamicgroup) then
    hideRegion = function()
      region:Hide();
      if (cloneId) then
        WeakAuras.ReleaseClone(id, cloneId, data.regionType);
      end
      parent:ControlChildren();
    end
  else
    hideRegion = function()
      region:Hide();
      if (cloneId) then
        WeakAuras.ReleaseClone(id, cloneId, data.regionType);
      end
    end
  end

  if(indynamicgroup) then
    if not(cloneId) then
      parent:PositionChildren();
    end
    function region:Collapse()
      if (not region.toShow) then
        return;
      end
      region.toShow = false;

      WeakAuras.PerformActions(data, "finish", region);
      if (not WeakAuras.Animate("display", data, "finish", data.animation.finish, region, false, hideRegion, nil, cloneId)) then
        hideRegion();
      end
      parent:ControlChildren();

      if (region.SoundRepeatStop) then
        region:SoundRepeatStop();
      end
    end
    function region:Expand()
      if (region.toShow) then
        return;
      end
      region.toShow = true;
      if(region.PreShow) then
        region:PreShow();
      end

      parent:EnsureTrays();
      region.justCreated = nil;
      WeakAuras.PerformActions(data, "start", region);
      if not(WeakAuras.Animate("display", data, "start", data.animation.start, region, true, startMainAnimation, nil, cloneId)) then
        startMainAnimation();
      end
      parent:ControlChildren();
    end
  elseif not(data.controlledChildren) then
    function region:Collapse()
      if (not region.toShow) then
        return;
      end
      region.toShow = false;

      WeakAuras.PerformActions(data, "finish", region);
      if (not WeakAuras.Animate("display", data, "finish", data.animation.finish, region, false, hideRegion, nil, cloneId)) then
        hideRegion();
      end

      if ingroup then
        parent:UpdateBorder(region);
      end

      if (region.SoundRepeatStop) then
        region:SoundRepeatStop();
      end
    end
    function region:Expand()
      if (region.toShow) then
        return;
      end
      region.toShow = true;

      if (data.anchorFrameType == "SELECTFRAME") then
        WeakAuras.AnchorFrame(data, region, parent);
      end

      region.justCreated = nil;
      if(region.PreShow) then
        region:PreShow();
      end
      region:Show();
      WeakAuras.PerformActions(data, "start", region);
      if not(WeakAuras.Animate("display", data, "start", data.animation.start, region, true, startMainAnimation, nil, cloneId)) then
        startMainAnimation();
      end

      if ingroup then
        parent:UpdateBorder(region);
      end
    end
  end
  -- Stubs that allow for polymorphism
  if not region.Collapse then
    function region:Collapse() end
  end
  if not region.Expand then
    function region:Expand() end
  end
end
