--[=[-----------------------------------------------------------------------------------------------

	TutorialFinger

-------------------------------------------------------------------------------------------------]=]

class 'TutorialFinger'

TutorialFinger.States =
{
	Up = "tutorial/hand_point_1",
	Mid = "tutorial/hand_point_2",
	Down = "tutorial/hand_point_3",
}

TutorialFinger.Easing =
{
	None = 0,
	In = 1,
	Out = 2,
	InOut = 3,
}

--[=[
Example frames input parameter to animate moving the finger from (100, 100)->(200, 200) and then tap once:
{
	{
		state = TutorialFinger.States.Up,
		start_position = Vector2(100, 100),
		end_position = Vector2(200, 200),
		duration = 1,
		easing = TutorialFinger.Easing.Out,
	},
	{
		state = TutorialFinger.States.Down,
		duration = 0.3,
	},
	{
		state = TutorialFinger.States.Up,
		duration = 0.5,
	},
}
]=]

function TutorialFinger:__init(frames, looping, offset)
	if frames then
		self.frames = frames
	else
		self.frames = {}
	end
	if looping then
		self.looping = looping
	else
		self.looping = false
	end
	if offset then
		self.offset = offset
	else
		self.offset = Vector2(0, 0)
	end
	self.current_frame = 0
	self.current_time = 0
	self.current_position = Vector2(0, 0)
	self.playing = false
	self.visible = true
end

function TutorialFinger:Start()
	if self.effect then
		self.effect:Stop(false)
		self.effect = nil
	end
	self.current_frame = 1
	self.current_time = 0
	self.playing = true
	local frame = self.frames[1]
	if frame and frame.state then
		if frame.start_position then
			self.current_position = frame.start_position
		elseif frame.end_position then
			self.current_position = frame.end_position
		end
		self.effect = World_CreateEffect(frame.state, self.current_position)
		self.effect:SetIsHidden(not self.visible)
	end
end

function TutorialFinger:Stop()
	if self.effect then
		self.effect:Stop(false)
		self.effect = nil
	end
	self.playing = false
end

function TutorialFinger:IsPlaying()
	return self.playing
end

function TutorialFinger:IsLooping()
	return self.looping
end

function TutorialFinger:SetLooping(looping)
	self.looping = looping
end

function TutorialFinger:IsVisible()
	return self.visible
end

function TutorialFinger:SetVisible(visible)
	if self.visible ~= visible then
		self.visible = visible
		if self.effect then
			self.effect:SetIsHidden(not visible)
		end
	end
end

function TutorialFinger:GetOffset()
	return Vector2(self.offset.x, self.offset.y)
end

function TutorialFinger:SetOffset(offset)
	if offset then
		self.offset.x = offset.x
		self.offset.y = offset.y
	else
		self.offset.x = 0
		self.offset.y = 0
	end
end

function TutorialFinger:OnUpdate(dt)
	if self.playing then
		local frame = self.frames[self.current_frame]
		--If current frame not valid, then we can assume there are no valid frames at all
		if frame then
			local duration = 0
			if frame.duration and frame.duration > 0 then
				duration = frame.duration
			end
			
			local prev_frame = self.current_frame
			local next_time = self.current_time + dt
			self.current_time = next_time
			
			--Check if we need to advance frames
			while self.current_time > duration do
				if frame.end_position then
					self.current_position = frame.end_position
				elseif frame.start_position then
					self.current_position = frame.start_position
				end
				self.current_time = self.current_time - duration
				self.current_frame = self.current_frame + 1
				frame = self.frames[self.current_frame]
				
				--Check for looping if we've reached the last frame
				if not frame then
					if self.looping then
						self.current_frame = 1
						frame = self.frames[1]
					else
						self.current_frame = self.current_frame - 1
						frame = self.frames[self.current_frame]
						if frame.duration and frame.duration > 0 then
							self.current_time = frame.duration
						else
							self.current_time = 0
						end
					end
				end
				
				if frame.duration and frame.duration > 0 then
					duration = frame.duration
				else
					duration = 0
				end
				
				--Check if we've completely looped around and none of the frames had any duration
				if self.current_frame == prev_frame and self.current_time == next_time then
					self.current_time = duration
				end
			end
			
			--Update current position
			if duration > 0 and frame.start_position and frame.end_position then
				local tween_progress = self.current_time / duration
				if frame.easing == TutorialFinger.Easing.In then
					tween_progress = tween_progress * tween_progress
				elseif frame.easing == TutorialFinger.Easing.Out then
					tween_progress = 1 - tween_progress
					tween_progress = 1 - tween_progress * tween_progress
				elseif frame.easing == TutorialFinger.Easing.InOut then
					if tween_progress < 0.5 then
						tween_progress = tween_progress * tween_progress * 2
					elseif tween_progress > 0.5 then
						tween_progress = 2 - tween_progress * 2
						tween_progress = 1 - tween_progress * tween_progress * 0.5
					end
				end
				self.current_position = frame.start_position + (frame.end_position - frame.start_position) * tween_progress
			elseif frame.start_position then
				self.current_position = frame.start_position
			elseif frame.end_position then
				self.current_position = frame.end_position
			end
			
			--Update effect if we've changed frames
			if self.current_frame ~= prev_frame then
				if self.effect then
					self.effect:Stop(false)
					self.effect = nil
				end
				if frame.state then
					self.effect = World_CreateEffect(frame.state, self.current_position + self.offset)
					self.effect:SetIsHidden(not self.visible)
				end
			elseif self.effect then
				self.effect:SetPosition(self.current_position + self.offset)
			end
		end
	end
end

--[=[-----------------------------------------------------------------------------------------------

	TapTutorialFinger

-------------------------------------------------------------------------------------------------]=]

class 'TapTutorialFinger' (TutorialFinger)

function TapTutorialFinger:__init(offset)
	super(
		{
			{
				state = TutorialFinger.States.Up,
				start_position = Vector2(0, -25),
				end_position = Vector2(0, -7),
				duration = 0.6,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				start_position = Vector2(0, -7),
				end_position = Vector2(0, -5),
				duration = 0.035,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				start_position = Vector2(0, -5),
				end_position = Vector2(0, 0),
				duration = 0.2,
				easing = TutorialFinger.Easing.Out,
			},
			{
				state = TutorialFinger.States.Down,
				start_position = Vector2(0, 0),
				end_position = Vector2(0, -5),
				duration = 0.2,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				start_position = Vector2(0, -5),
				end_position = Vector2(0, -7),
				duration = 0.035,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Up,
				start_position = Vector2(0, -7),
				end_position = Vector2(0, -25),
				duration = 0.6,
				easing = TutorialFinger.Easing.Out,
			},
		},
		true,
		offset
	)
end

--[=[-----------------------------------------------------------------------------------------------

	SwipeTutorialFinger

-------------------------------------------------------------------------------------------------]=]

class 'SwipeTutorialFinger' (TutorialFinger)

function SwipeTutorialFinger:__init(start_position, end_position)
	super(
		{
			{
				state = TutorialFinger.States.Up, 
				duration = 0.3,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				duration = 0.04,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.4,
				easing = TutorialFinger.Easing.Out,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 2,
				easing = TutorialFinger.Easing.InOut,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.15,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				duration = 0.04,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Up,
				duration = .6, 
				easing = TutorialFinger.Easing.Out,
			}, 
		},
		true
	)
	self.start_position = Vector2(0, 0)
	self.end_position = Vector2(0, 0)
	self:SetStartPosition(start_position)
	self:SetEndPosition(end_position)
end

function SwipeTutorialFinger:GetStartPosition()
	return Vector2(self.start_position.x, self.start_position.y)
end

function SwipeTutorialFinger:GetEndPosition()
	return Vector2(self.end_position.x, self.end_position.y)
end

function SwipeTutorialFinger:SetStartPosition(pos)
	if pos then
		self.start_position.x = pos.x
		self.start_position.y = pos.y
	else
		self.start_position.x = 0
		self.start_position.y = 0
	end
	self.frames[1].start_position = self.start_position + Vector2(0, -20)
	self.frames[1].end_position = self.start_position + Vector2(0, -12)
	self.frames[2].start_position = self.start_position + Vector2(0, -12)
	self.frames[2].end_position = self.start_position + Vector2(0, -10)
	self.frames[3].start_position = self.start_position + Vector2(0, -10)
	self.frames[3].end_position = self.start_position + Vector2(0, 0)
	self.frames[4].start_position = self.start_position + Vector2(0, 0)
end

function SwipeTutorialFinger:SetEndPosition(pos)
	if pos then
		self.end_position.x = pos.x
		self.end_position.y = pos.y
	else
		self.end_position.x = 0
		self.end_position.y = 0
	end
	self.frames[4].end_position = self.end_position + Vector2(0, 0)
	self.frames[5].start_position = self.end_position + Vector2(0, 0)
	self.frames[5].end_position = self.end_position + Vector2(0, -5)
	self.frames[6].start_position = self.end_position + Vector2(0, -5)
	self.frames[6].end_position = self.end_position + Vector2(0, -7)
	self.frames[7].start_position = self.end_position + Vector2(0, -7)
	self.frames[7].end_position = self.end_position + Vector2(0, -20)
end

--[=[-----------------------------------------------------------------------------------------------

	RotateTutorialFinger

-------------------------------------------------------------------------------------------------]=]

class 'RotateTutorialFinger' (TutorialFinger)

function RotateTutorialFinger:__init(radius, reversed, offset)
	super(
		{
			{
				state = TutorialFinger.States.Up,
				duration = 0.3,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				duration = 0.04,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.4,
				easing = TutorialFinger.Easing.Out,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.17,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.085,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.17,
				easing = TutorialFinger.Easing.Out,
			},
			{
				state = TutorialFinger.States.Down,
				duration = 0.15,
				easing = TutorialFinger.Easing.In,
			},
			{
				state = TutorialFinger.States.Mid,
				duration = 0.04,
				easing = TutorialFinger.Easing.None,
			},
			{
				state = TutorialFinger.States.Up,
				duration = 0.6,
				easing = TutorialFinger.Easing.Out,
			},
		},
		true,
		offset
	)
	if reversed then
		self.reversed = true
	else
		self.reversed = false
	end
	self:SetRadius(radius)
end

function RotateTutorialFinger:GetRadius()
	return self.radius
end

function RotateTutorialFinger:SetRadius(radius)
	if radius == nil then
		radius = 0
	end
	if self.radius ~= radius then
		self.radius = radius
		self.frames[1].start_position = Vector2(radius, -20)
		self.frames[1].end_position = Vector2(radius, -12)
		self.frames[2].start_position = Vector2(radius, -12)
		self.frames[2].end_position = Vector2(radius, -10)
		self.frames[3].start_position = Vector2(radius, -10)
		self.frames[3].end_position = Vector2(radius, 0)
		self.frames[4].start_position = Vector2(radius, 0)
		local yradius
		if self.reversed then
			yradius = radius
		else
			yradius = -radius
		end
		local theta = math.pi / 16
		local x1 = math.cos(theta) * radius
		local y1 = math.sin(theta) * yradius
		self.frames[4].end_position = Vector2(x1, y1)
		self.frames[5].start_position = Vector2(x1, y1)
		local theta2 = theta * 2
		local x2 = math.cos(theta2) * radius
		local y2 = math.sin(theta2) * yradius
		self.frames[5].end_position = Vector2(x2, y2)
		self.frames[6].start_position = Vector2(x2, y2)
		local theta3 = theta * 3
		local x3 = math.cos(theta3) * radius
		local y3 = math.sin(theta3) * yradius
		self.frames[6].end_position = Vector2(x3, y3)
		self.frames[7].start_position = Vector2(x3, y3)
		local x4 = math.sqrt(radius * radius / 2)
		local y4 = x4
		if not self.reversed then
			y4 = -y4
			y3 = -y3
			x3 = -x3
			y2 = -y2
			x2 = -x2
			y1 = -y1
			x1 = -x1
		end
		self.frames[7].end_position = Vector2(x4, y4)
		self.frames[8].start_position = Vector2(x4, y4)
		self.frames[8].end_position = Vector2(y3, x3)
		self.frames[9].start_position = Vector2(y3, x3)
		self.frames[9].end_position = Vector2(y2, x2)
		self.frames[10].start_position = Vector2(y2, x2)
		self.frames[10].end_position = Vector2(y1, x1)
		self.frames[11].start_position = Vector2(y1, x1)
		self.frames[11].end_position = Vector2(0, yradius)
		self.frames[12].start_position = Vector2(0, yradius)
		self.frames[12].end_position = Vector2(0, yradius - 5)
		self.frames[13].start_position = Vector2(0, yradius - 5)
		self.frames[13].end_position = Vector2(0, yradius - 7)
		self.frames[14].start_position = Vector2(0, yradius - 7)
		self.frames[14].end_position = Vector2(0, yradius - 20)
	end
end

function RotateTutorialFinger:GetReversed()
	return self.reversed
end

function RotateTutorialFinger:SetReversed(reversed)
	if reversed then
		reversed = true
	else
		reversed = false
	end
	if reversed ~= self.reversed then
		self.reversed = reversed
		self.frames[4].end_position.y = -self.frames[4].end_position.y
		for i = 5, 11 do
			self.frames[i].start_position.y = -self.frames[i].start_position.y
			self.frames[i].end_position.y = -self.frames[i].end_position.y
		end
		local radius
		if reversed then
			radius = self.radius
		else
			radius = -self.radius
		end
		self.frames[12].start_position.y = fixed_float(radius)
		self.frames[12].end_position.y = fixed_float(radius - 5)
		self.frames[13].start_position.y = fixed_float(radius - 5)
		self.frames[13].end_position.y = fixed_float(radius - 7)
		self.frames[14].start_position.y = fixed_float(radius - 7)
		self.frames[14].end_position.y = fixed_float(radius - 20)
	end
end

--[=[-----------------------------------------------------------------------------------------------

	Preset fingers

-------------------------------------------------------------------------------------------------]=]

class 'TapGoTutorialFinger' (TapTutorialFinger)
function TapGoTutorialFinger:__init()
	super(Vector2(92, 612))
end

class 'TapHintTutorialFinger' (TapTutorialFinger)
function TapHintTutorialFinger:__init()
	super(Vector2(965, 609))
end

--[=[-----------------------------------------------------------------------------------------------

	Object tutorial finger utility functions

-------------------------------------------------------------------------------------------------]=]

local blueprint_finger_offsets =
{
	beehive = Vector2(0, -43),
	board = Vector2(0, -22),
	cannon = Vector2(0, -31),
	cannon_cupcake = Vector2(0, -31),
	cannon_onion = Vector2(0, -31),
	cannon_pepper = Vector2(0, -31),
	cart_ejection = Vector2(0, -24),
	cart_tnt = Vector2(0, -22),
	fan = Vector2(0, -45),
	plumpo = Vector2(0, -31),
	reflector = Vector2(0, -25),
	spring = Vector2(0, -35),
	whale = Vector2(0, -45),
}

function GetBlueprintTutorialFingerOffset(blueprint)
	local offset = blueprint_finger_offsets[blueprint]
	if offset then
		return Vector2(offset.x, offset.y)
	end
	return Vector2(0, 0)
end

function GetObjectTutorialFingerPosition(object)
	return object:GetPosition() + GetBlueprintTutorialFingerOffset(object:GetBlueprintName())
end

--[=[-----------------------------------------------------------------------------------------------

	TapObjectTutorialFinger

-------------------------------------------------------------------------------------------------]=]

class 'TapObjectTutorialFinger' (TapTutorialFinger)

function TapObjectTutorialFinger:__init(object)
	super()
	if object then
		self:SetOffset(GetObjectTutorialFingerPosition(object))
		self.object = object
	end
end

function TapObjectTutorialFinger:Start()
	TapTutorialFinger.Start(self)
	if self.object then
		local ce = self.object:GetClickableExtension()
		if ce then
			ce:SetShowPointerEffect(false)
		end
	end
end

function TapObjectTutorialFinger:Stop()
	TapTutorialFinger.Stop(self)
	if self.object then
		local ce = self.object:GetClickableExtension()
		if ce then
			ce:SetShowPointerEffect(true)
		end
	end
end