-------------------------------------------------------------------------------------
-- Title: MSBT Default Animations
-- Author: Mik
-------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------
-- Private constants.
-------------------------------------------------------------------------------------

-- How long to show stickies before they are faded.
local STICKY_FADE_IN_TIME = 0.15;
local STICKY_DISPLAY_TIME = 1.5;
local STICKY_FADE_OUT_TIME = 0.5;

-- How long to wait between jiggles.
local JIGGLE_DELAY_TIME = 0.05;


-- Default movement speed. (260 pixels every 3 seconds)
local MOVEMENT_SPEED = (3 / 260);

-- Default time to move 1 pixel. (260 pixels every 3 seconds)
local PER_PIXEL_TIME = (3 / 260);

-- Minimum amount of space allowed between two strings.
local MIN_VERTICAL_SPACING = 8;


-------------------------------------------------------------------------------------
-- Pow Sticky functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Initialize the passed display event and reposition the ones that are currently
-- animating in the scroll area to prevent overlaps.
-- **********************************************************************************
local function InitPow(newDisplayEvent, activeDisplayEvents, isReversed)
 -- Set the new event to the center of the scroll area.
 newDisplayEvent.PositionY = ceil(newDisplayEvent.ScrollHeight / 2);

 -- Save the original x and y positions for calculating the jiggle effect.
 newDisplayEvent.OriginalPositionX = newDisplayEvent.PositionX;
 newDisplayEvent.OriginalPositionY = newDisplayEvent.PositionY;
 newDisplayEvent.TimeLastJiggled = 0;

 -- Set the initial alpha of the new display event to 1.
 newDisplayEvent.Alpha = 1;

 -- Get the number of sticky display events that are currently animating.
 local numActiveAnimations = #activeDisplayEvents;

 -- Check if the text is scrolling down.
 if (isReversed) then

  -- Check if there are other stickies animating.
  if (numActiveAnimations > 0) then
   -- Get the middle sticky.
   local middleSticky = floor((numActiveAnimations + 2) / 2);

   -- Set the middle sticky to the center of the scroll area.
   activeDisplayEvents[middleSticky].OriginalPositionY = newDisplayEvent.ScrollHeight / 2;

   -- Loop backwards from the middle sticky and move the animating display events so they don't collide.
   for x = middleSticky - 1, 1, -1 do
    activeDisplayEvents[x].OriginalPositionY = activeDisplayEvents[x+1].OriginalPositionY - activeDisplayEvents[x].FontSize - MIN_VERTICAL_SPACING;
    activeDisplayEvents[x].PositionY = activeDisplayEvents[x].OriginalPositionY
   end

   -- Loop forwards from the middle sticky and move the animating display events so they don't collide.
   for x = middleSticky + 1, numActiveAnimations do
    activeDisplayEvents[x].OriginalPositionY = activeDisplayEvents[x-1].OriginalPositionY + activeDisplayEvents[x-1].FontSize + MIN_VERTICAL_SPACING;
    activeDisplayEvents[x].PositionY = activeDisplayEvents[x].OriginalPositionY
   end

   -- Move the new display event so it doesn't collide.
   newDisplayEvent.OriginalPositionY = activeDisplayEvents[numActiveAnimations].OriginalPositionY + activeDisplayEvents[numActiveAnimations].FontSize + MIN_VERTICAL_SPACING;
   newDisplayEvent.PositionY = newDisplayEvent.OriginalPositionY;
  end

 -- Text is scrolling up.
 else

  -- Check if there are other stickies animating.
  if (numActiveAnimations > 0) then
   -- Get the middle sticky.
   local middleSticky = ceil(numActiveAnimations / 2);

   -- Set the middle sticky to the center of the scroll area.
   activeDisplayEvents[middleSticky].OriginalPositionY = newDisplayEvent.ScrollHeight / 2;

   -- Loop backwards from the middle sticky and move the animating display events so they don't collide.
   for x = middleSticky - 1, 1, -1 do
    activeDisplayEvents[x].OriginalPositionY = activeDisplayEvents[x+1].OriginalPositionY + activeDisplayEvents[x+1].FontSize + MIN_VERTICAL_SPACING;
    activeDisplayEvents[x].PositionY = activeDisplayEvents[x].OriginalPositionY
   end

   -- Loop forwards from the middle sticky and move the animating display events so they don't collide.
   for x = middleSticky + 1, numActiveAnimations do
    activeDisplayEvents[x].OriginalPositionY = activeDisplayEvents[x-1].OriginalPositionY - activeDisplayEvents[x].FontSize - MIN_VERTICAL_SPACING;
    activeDisplayEvents[x].PositionY = activeDisplayEvents[x].OriginalPositionY
   end

   -- Move the new display event so it doesn't collide.
   newDisplayEvent.OriginalPositionY = activeDisplayEvents[numActiveAnimations].OriginalPositionY - activeDisplayEvents[numActiveAnimations].FontSize - MIN_VERTICAL_SPACING;
   newDisplayEvent.PositionY = newDisplayEvent.OriginalPositionY;
  end
 end
end


-- **********************************************************************************
-- Animates the passed display event using using the pow style.
-- **********************************************************************************
local function AnimatePowJiggle(displayEvent)
 -- Scale the display and fade out times based on the animation speed.
 local displayTime = STICKY_DISPLAY_TIME * (1/displayEvent.AnimationSpeed);
 local fadeOutTime = STICKY_FADE_OUT_TIME * (1/displayEvent.AnimationSpeed);

 -- Scale the text height.
 if (displayEvent.ElapsedTime <= STICKY_FADE_IN_TIME) then
  local textDelta = displayEvent.FontSize * (1 - (displayEvent.ElapsedTime / STICKY_FADE_IN_TIME));
  displayEvent.FontString:SetTextHeight(displayEvent.FontSize + textDelta);
  return false;

 -- Jiggle the text around.
 elseif (displayEvent.ElapsedTime <= (STICKY_FADE_IN_TIME + displayTime)) then
  if (displayEvent.ElapsedTime - displayEvent.TimeLastJiggled > JIGGLE_DELAY_TIME) then
   displayEvent.PositionX = displayEvent.OriginalPositionX + ceil(random(-1, 1));
   displayEvent.PositionY = displayEvent.OriginalPositionY + ceil(random(-1, 1));
   displayEvent.TimeLastJiggled = displayEvent.ElapsedTime;
  end

  displayEvent.FontString:SetTextHeight(displayEvent.FontSize);
  return false;

 -- Fade out.
 elseif (displayEvent.ElapsedTime <= (STICKY_FADE_IN_TIME + displayTime + fadeOutTime)) then
  displayEvent.Alpha = 1 - ((displayEvent.ElapsedTime - STICKY_FADE_IN_TIME - displayTime) / fadeOutTime);
  return false;
 end

 -- Return true to indicate the animation is complete.
 return true;
end


-------------------------------------------------------------------------------------
-- Straight scroll functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Move previous display event if the passed current display event collides with it.
-- **********************************************************************************
local function RepositionStraightUp(currentDisplayEvent, prevDisplayEvent)
 -- Do nothing if the passed previous display event doesn't exist.
 if (not prevDisplayEvent) then return; end

 -- Scale the per pixel time based on the animation speed.
 local perPixelTime = PER_PIXEL_TIME * (1/currentDisplayEvent.AnimationSpeed);

 -- Calculate the elapsed time for the top point of the current display event.
 local topTimeCurrent = currentDisplayEvent.ElapsedTime + (currentDisplayEvent.FontSize + MIN_VERTICAL_SPACING) * perPixelTime;

 -- Adjust the elapsed time of the previous display event if the current one is colliding with it.
 if (prevDisplayEvent.ElapsedTime < topTimeCurrent) then
  prevDisplayEvent.ElapsedTime = topTimeCurrent;
 end
end


-- **********************************************************************************
-- Move previous display event if the passed current display event collides with it.
-- **********************************************************************************
local function RepositionStraightDown(currentDisplayEvent, prevDisplayEvent)
 -- Do nothing if the passed previous display event doesn't exist.
 if (not prevDisplayEvent) then return; end

 -- Scale the per pixel time based on the animation speed.
 local perPixelTime = PER_PIXEL_TIME * (1/currentDisplayEvent.AnimationSpeed);

 -- Calculate the elapsed time for the top point of the previous display event.
 local topTimePrev = prevDisplayEvent.ElapsedTime - (prevDisplayEvent.FontSize + MIN_VERTICAL_SPACING) * perPixelTime;

 -- Move the previous display event is the current one is colliding with it.
 if (topTimePrev < currentDisplayEvent.ElapsedTime) then
  prevDisplayEvent.ElapsedTime = currentDisplayEvent.ElapsedTime + (prevDisplayEvent.FontSize + MIN_VERTICAL_SPACING) * perPixelTime;
 end
end


-- **********************************************************************************
-- Initialize the passed display event and reposition the ones that are currently
-- scrolling in the scroll area to prevent overlaps.
-- **********************************************************************************
local function InitStraight(newDisplayEvent, activeDisplayEvents, isReversed)
 -- Function to call for repositioning colliding events.
 local repositionFunc;

 -- Get the number of display events that are currently scrolling for this style.
 local numActiveAnimations = #activeDisplayEvents;

 -- Set the initial alpha of the new display event to 1.
 newDisplayEvent.Alpha = 1;


 -- Check if the text is scrolling down.
 if (isReversed) then
  repositionFunc = RepositionStraightDown;

 -- Text is scrolling up.
 else
  repositionFunc = RepositionStraightUp;
 end -- Scroll direction.


 -- Move events that are colliding.
 repositionFunc(newDisplayEvent, activeDisplayEvents[numActiveAnimations]);
 for x = numActiveAnimations, 2, -1 do
  repositionFunc(activeDisplayEvents[x], activeDisplayEvents[x-1]);
 end
end


-- **********************************************************************************
-- Scrolls the passed display event upwards.
-- **********************************************************************************
local function ScrollUp(displayEvent)
 -- Calculate how long the animation should take based on the height of the scroll area it's in
 -- and scaled by the animation speed.
 local scaledScrollTime = displayEvent.ScrollHeight * PER_PIXEL_TIME * (1/displayEvent.AnimationSpeed);

 -- Calculate the fade effect start time to be halfway through the animation.
 local fadeStartTime =  scaledScrollTime / 2;

 -- Set the y position based on the elapsed time.
 displayEvent.PositionY = floor(displayEvent.ScrollHeight * (displayEvent.ElapsedTime / scaledScrollTime));

 -- Fade the text out once enough time has passed.
 if (displayEvent.ElapsedTime >= fadeStartTime) then
  displayEvent.Alpha = 1 - ((displayEvent.ElapsedTime - fadeStartTime) / (scaledScrollTime - fadeStartTime));
  displayEvent.Alpha = math.max(displayEvent.Alpha, 0);
 end

 -- Return true to indicate that the animation is complete when enough time has passed.
 if (displayEvent.ElapsedTime >= scaledScrollTime) then
  return true;
 end

 -- Return false to indicate the animation is NOT complete.
 return false;
end


-- **********************************************************************************
-- Scrolls the passed display event downwards.
-- **********************************************************************************
local function ScrollDown(displayEvent)
 -- Calculate how long the animation should take based on the height of the scroll area it's in
 -- and scaled by the animation speed.
 local scaledScrollTime = displayEvent.ScrollHeight * PER_PIXEL_TIME * (1/displayEvent.AnimationSpeed);

 -- Calculate the fade effect start time to be halfway through the animation.
 local fadeStartTime =  scaledScrollTime / 2;

 -- Set the y position based on the elapsed time.
 displayEvent.PositionY = displayEvent.ScrollHeight - floor(displayEvent.ScrollHeight * (displayEvent.ElapsedTime / scaledScrollTime));

 -- Fade the text out once enough time has passed.
 if (displayEvent.ElapsedTime >= fadeStartTime) then
  displayEvent.Alpha = 1 - ((displayEvent.ElapsedTime - fadeStartTime) / (scaledScrollTime - fadeStartTime));
  displayEvent.Alpha = math.max(displayEvent.Alpha, 0);
 end

 -- Return true to indicate that the animation is complete when enough time has passed.
 if (displayEvent.ElapsedTime >= scaledScrollTime) then
  return true;
 end

 -- Return false to indicate the animation is NOT complete.
 return false;
end


-------------------------------------------------------------------------------------
-- Parabola scroll functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Initialize the starting position of the passed display event and reposition
-- the ones that are currently scrolling in the scroll area to prevent overlaps.
-- **********************************************************************************
local function InitLeftParabola(newDisplayEvent, activeDisplayEvents, isReversed)
 -- Just use the same initialization logic as straight scrolling since the parabola
 -- effect is created by calculating the x position based on the y position. 
 InitStraight(newDisplayEvent, activeDisplayEvents, isReversed);
end


-- **********************************************************************************
-- Scrolls the passed display event in a left parabola.
-- **********************************************************************************
local function ScrollLeftParabola(displayEvent)
 -- Calculate the animation midpoint.
 local animationMidPoint = displayEvent.ScrollHeight / 2;

 -- Calculate the new x position based on equation of a parabola.
 -- Equation of a parabola at vertex 0,0: x = y^2 / 4a
 local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint));
 local parabolaY = displayEvent.PositionY - animationMidPoint;
 local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint));

 -- Set the new x position.
 displayEvent.PositionX = newX * -1;
end

-- **********************************************************************************
-- Scrolls the passed display event in a left parabola upwards.
-- **********************************************************************************
local function ScrollLeftParabolaUp(displayEvent)
 -- Leverage the scroll up logic.
 local animationComplete = ScrollUp(displayEvent);

 -- Scroll the text in a left parabola.
 ScrollLeftParabola(displayEvent);

 -- Return whether or not the animation is complete.
 return animationComplete;
end


-- **********************************************************************************
-- Scrolls the passed display event in a left parabola downwards.
-- **********************************************************************************
local function ScrollLeftParabolaDown(displayEvent)
 -- Leverage the scroll up logic.
 local animationComplete = ScrollDown(displayEvent);

 -- Scroll the text in a left parabola.
 ScrollLeftParabola(displayEvent);

 -- Return whether or not the animation is complete.
 return animationComplete;
end


-- **********************************************************************************
-- Initialize the starting position of the passed display event and reposition
-- the ones that are currently scrolling in the scroll area to prevent overlaps.
-- **********************************************************************************
local function InitRightParabola(newDisplayEvent, activeDisplayEvents, isReversed)
 -- Just use the same initialization logic as straight scrolling since the parabola
 -- effect is created by calculating the x position based on the y position. 
 InitStraight(newDisplayEvent, activeDisplayEvents, isReversed);
end


-- **********************************************************************************
-- Scrolls the passed display event in a right parabola.
-- **********************************************************************************
local function ScrollRightParabola(displayEvent)
 -- Calculate the animation midpoint.
 local animationMidPoint = displayEvent.ScrollHeight / 2;

 -- Calculate the new x position based on equation of a parabola.
 -- Equation of a parabola at vertex 0,0: x = y^2 / 4a
 local parabolaMaxX = ceil((animationMidPoint * animationMidPoint) / (4 * animationMidPoint));
 local parabolaY = displayEvent.PositionY - animationMidPoint;
 local newX = parabolaMaxX - ceil((parabolaY * parabolaY) / (4 * animationMidPoint));

 -- Set the new x position.
 displayEvent.PositionX = newX;
end


-- **********************************************************************************
-- Scrolls the passed display event in a right parabola upwards.
-- **********************************************************************************
local function ScrollRightParabolaUp(displayEvent)
 -- Leverage the scroll up logic.
 local animationComplete = ScrollUp(displayEvent);

 -- Scroll the text in a right parabola.
 ScrollRightParabola(displayEvent);

 -- Return whether or not the animation is complete.
 return animationComplete;
end


-- **********************************************************************************
-- Scrolls the passed display event in a right parabola downwards.
-- **********************************************************************************
local function ScrollRightParabolaDown(displayEvent)
 -- Leverage the scroll down logic.
 local animationComplete = ScrollDown(displayEvent);

 -- Scroll the text in a right parabola.
 ScrollRightParabola(displayEvent);

 -- Return whether or not the animation is complete.
 return animationComplete;
end


-------------------------------------------------------------------------------------
-- Load.
-------------------------------------------------------------------------------------

-- Register the default animation styles.
MikSBT.RegisterAnimationStyle("Straight", InitStraight, ScrollUp, ScrollDown);
MikSBT.RegisterAnimationStyle("LeftParabola", InitLeftParabola, ScrollLeftParabolaUp, ScrollLeftParabolaDown);
MikSBT.RegisterAnimationStyle("RightParabola", InitRightParabola, ScrollRightParabolaUp, ScrollRightParabolaDown);

-- Register the default sticky animation styles.
MikSBT.RegisterStickyAnimationStyle("Pow", InitPow, AnimatePowJiggle, AnimatePowJiggle);