-- Import SM for statusbar-textures, font-styles and border-types
local SharedMedia = LibStub("LibSharedMedia-3.0");

-- Default settings
local default = {
  icon         = true,
  desaturate       = false,
  auto         = true,
  barInFront      = true,
  border        = false,
  timer         = true,
  text         = true,
  stacks         = true,
  textColor       = {1.0, 1.0, 1.0, 1.0},
  timerColor       = {1.0, 1.0, 1.0, 1.0},
  stacksColor     = {1.0, 1.0, 1.0, 1.0},  
  textFont       = "Friz Quadrata TT",
  timerFont       = "Friz Quadrata TT",
  stacksFont       = "Friz Quadrata TT",
  textSize       = 12,
  timerSize       = 12,
  stacksSize       = 12,
  textFlags      = "None",
  timerFlags      = "None",
  stacksFlags      = "None",
  displayTextRight   = "%p",
  displayTextLeft   = "%n",
  texture       = "Blizzard",
  width         = 200,
  height         = 15,
  orientation     = "HORIZONTAL",
  inverse       = false,
  alpha         = 1.0,
  barColor       = {1.0, 0.0, 0.0, 1.0},
  backgroundColor   = {0.0, 0.0, 0.0, 0.5},
  borderColor     = {1.0, 1.0, 1.0, 0.5},
  backdropColor    = {1.0, 1.0, 1.0, 0.5},
  borderEdge      = "None",
  borderOffset     = 5,
  borderInset      = 11,
  borderSize      = 16,
  borderBackdrop    = "Blizzard Tooltip",
  selfPoint       = "CENTER",
  anchorPoint     = "CENTER",
  xOffset       = 0,
  yOffset       = 0,
  stickyDuration     = false,
  icon_side       = "RIGHT",
  rotateText       = "NONE",
  frameStrata     = 1,
  customTextUpdate   = "update"
};

-- Emulate blizzard statusbar with advanced features (more grow directions)
local barPrototype = {
  -- Apply settings to bar (re-align textures)
  ["Update"] = function(self, OnSizeChanged)
    -- Limit values
    self.value   = math.max(self.min, self.value);
    self.value   = math.min(self.max, self.value);
    
    -- Alignment variables
    local progress = (self.value - self.min) / (self.max - self.min);
    local align1, align2;
    local xProgress, yProgress;
    local TLx,  TLy,  BLx,  BLy,  TRx,  TRy,  BRx,  BRy;
    local TLx_, TLy_, BLx_, BLy_, TRx_, TRy_, BRx_, BRy_;
    
    -- Do not flip/rotate textures
    local orientation = self.orientation;
    if not self.rotate then
      if orientation == "HORIZONTAL_INVERSE" then
        orientation = "HORIZONTAL";
      elseif orientation == "VERTICAL_INVERSE" then
        orientation = "VERTICAL";
      end
    end
    
    -- HORIZONTAL (Grow: L -> R, Deplete: R -> L)
    if orientation == "HORIZONTAL" then
      TLx , TLy  = 0.0      , 0.0    ; TRx , TRy  = 1.0        , 0.0      ;
      BLx , BLy  = 0.0      , 1.0    ; BRx , BRy  = 1.0        , 1.0      ;
      
      TLx_, TLy_ = TLx      , TLy    ; TRx_, TRy_ = TRx*progress    , TRy      ;
      BLx_, BLy_ = BLx      , BLy    ; BRx_, BRy_ = BRx*progress    , BRy      ;
      
    -- HORIZONTAL_INVERSE (Grow: R -> L, Deplete: L -> R)
    elseif orientation == "HORIZONTAL_INVERSE" then
      TLx , TLy  = 1.0      , 0.0      ; TRx , TRy  = 0.0      , 0.0      ;
      BLx , BLy  = 1.0      , 1.0      ; BRx , BRy  = 0.0      , 1.0      ;
      
      TLx_, TLy_ = TLx*progress  , TLy      ; TRx_, TRy_ = TRx      , TRy      ;
      BLx_, BLy_ = BLx*progress  , BLy      ; BRx_, BRy_ = BRx      , BRy      ;
      
    -- VERTICAL (Grow: T -> B, Deplete: B -> T)
    elseif orientation == "VERTICAL" then
      TLx , TLy  = 0.0      , 1.0      ; TRx , TRy  = 0.0      , 0.0      ;
      BLx , BLy  = 1.0      , 1.0      ; BRx , BRy  = 1.0      , 0.0      ;
      
      TLx_, TLy_ = TLx      , TLy      ; TRx_, TRy_ = TRx      , TRy      ;
      BLx_, BLy_ = BLx*progress  , BLy      ; BRx_, BRy_ = BRx*progress  , BRy      ;

    -- VERTICAL_INVERSE (Grow: B -> T, Deplete: T -> B)
    elseif orientation == "VERTICAL_INVERSE" then
      TLx , TLy  = 1.0      , 0.0      ; TRx , TRy  = 1.0      , 1.0      ;
      BLx , BLy  = 0.0      , 0.0      ; BRx , BRy  = 0.0      , 1.0      ;
      
      TLx_, TLy_ = TLx*progress  , TLy      ; TRx_, TRy_ = TRx*progress  , TRy      ;
      BLx_, BLy_ = BLx      , BLy      ; BRx_, BRy_ = BRx      , BRy      ;
      
    end
    -- HORIZONTAL (Grow: L -> R, Deplete: R -> L)
    if self.orientation == "HORIZONTAL" then
      align1, align2   = "TOPLEFT", "BOTTOMLEFT";
      xProgress    = self:GetWidth() * progress;
      
    -- HORIZONTAL_INVERSE (Grow: R -> L, Deplete: L -> R)
    elseif self.orientation == "HORIZONTAL_INVERSE" then
      align1, align2   = "TOPRIGHT", "BOTTOMRIGHT";
      xProgress    = self:GetWidth() * progress;
      
    -- VERTICAL (Grow: T -> B, Deplete: B -> T)
    elseif self.orientation == "VERTICAL" then
      align1, align2   = "TOPLEFT", "TOPRIGHT";
      yProgress    = self:GetHeight() * progress;

    -- VERTICAL_INVERSE (Grow: B -> T, Deplete: T -> B)
    elseif self.orientation == "VERTICAL_INVERSE" then
      align1, align2   = "BOTTOMLEFT", "BOTTOMRIGHT";
      yProgress    = self:GetHeight() * progress;
      
    end
    -- Only width/height of parent changed
     if not OnSizeChanged then
      -- Stretch bg accross complete frame
      self.bg:ClearAllPoints();
      self.bg:SetAllPoints();
      self.bg:SetTexCoord(TLx , TLy , BLx , BLy , TRx , TRy , BRx , BRy );
      
      -- Set alignment
      self.fg:ClearAllPoints();
      self.fg:SetPoint(align1);
      self.fg:SetPoint(align2);
      
      -- Stretch texture
      self.fg:SetTexCoord(TLx_, TLy_, BLx_, BLy_, TRx_, TRy_, BRx_, BRy_);
     end
    
    -- Create statusbar illusion
    if xProgress then
      self.fg:SetWidth(xProgress > 0 and xProgress or 0.1);
    end
    if yProgress then
      self.fg:SetHeight(yProgress > 0 and yProgress or 0.1);
    end
  end,
  
  -- Need to update progress!
  ["OnSizeChanged"] = function(self, width, height)
    self:Update(true);
  end,
  
  -- Blizzard like SetMinMaxValues
  ["SetMinMaxValues"] = function(self, minVal, maxVal)
    local update = false;
    if minVal and type(minVal) == "number" then
      self.min   = minVal;
      update    = true;
    end
    if maxVal and type(maxVal) == "number" then
      self.max   = maxVal;
      update    = true;
    end
    
    if update then
      self:Update();
    end
  end,
  ["GetMinMaxValues"] = function(self)
    return self.min, self.max
  end,
  
  -- Blizzard like SetValue
  ["SetValue"] = function(self, value)
    if value and type(value) == "number" then
      self.value = value;
      
      self:Update();
    end
  end,
  ["GetValue"] = function(self)
    return self.value;
  end,
  
  -- Blizzard like SetOrientation (added: HORIZONTAL_INVERSE, VERTICAL_INVERSE)
  ["SetOrientation"] = function(self, orientation)
    if orientation == "HORIZONTAL"
    or orientation == "HORIZONTAL_INVERSE"
    or orientation == "VERTICAL"
    or orientation == "VERTICAL_INVERSE"
    then
      self.orientation = orientation;
    
      self:Update();
    end
  end,
  ["GetOrientation"] = function(self)
    return self.orientation;
  end,
  
  -- Blizzard like SetRotatesTexture (added: flip texture for right->left, bottom->top)
  ["SetRotatesTexture"] = function(self, rotate)
    if rotate and type(rotate) == "boolean" then
      self.rotate = rotate;
    
      self:Update();
    end
  end,
  ["GetRotatesTexture"] = function(self)
    return self.rotate;
  end,
  
  -- Blizzard like SetStatusBarTexture
  ["SetStatusBarTexture"] = function(self, texture)
    self.fg:SetTexture(texture);
    self.bg:SetTexture(texture);
  end,
  ["GetStatusBarTexture"] = function(self)
    return self.fg:GetTexture();
  end,
  
  -- Set bar color
  ["SetForegroundColor"] = function(self, r, g, b, a)
    self.fg:SetVertexColor(r, g, b, a);
  end,
  ["GetForegroundColor"] = function(self)
    return self.fg:GetVertexColor();
  end,
  
  -- Set background color
  ["SetBackgroundColor"] = function(self, r, g, b, a)
    self.bg:SetVertexColor(r, g, b, a);
  end,
  ["GetBackgroundColor"] = function(self)
    return self.bg:GetVertexColor();
  end,
  
  -- Convenience methods
  ["SetTexture"] = function(self, texture)
    self:SetStatusBarTexture(texture);
  end,
  ["GetTexture"] = function(self)
    return self:GetStatusBarTexture();
  end,
  ["SetVertexColor"] = function(self, r, g, b, a)
    self:SetForegroundColor(r, g, b, a);
  end,
  ["GetVertexColor"] = function(self)
    return self.fg:GetVertexColor();
  end,
  
  -- Internal variables
  ["min"]     = 0,
  ["max"]     = 1,
  ["value"]     = 0.5,
  ["rotate"]     = true,
  ["orientation"]  = "HORIZONTAL",
}

-- Called when first creating a new region/display
local function create(parent)
  -- Create overall region (containing everything else)
  local region = CreateFrame("FRAME", nil, parent);
  region:SetMovable(true);
  region:SetResizable(true);
  region:SetMinResize(1, 1);
    
  -- Create statusbar (inherit prototype)
  local bar = CreateFrame("FRAME", nil, region);
  local fg = bar:CreateTexture(nil, "ARTWORK");
  local bg = bar:CreateTexture(nil, "BACKGROUND");
  bar.fg = fg;
  bar.bg = bg;
  for key, value in pairs(barPrototype) do
    bar[key] = value;
  end
  bar:SetRotatesTexture(true);
  bar:HookScript("OnSizeChanged", bar.OnSizeChanged);
  region.bar = bar;
    
-- Create border
  local border = CreateFrame("frame", nil, region);
  region.border = border;
  
-- Create timer text
  local timer = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight");
  region.timer = timer;
  timer:SetText("0.0");
  timer:SetNonSpaceWrap(true);
  timer:SetPoint("center");
  
-- Create (name) text
  local text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight");
  region.text = text;
  text:SetText("Error");
  text:SetNonSpaceWrap(true);
  text:SetPoint("center");
    
  -- Create icon
  local iconFrame = CreateFrame("FRAME", nil, region);
  region.iconFrame = iconFrame;
  local icon = iconFrame:CreateTexture(nil, "OVERLAY");
  region.icon = icon;
  icon:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark");
  
-- Create stack text
  local stacks = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight");
  region.stacks = stacks;
  stacks:SetText(1);
  stacks:ClearAllPoints();
  stacks:SetPoint("CENTER", icon, "CENTER");
  
-- Region variables
  region.values = {};
  region.duration = 0;
  region.expirationTime = math.huge;
  
-- Return new display/region
  return region;
end

-- Rotate object around its origin
local function animRotate(object, degrees)
  -- Something to rotate
    if object.animationGroup or degrees ~= 0 then
    -- Create AnimatioGroup and rotation animation
        object.animationGroup = object.animationGroup or object:CreateAnimationGroup();
        local group = object.animationGroup;
        group.rotate = group.rotate or group:CreateAnimation("rotation");
        local rotate = group.rotate;
        
    -- Reset animation
        group:Stop();
        rotate:Stop();
        
    -- Rotate around origin
        if degrees ~= 0 then
            rotate:SetOrigin("CENTER", 0, 0);
            rotate:SetDegrees(degrees);
            rotate:SetDuration(0);
            rotate:SetEndDelay(0.1);
            rotate:SetScript("OnUpdate", rotate.Pause);
            rotate:Play();
            group:Play();
        end
    end
end
WeakAuras.animRotate = animRotate;

-- Calculate offset after rotation
local function getRotateOffset(object, degrees, point)
  -- Any rotation at all?
    if degrees ~= 0 then
        -- Basic offset
    local xo, yo;
        local originoffset = object:GetStringHeight() / 2;
        xo = -1 * originoffset * sin(degrees);
        yo = originoffset * (cos(degrees) - 1);
    
    -- Alignment dependant offset
        if point == "BOTTOM" then
            yo = yo + (1 - cos(degrees)) * (object:GetStringWidth() / 2 - originoffset);
        elseif point == "TOP" then
            yo = yo - (1 - cos(degrees)) * (object:GetStringWidth() / 2 - originoffset);
        elseif point == "RIGHT" then
            xo = xo + (1 - cos(degrees)) * (object:GetStringWidth() / 2 - originoffset);
        elseif point == "LEFT" then
            xo = xo - (1 - cos(degrees)) * (object:GetStringWidth() / 2 - originoffset);
        end
        
    -- Done
        return xo, yo;
    
  -- No rotation
    else
        return 0, 0;
    end
end

-- Orientation helper methods
local function orientHorizontalInverse(region, data)
  -- Localize
  local bar, timer, text, icon = region.bar, region.timer, region.text, region.icon;
  local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Reset
  icon:ClearAllPoints();
  bar:ClearAllPoints();
  
  -- Align icon and bar
  if data.icon then
    if data.icon_side == "LEFT" then
      icon:SetPoint("LEFT", region, "LEFT");
      bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
      bar:SetPoint("TOPLEFT", icon, "TOPRIGHT");
    else
      icon:SetPoint("RIGHT", region, "RIGHT");
      bar:SetPoint("BOTTOMLEFT", region, "BOTTOMLEFT");
      bar:SetPoint("TOPRIGHT", icon, "TOPLEFT");
    end
  else
    bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
    bar:SetPoint("TOPLEFT", region, "TOPLEFT");
  end
  
    -- Save orientation
  region.orientation = "HORIZONTAL_INVERSE";
  bar:SetOrientation(region.orientation);
  
  -- Temp variable
  local xo, yo;
  
  -- Align timer text
  xo, yo = getRotateOffset(timer, textDegrees, "LEFT");
  timer:ClearAllPoints();
  timer:SetPoint("LEFT", bar, "LEFT", 2 + xo, 0 + yo);
  
  -- Align name text
  xo, yo = getRotateOffset(text, textDegrees, "RIGHT");
  text:ClearAllPoints();
  text:SetPoint("RIGHT", bar, "RIGHT", -2 + xo, 0 + yo);
  
  -- Text internal alignment
  if textDegrees == 0 then
    text:SetWidth(bar:GetWidth() - (timer:GetWidth() + (data.textSize/2)));
    text:SetJustifyH("RIGHT");
  else
    text:SetWidth(0);
    text:SetJustifyH("CENTER");
  end
end
local function orientHorizontal(region, data)
  -- Localize
  local bar, timer, text, icon = region.bar, region.timer, region.text, region.icon;
  local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Reset
  icon:ClearAllPoints();
  bar:ClearAllPoints();
  
  -- Align icon and bar
  if data.icon then
    if data.icon_side == "LEFT" then
      icon:SetPoint("LEFT", region, "LEFT");
      bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
      bar:SetPoint("TOPLEFT", icon, "TOPRIGHT");
    else
      icon:SetPoint("RIGHT", region, "RIGHT");
      bar:SetPoint("BOTTOMLEFT", region, "BOTTOMLEFT");
      bar:SetPoint("TOPRIGHT", icon, "TOPLEFT");
    end
  else
    bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
    bar:SetPoint("TOPLEFT", region, "TOPLEFT");
  end
 
    -- Save orientation
  region.orientation = "HORIZONTAL";
  bar:SetOrientation(region.orientation);
 
  -- Temp variable
  local xo, yo;
  
  -- Align timer text
  xo, yo = getRotateOffset(timer, textDegrees, "RIGHT");
  timer:ClearAllPoints();
  timer:SetPoint("RIGHT", bar, "RIGHT", -2 + xo, 0 + yo);
  
  -- Align name text
  xo, yo = getRotateOffset(text, textDegrees, "LEFT");
  text:ClearAllPoints();
  text:SetPoint("LEFT", bar, "LEFT", 2 + xo, 0 + yo);
  
  -- Text internal alignment
  if textDegrees == 0 then
    text:SetWidth(bar:GetWidth() - (timer:GetWidth() + (data.textSize/2)));
    text:SetJustifyH("LEFT");
  else
    text:SetWidth(0);
    text:SetJustifyH("CENTER");
  end
end
local function orientVerticalInverse(region, data)
  -- Localize
  local bar, timer, text, icon = region.bar, region.timer, region.text, region.icon;
  local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Reset
  icon:ClearAllPoints();
  bar:ClearAllPoints();
  
  -- Align icon and bar
  if data.icon then
    if data.icon_side == "LEFT" then
      icon:SetPoint("TOP", region, "TOP");
      bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
      bar:SetPoint("TOPLEFT", icon, "BOTTOMLEFT");
    else
      icon:SetPoint("BOTTOM", region, "BOTTOM");
      bar:SetPoint("TOPRIGHT", region, "TOPRIGHT");
      bar:SetPoint("BOTTOMLEFT", icon, "TOPLEFT");
    end
  else
    bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
    bar:SetPoint("TOPLEFT", region, "TOPLEFT");
  end
  
    -- Save orientation
  region.orientation = "VERTICAL_INVERSE";
  bar:SetOrientation("VERTICAL_INVERSE");
  
  -- Temp variable
  local xo, yo;
  
  -- Align timer text
  xo, yo = getRotateOffset(timer, textDegrees, "BOTTOM");
  timer:ClearAllPoints();
  timer:SetPoint("BOTTOM", bar, "BOTTOM", 0 + xo, 2 + yo);
  
  -- Align name text
  xo, yo = getRotateOffset(text, textDegrees, "TOP");
  text:ClearAllPoints();
  text:SetPoint("TOP", bar, "TOP", 0 + xo, -2 + yo);
  
  -- Text internal alignment
  text:SetWidth(0);
  text:SetJustifyH("CENTER");
end
local function orientVertical(region, data)
  -- Localize
  local bar, timer, text, icon = region.bar, region.timer, region.text, region.icon;
  local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Reset
  icon:ClearAllPoints();
  bar:ClearAllPoints();
  
  -- Align icon and bar
  if data.icon then
    if data.icon_side == "LEFT" then
      icon:SetPoint("TOP", region, "TOP");
      bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
      bar:SetPoint("TOPLEFT", icon, "BOTTOMLEFT");
    else
      icon:SetPoint("BOTTOM", region, "BOTTOM");
      bar:SetPoint("TOPRIGHT", region, "TOPRIGHT");
      bar:SetPoint("BOTTOMLEFT", icon, "TOPLEFT");
    end
  else
    bar:SetPoint("BOTTOMRIGHT", region, "BOTTOMRIGHT");
    bar:SetPoint("TOPLEFT", region, "TOPLEFT");
  end
  
  -- Save orientation
  region.orientation = "VERTICAL";
  bar:SetOrientation("VERTICAL");
  
  -- Temp variable
  local xo, yo;
  
  -- Align timer text
  xo, yo = getRotateOffset(timer, textDegrees, "TOP");
  timer:ClearAllPoints();
  timer:SetPoint("TOP", bar, "TOP", 0 + xo, -2 + yo);
  
  -- Align name text
  xo, yo = getRotateOffset(text, textDegrees, "BOTTOM");
  text:ClearAllPoints();
  text:SetPoint("BOTTOM", bar, "BOTTOM", 0 + xo, 2 + yo);
  
  -- Text internal alignment
  text:SetWidth(0);
  text:SetJustifyH("CENTER");
end
local function orient(region, data)
  -- Apply correct orientation
  if data.orientation == "HORIZONTAL_INVERSE" then
    orientHorizontalInverse(region, data);
  elseif data.orientation == "HORIZONTAL" then
    orientHorizontal(region, data);
  elseif data.orientation == "VERTICAL_INVERSE" then
    orientVerticalInverse(region, data);
  elseif data.orientation == "VERTICAL" then
    orientVertical(region, data);
  end
end

-- Update custom text
local function UpdateText(region, data)
  -- Localize
  local text, timer = region.text, region.timer;
  local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Needs re-orientation?
  local shouldOrient = false;
  local textStr
  
  -- Replace %-marks
  textStr = data.displayTextLeft or "";
  for symbol, v in pairs(WeakAuras.dynamic_texts) do
    textStr = textStr:gsub(symbol, region.values[v.value] or "");
  end
  
  -- Update left text
  if not text.displayTextLeft or #text.displayTextLeft ~= #textStr then
    shouldOrient = true;
  end
  if text.displayTextLeft ~= textStr then
    text:SetText(textStr);
    text.displayTextLeft = textStr;
  end
  
  -- Replace %-marks
  textStr = data.displayTextRight or "";
  for symbol, v in pairs(WeakAuras.dynamic_texts) do
    textStr = textStr:gsub(symbol, region.values[v.value] or "");
  end
  
  -- Update right text
  if not timer.displayTextRight or #timer.displayTextRight ~= #textStr then
    shouldOrient = true;
  end
  if timer.displayTextLeft ~= textStr then
    timer:SetText(textStr);
    timer.displayTextLeft = textStr;
  end
  
  -- Re-orientate
  if shouldOrient then
    orient(region, data);
  end
end

-- Update time (status-bar and text)
local function UpdateTime(region, data, inverse)
  -- Timing variables
  local remaining  = region.expirationTime - GetTime();
  local duration  = region.duration;
  local progress  = duration ~= 0 and remaining / duration or 1;
  
  -- Need to invert?
  if (
      (data.inverse and not inverse) 
      or (inverse and not data.inverse)
    )
  then
    progress = 1 - progress;
  end
  region.bar:SetValue(progress);
  
  -- Format a remaining time string
  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);
  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);
  else
    durationStr     = " ";
  end
  region.values.duration   = durationStr;
  
  -- Update text
  UpdateText(region, data);
end
local function UpdateTimeInverse(region, data)
  -- Relay
  UpdateTime(region, data, true);
end

-- Update current state (progress)
local function UpdateValue(region, data, value, total)
  -- Calc progress (percent)
  local progress = 1
  if total > 0 then
    progress = value / total;
  else
        progress = 0;
    end
  if data.inverse then
    progress = 1 - progress;
  end

  -- Save values
  region.values.progress = value;
  region.values.duration = total;
  region.bar:SetValue(progress);
  
  -- Update text
  UpdateText(region, data);
end

-- Modify a given region/display
local function modify(parent, region, data)
  -- Localize
  local bar, border, timer, text, iconFrame, icon, stacks = region.bar, region.border, region.timer, region.text, region.iconFrame, region.icon, region.stacks;
    
  -- Adjust framestrata
    if data.frameStrata == 1 then
        region:SetFrameStrata(region:GetParent():GetFrameStrata());
    else
        region:SetFrameStrata(WeakAuras.frame_strata_types[data.frameStrata]);
    end
    
  -- Adjust region size
    region:SetWidth(data.width);
    region:SetHeight(data.height);
    
  -- Reset anchors
    region:ClearAllPoints();
    region:SetPoint(data.selfPoint, parent, data.anchorPoint, data.xOffset, data.yOffset);
  
  -- Set overall alpha
    region:SetAlpha(data.alpha);

    -- Update border
  if data.border then
    border:SetBackdrop({
      edgeFile = SharedMedia:Fetch("border", data.borderEdge) or "",
      edgeSize = data.borderSize,
      bgFile = SharedMedia:Fetch("background", data.borderBackdrop) or "",
      insets = {
        left   = data.borderInset,
        right   = data.borderInset,
        top   = data.borderInset,
        bottom   = data.borderInset,
      },
    });
    border:SetPoint("bottomleft", region, "bottomleft", -data.borderOffset, -data.borderOffset);
    border:SetPoint("topright",   region, "topright",    data.borderOffset,  data.borderOffset);
    border:SetBackdropBorderColor(data.borderColor[1], data.borderColor[2], data.borderColor[3], data.borderColor[4]);
    border:SetBackdropColor(data.backdropColor[1], data.backdropColor[2], data.backdropColor[3], data.backdropColor[4]);
    border:Show();
    else
    border:Hide();
  end
  
  -- Update texture settings
  local texturePath = SharedMedia:Fetch("statusbar", data.texture) or "";
  bar:SetStatusBarTexture(texturePath);
  bar:SetBackgroundColor(data.backgroundColor[1], data.backgroundColor[2], data.backgroundColor[3], data.backgroundColor[4]);
  
  -- Bar or Border (+Backdrop) in front
  if data.barInFront then
    iconFrame:SetFrameLevel(5);
    bar:SetFrameLevel(5);
    border:SetFrameLevel(2);
  else
    iconFrame:SetFrameLevel(2);
    bar:SetFrameLevel(2);
    border:SetFrameLevel(5);
  end
    
  -- Color update function
    region.Color = region.Color or function(self, r, g, b, a)
        self.color_r = r;
        self.color_g = g;
        self.color_b = b;
        self.color_a = a;
    self.bar:SetForegroundColor(r, g, b, a);
    end
  region.GetColor = region.GetColor or function(self)
        return   self.color_r, 
        self.color_g,
        self.color_b, 
        self.color_a;
    end
  region:Color(data.barColor[1], data.barColor[2], data.barColor[3], data.barColor[4]);
  
  -- Rotate text
    local textDegrees = data.rotateText == "LEFT" and 90 or data.rotateText == "RIGHT" and -90 or 0;
  
  -- Update timer visibility
    if data.timer then
    -- Update text font
    text:SetFont(SharedMedia:Fetch("font", data.textFont), data.textSize, data.textFlags and data.textFlags ~= "None" and data.textFlags);
    text:SetTextColor(data.textColor[1], data.textColor[2], data.textColor[3], data.textColor[4]);
    text:SetWordWrap(false);
    animRotate(text, textDegrees);
    
        timer:Show();
    else
        timer:Hide();
    end
      
  -- Update text visibility
    if data.text then
    -- Update timer font
    timer:SetFont(SharedMedia:Fetch("font", data.timerFont), data.timerSize, data.timerFlags and data.timerFlags ~= "None" and data.timerFlags);
    timer:SetTextColor(data.timerColor[1], data.timerColor[2], data.timerColor[3], data.timerColor[4]);
    animRotate(timer, textDegrees);
    
        text:Show();
    else
        text:Hide();
    end

  -- Update icon visibility
    if data.icon then
    -- Update icon
    local iconsize = math.min(data.height, data.width);
    icon:SetWidth(iconsize);
    icon:SetHeight(iconsize);

    -- Icon update function
        function region:SetIcon(path)
      -- Set icon options
            local iconPath = (
                WeakAuras.CanHaveAuto(data)
                and data.auto
                and path ~= ""
                and path
                or data.displayIcon
                or "Interface\\Icons\\INV_Misc_QuestionMark"
            );
            self.icon:SetTexture(iconPath);
            self.icon:SetDesaturated(data.desaturate)
            region.values.icon = "|T"..iconPath..":12:12:0:0:64:64:4:60:4:60|t";
      
      -- Update text
            UpdateText(self, data);
        end
--    region:SetIcon("");

    -- Update icon visibility
        icon:Show();
    
    -- Update stack text visibility
    if data.icon and data.stacks then
      -- Update stack font
      stacks:SetFont(SharedMedia:Fetch("font", data.stacksFont), data.stacksSize, data.stacksFlags and data.stacksFlags ~= "None" and data.stacksFlags);
      stacks:SetTextColor(data.stacksColor[1], data.stacksColor[2], data.stacksColor[3], data.stacksColor[4]);
      animRotate(stacks, textDegrees);
      
      -- Align text after rotation
      local xo, yo;
      xo, yo = getRotateOffset(stacks, textDegrees, "CENTER");
      stacks:SetPoint("CENTER", icon, "CENTER", xo, yo);
    
      stacks:Show();
    else
      stacks:Hide();
    end
  --
    else
        stacks:Hide();
        icon:Hide();
    end
    
  -- Apply orientation alignment
    orient(region, data);
    
  -- Update tooltip availability
    local tooltipType = WeakAuras.CanHaveTooltip(data);
    if tooltipType and data.useTooltip then
    -- Create and enable tooltip-hover frame
        region.tooltipFrame = region.tooltipFrame or CreateFrame("frame");
        region.tooltipFrame:SetAllPoints(icon);
        region.tooltipFrame:EnableMouse(true);
        region.tooltipFrame:SetScript("OnEnter", function()
            WeakAuras.ShowMouseoverTooltip(data, region, region.tooltipFrame, tooltipType);
        end);
        region.tooltipFrame:SetScript("OnLeave", WeakAuras.HideTooltip);
    
  -- Disable tooltip
    elseif region.tooltipFrame then
        region.tooltipFrame:EnableMouse(false);
    end
    
  -- Look for need to use custom text update
    if (data.displayTextLeft:find("%%c") or data.displayTextRight:find("%%c")) and data.customText then
    -- Load custom code function
        local customTextFunc = WeakAuras.LoadFunction("return "..data.customText)
        local values = region.values;
    
    -- Save custom text function
        region.UpdateCustomText = function()
      -- Evaluate and update text
            local custom = customTextFunc(region.expirationTime, region.duration, values.progress, values.duration, values.name, values.icon, values.stacks);
            if custom ~= values.custom then
                values.custom = custom;
                UpdateText(region, data);
            end
        end
    
    -- Add/Remove custom text update
        if data.customTextUpdate == "update" then
            WeakAuras.RegisterCustomTextUpdates(region);
        else
            WeakAuras.UnregisterCustomTextUpdates(region);
        end
  
  -- Remove custom text update
    else
        region.UpdateCustomText = nil;
        WeakAuras.UnregisterCustomTextUpdates(region);
    end
    
  -- Stack update function
    function region:SetStacks(count)
    -- Update text content
        if count and count > 0 then
            self.values.stacks = count;
            self.stacks:SetText(count);
        else
            self.values.stacks = 0;
           self.stacks:SetText("");
        end
        UpdateText(self, data);
    end
--  region:SetStacks();
    
  -- Scale update function
    function region:Scale(scalex, scaley)
    -- Icon size
    local iconsize = math.min(data.height, data.width);
    
    -- Re-orientate region
        if scalex < 0 then
            scalex = -scalex;
            if data.orientation == "HORIZONTAL" then
                if self.orientation ~= "HORIZONTAL_INVERSE" then
                    orientHorizontalInverse();
                end
            elseif data.orientation == "HORIZONTAL_INVERSE" then
                if self.orientation ~= "HORIZONTAL" then
                    orientHorizontal();
                end
            end
        else
            if data.orientation == "HORIZONTAL" then
                if self.orientation ~= "HORIZONTAL" then
                    orientHorizontal();
                end
            elseif data.orientation == "HORIZONTAL_INVERSE" then
                if self.orientation ~= "HORIZONTAL_INVERSE" then
                    orientHorizontalInverse();
                end
            end
        end
    
    -- Update width
        self:SetWidth(data.width * scalex);
        icon:SetWidth(iconsize * scalex);
    
    -- Re-orientate region
        if scaley < 0 then
            scaley = -scaley;
            if data.orientation == "VERTICAL" then
                if self.orientation ~= "VERTICAL_INVERSE" then
                    orientVerticalInverse(self, data);
                end
            elseif data.orientation == "VERTICAL_INVERSE" then
                if self.orientation ~= "VERTICAL" then
                    orientVertical(self, data);
                end
            end
        else
            if data.orientation == "VERTICAL" then
                if self.orientation ~= "VERTICAL" then
                    orientVertical(self, data);
                end
            elseif data.orientation == "VERTICAL_INVERSE" then
                if self.orientation ~= "VERTICAL_INVERSE" then
                    orientVerticalInverse(self, data);
                end
            end
        end
    
    -- Update height
        self:SetHeight(data.height * scaley);
        icon:SetHeight(iconsize * scaley);
    end
--  region:Scale(1.0, 1.0);

  -- Name update function
    function region:SetName(name)
        region.values.name = WeakAuras.CanHaveAuto(data) and name or data.id;
        UpdateText(self, data);
    end
--  region:SetName("");

  -- Duration update function
    function region:SetDurationInfo(duration, expirationTime, customValue, inverse)
    -- Update duration/expiration values
        if duration <= 0 or duration > self.duration or not data.stickyDuration then
            self.duration = duration;
        end
        self.expirationTime = expirationTime;
        
    -- Use custom OnUpdate handler
        if customValue then
      -- Update via custom OnUpdate handler
            if type(customValue) == "function" then
                local value, total = customValue(data.trigger);
                if total > 0 and value < total then
                    self.customValueFunc = customValue;
                    self:SetScript("OnUpdate", function()
            -- Relay
            local value, total = self.customValueFunc(data.trigger);
            UpdateValue(self, data, value, total);
          end);
                else
                    UpdateValue(self, data, duration, expirationTime);
                    self:SetScript("OnUpdate", nil);
                end
      -- Remove OnUpdate handler, call update once
            else
                UpdateValue(self, data, duration, expirationTime);
                self:SetScript("OnUpdate", nil);
            end
    -- Use default OnUpdate handler
        else
      -- Enable OnUpdate script
            if duration > 0 then
                if inverse then
                    self:SetScript("OnUpdate", function() UpdateTimeInverse(self, data) end);
                else
                    self:SetScript("OnUpdate", function() UpdateTime(self, data, inverse) end);
                end
      -- Reset to full
            else
                bar:SetValue(1);
                self:SetScript("OnUpdate", nil);
                UpdateTime(self, data, inverse);
            end
        end
    end
--  region:SetDurationInfo(1, 0, nil, nil);

  -- Update internal bar alignment
  region.bar:Update();
end

-- Register new region type with WeakAuras
WeakAuras.RegisterRegionType("aurabar", create, modify, default);