-- lx-bundle:true

-- This file is part of Lx and Qui, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

-- Easing functions taken from Resonic Core

--- Provide real-valued easing curves for QUI animation and interpolation
-- Inputs normally run from zero to one
-- @module qui.easing

local easing = {}
local pi = 3.14159265358979323846
local back_overshoot = 1.70158
local back_in_out_overshoot = back_overshoot * 1.525

--- Round to the nearest integer for the elastic strength setting
-- Midpoints move away from zero, matching the original easing code
-- @param value number value to round
-- @return integer rounded
local function round_strength(value)
	local absolute = math.abs(value)
	local rounded = math.floor(absolute)
	if absolute - rounded >= 0.5 then
		rounded = rounded + 1
	end

	return value >= 0 and rounded or -rounded
end

--- Return an unchanged real value
-- @param x number input from zero to one
-- @return number value
function easing.linear(x)
	return x
end

--- Return the falling straight-line value
-- @param x number input from zero to one
-- @return number value
function easing.inverse(x)
	return 1 - x
end

--- Ease through a wide middle section
-- @param x number input from zero to one
-- @return number value
function easing.smooth(x)
	return x * (2 * x * x - 3 * x + 2)
end

--- Ease through a wider middle section
-- @param x number input from zero to one
-- @return number value
function easing.smoother(x)
	return x * (3 * x * x - 4.5 * x + 2.5)
end

--- Ease through the widest polynomial middle section
-- @param x number input from zero to one
-- @return number value
function easing.smoothest(x)
	return x * (4 * x * x - 6 * x + 3)
end

--- Weight motion toward the bottom of the range
-- @param x number input from zero to one
-- @return number value
function easing.bottom(x)
	return x * (x * x + 1) * 0.5
end

--- Weight motion more strongly toward the bottom of the range
-- @param x number input from zero to one
-- @return number value
function easing.bottom2(x)
	local x2 = x * x

	return x2 * (x2 * x + 1) * 0.5
end

--- Weight motion toward the top of the range
-- @param x number input from zero to one
-- @return number value
function easing.top(x)
	return x * (x * x - 3 * x + 4) * 0.5
end

--- Apply the second top-weighted curve
-- @param x number input from zero to one
-- @return number value
function easing.top2(x)
	return x * (x * x - 4 * x + 5) * 0.5
end

--- Apply the third top-weighted curve
-- @param x number input from zero to one
-- @return number value
function easing.top3(x)
	return x * (x * x - 5 * x + 6) * 0.5
end

--- Rise to one and return to zero along a parabola
-- @param x number input from zero to one
-- @return number value
function easing.parable(x)
	return 4 * x - 4 * x * x
end

--- Scale an eased value to a fraction of its effect
-- @param value number eased value from zero to one
-- @param fraction number amount of the easing to retain
-- @return number scaled
function easing.scale(value, fraction)
	return (1 - fraction) + fraction * value
end

--- Apply the perceptual volume-control curve described by Dr. Lex
-- @param x number input from zero to one
-- @return number value
function easing.lex(x)
	if x <= 0 then
		return 0
	end
	if x >= 1 then
		return 1
	end

	return math.exp(6.908 * x) * 0.001
end

--- Apply a smooth cubic crossfade curve
-- @param x number input from zero to one
-- @return number value
function easing.crossfade(x)
	if x < 0.5 then
		return 4 * x * x * x
	end

	local inverse = -2 * x + 2

	return 1 - inverse * inverse * inverse * 0.5
end

--- Accelerate along a sine curve
-- @param x number input from zero to one
-- @return number value
function easing.sine_in(x)
	return 1 - math.cos(x * pi * 0.5)
end

--- Decelerate along a sine curve
-- @param x number input from zero to one
-- @return number value
function easing.sine_out(x)
	return math.sin(x * pi * 0.5)
end

--- Accelerate then decelerate along a sine curve
-- @param x number input from zero to one
-- @return number value
function easing.sine_in_out(x)
	return 0.5 - math.cos(x * pi) * 0.5
end

--- Pull backward before accelerating
-- @param x number input from zero to one
-- @return number value
function easing.back_in(x)
	return x * x * ((back_overshoot + 1) * x - back_overshoot)
end

--- Overshoot before settling at the end
-- @param x number input from zero to one
-- @return number value
function easing.back_out(x)
	local shifted = x - 1

	return shifted * shifted * ((back_overshoot + 1) * shifted + back_overshoot) + 1
end

--- Pull backward, accelerate, overshoot, and settle
-- @param x number input from zero to one
-- @return number value
function easing.back_in_out(x)
	local doubled = x * 2
	if doubled < 1 then
		return 0.5 * doubled * doubled * ((back_in_out_overshoot + 1) * doubled - back_in_out_overshoot)
	end

	local shifted = doubled - 2

	return 0.5 * (shifted * shifted * ((back_in_out_overshoot + 1) * shifted + back_in_out_overshoot) + 2)
end

--- Accelerate along a cubic curve
-- @param x number input from zero to one
-- @return number value
function easing.cubic_in(x)
	return x * x * x
end

--- Decelerate along a cubic curve
-- @param x number input from zero to one
-- @return number value
function easing.cubic_out(x)
	local shifted = x - 1

	return 1 + shifted * shifted * shifted
end

--- Accelerate then decelerate along a cubic curve
-- @param x number input from zero to one
-- @return number value
function easing.cubic_in_out(x)
	if x < 0.5 then
		return 4 * x * x * x
	end

	local shifted = x - 1

	return 1 + 4 * shifted * shifted * shifted
end

--- Accelerate along a quadratic curve
-- @param x number input from zero to one
-- @return number value
function easing.quad_in(x)
	return x * x
end

--- Decelerate along a quadratic curve
-- @param x number input from zero to one
-- @return number value
function easing.quad_out(x)
	local shifted = x - 1

	return 1 - shifted * shifted
end

--- Accelerate then decelerate along a quadratic curve
-- @param x number input from zero to one
-- @return number value
function easing.quad_in_out(x)
	if x < 0.5 then
		return 2 * x * x
	end

	local shifted = x - 1

	return 1 - 2 * shifted * shifted
end

--- Accelerate along a quartic curve
-- @param x number input from zero to one
-- @return number value
function easing.quart_in(x)
	local squared = x * x

	return squared * squared
end

--- Decelerate along a quartic curve
-- @param x number input from zero to one
-- @return number value
function easing.quart_out(x)
	local shifted = x - 1
	local squared = shifted * shifted

	return 1 - squared * squared
end

--- Accelerate then decelerate along a quartic curve
-- @param x number input from zero to one
-- @return number value
function easing.quart_in_out(x)
	local value = x < 0.5 and x or x - 1
	local squared = value * value
	local amount = 8 * squared * squared

	return x < 0.5 and amount or 1 - amount
end

--- Accelerate along a quintic curve
-- @param x number input from zero to one
-- @return number value
function easing.quint_in(x)
	local squared = x * x

	return squared * squared * x
end

--- Decelerate along a quintic curve
-- @param x number input from zero to one
-- @return number value
function easing.quint_out(x)
	local shifted = x - 1
	local squared = shifted * shifted

	return 1 + squared * squared * shifted
end

--- Accelerate then decelerate along a quintic curve
-- @param x number input from zero to one
-- @return number value
function easing.quint_in_out(x)
	local value = x < 0.5 and x or x - 1
	local squared = value * value
	local amount = 16 * squared * squared * value

	return x < 0.5 and amount or 1 + amount
end

--- Accelerate along a circular arc
-- @param x number input from zero to one
-- @return number value
function easing.circ_in(x)
	return 1 - math.sqrt(1 - x * x)
end

--- Decelerate along a circular arc
-- @param x number input from zero to one
-- @return number value
function easing.circ_out(x)
	local shifted = x - 1

	return math.sqrt(1 - shifted * shifted)
end

--- Accelerate then decelerate along two circular arcs
-- @param x number input from zero to one
-- @return number value
function easing.circ_in_out(x)
	if x < 0.5 then
		local doubled = 2 * x

		return 0.5 - math.sqrt(1 - doubled * doubled) * 0.5
	end

	local shifted = 2 * x - 2

	return 0.5 + math.sqrt(1 - shifted * shifted) * 0.5
end

--- Accelerate exponentially
-- @param x number input from zero to one
-- @return number value
function easing.expo_in(x)
	return 2 ^ (10 * (x - 1))
end

--- Decelerate exponentially
-- @param x number input from zero to one
-- @return number value
function easing.expo_out(x)
	return 1 - 2 ^ (-10 * x)
end

--- Accelerate then decelerate exponentially
-- @param x number input from zero to one
-- @return number value
function easing.expo_in_out(x)
	local shifted = 2 * x - 1
	if shifted < 0 then
		return 2 ^ (10 * shifted) * 0.5
	end

	return 1 - 2 ^ (-10 * shifted) * 0.5
end

--- Pull backward before accelerating with the original swing name
-- @param x number input from zero to one
-- @return number value
function easing.swing_from(x)
	return x * x * ((back_overshoot + 1) * x - back_overshoot)
end

--- Overshoot before settling with the original swing name
-- @param x number input from zero to one
-- @return number value
function easing.swing_to(x)
	local shifted = x - 1

	return shifted * shifted * ((back_overshoot + 1) * shifted + back_overshoot) + 1
end

--- Pull backward and overshoot with the original swing name
-- @param x number input from zero to one
-- @return number value
function easing.swing_from_to(x)
	local doubled = x * 2
	if doubled < 1 then
		return 0.5 * doubled * doubled * ((back_in_out_overshoot + 1) * doubled - back_in_out_overshoot)
	end

	local shifted = doubled - 2

	return 0.5 * (shifted * shifted * ((back_in_out_overshoot + 1) * shifted + back_in_out_overshoot) + 2)
end

--- Accelerate with the original fourth-power ease name
-- @param x number input from zero to one
-- @return number value
function easing.ease_from(x)
	local squared = x * x

	return squared * squared
end

--- Decelerate with the original fourth-root ease name
-- @param x number input from zero to one
-- @return number value
function easing.ease_to(x)
	return x ^ 0.25
end

--- Accelerate then decelerate with the original fourth-power ease name
-- @param x number input from zero to one
-- @return number value
function easing.ease_from_to(x)
	local doubled = x * 2
	if doubled < 1 then
		local squared = doubled * doubled

		return 0.5 * squared * squared
	end

	local shifted = doubled - 2

	return -0.5 * (shifted * shifted * shifted * shifted - 2)
end

--- Repeat a smooth pulse
-- @param x number input from zero to one
-- @param count number pulse count, with zero meaning one
-- @return number value
function easing.pulse(x, count)
	if count == 0 then
		count = 1
	end

	return 0.5 - math.cos(x * count * 2 * pi) * 0.5
end

--- Wobble through a requested number of turns
-- @param x number input from zero to one
-- @param count number wobble count, with zero meaning three
-- @return number value
function easing.wobble(x, count)
	if count == 0 then
		count = 3
	end

	return 0.5 - math.cos((2 * count - 1) * x * x * pi) * 0.5
end

--- Apply a decaying elastic oscillation
-- @param x number input from zero to one
-- @param strength number strength from one to ten, with zero using the original default
-- @return number value
function easing.elastic(x, strength)
	local damping
	if strength == 0 then
		damping = 30
	else
		strength = round_strength(math.max(1, math.min(10, strength)))
		damping = (11 - strength) * 5
	end

	return 1 - math.cos(x * 8 * pi) / (damping * x + 1) * (1 - x)
end

--- Bounce into the end value
-- @param x number input from zero to one
-- @param count number bounce count, with zero meaning four
-- @return number value
function easing.bounce(x, count)
	count = count ~= 0 and round_strength(count) or 4

	local scale = 3 - 2 ^ (2 - count)
	local marker = -1
	local distance = 0
	local index = 0
	while marker / scale < x do
		distance = 2 ^ (1 - index)
		index = index + 1
		marker = marker + distance
	end
	if marker - distance > 0 then
		x = x - ((marker - distance) + distance * 0.5) / scale
	end

	return scale * scale * x * x + (1 - 0.25 ^ (index - 1))
end

--- Bounce past the end value before settling
-- @param x number input from zero to one
-- @return number value
function easing.bounce_past(x)
	if x < 1 / 2.75 then
		return 7.5625 * x * x
	end
	if x < 2 / 2.75 then
		x = x - 1.5 / 2.75

		return 2 - (7.5625 * x * x + 0.75)
	end
	if x < 2.5 / 2.75 then
		x = x - 2.25 / 2.75

		return 2 - (7.5625 * x * x + 0.9375)
	end

	x = x - 2.625 / 2.75
	return 2 - (7.5625 * x * x + 0.984375)
end

return easing
