--[[ Public API ]]

--
function Thumbstick:Enable()
	ThumbstickFrame.Visible = true
	local humanoid = MasterControl:GetHumanoid()
	enableAutoJump(humanoid)
end

function Thumbstick:Disable()
	if RevertAutoJumpEnabledToFalse then
		local humanoid = MasterControl:GetHumanoid()
		if humanoid then
			humanoid.AutoJumpEnabled = false
		end
	end
	ThumbstickFrame.Visible = false
	OnMoveTouchEnded()
end

function Thumbstick:Create(parentFrame)
	if ThumbstickFrame then
		ThumbstickFrame:Destroy()
		ThumbstickFrame = nil
		if OnTouchMovedCn then
			OnTouchMovedCn:disconnect()
			OnTouchMovedCn = nil
		end
		if OnTouchEndedCn then
			OnTouchEndedCn:disconnect()
			OnTouchEndedCn = nil
		end
		if OnRenderSteppedCn then
			OnRenderSteppedCn:disconnect()
			OnRenderSteppedCn = nil
		end
		if TouchActivateCn then
			TouchActivateCn:disconnect()
			TouchActivateCn = nil
		end
	end
	
	local ThumbstickSize = 40
	local ThumbstickRingSize = 58
	local MiddleSize = 13
	local MiddleSpacing = MiddleSize + 5
	local RadiusOfDeadZone = 2
	local RadiusOfMaxSpeed = 50	
	
	local screenSize = parentFrame.AbsoluteSize
	local isBigScreen = math.min(screenSize.x, screenSize.y) > 500
	if isBigScreen then
		ThumbstickSize = ThumbstickSize * 2
		ThumbstickRingSize = ThumbstickRingSize * 2
		MiddleSize = MiddleSize * 2
		MiddleSpacing = MiddleSpacing * 2
		RadiusOfDeadZone = RadiusOfDeadZone * 2
		RadiusOfMaxSpeed = RadiusOfMaxSpeed * 2
	end
	
	local color = Color3.fromRGB(255, 255, 255)
	
	local function layoutThumbstickFrame(portraitMode)
		if portraitMode then
			ThumbstickFrame.Size = UDim2.new(1, 0, .4, 0)
			ThumbstickFrame.Position = UDim2.new(0, 0, 0.6, 0)
		else
			ThumbstickFrame.Size = UDim2.new(1, 0, .5, 18)
			ThumbstickFrame.Position = UDim2.new(0, 0, 0.5, -18)
		end
	end
		
	ThumbstickFrame = Instance.new('Frame')
	ThumbstickFrame.Name = "DynamicThumbstickFrame"
	ThumbstickFrame.Active = false
	ThumbstickFrame.Visible = false
	ThumbstickFrame.BackgroundTransparency = 1.0
	ThumbstickFrame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
	layoutThumbstickFrame()

	StartImage = Instance.new("ImageLabel")
	StartImage.Name = "ThumbstickStart"
	StartImage.Visible = false
	StartImage.BackgroundTransparency = 1
	StartImage.Image = IMAGE_RING
	StartImage.AnchorPoint = Vector2.new(0.5, 0.5)
	StartImage.Parent = ThumbstickFrame
	
	EndImage = Instance.new("ImageLabel")
	EndImage.Name = "ThumbstickEnd"
	EndImage.Visible = false
	EndImage.BackgroundTransparency = 1
	EndImage.Image = IMAGE_DISK
	EndImage.ImageTransparency = 0.20
	EndImage.AnchorPoint = Vector2.new(0.5, 0.5)
	EndImage.Parent = ThumbstickFrame
	
	for i = 1, NUM_MIDDLE_IMAGES do
		MiddleImages[i] = Instance.new("ImageLabel")
		MiddleImages[i].Name = "ThumbstickMiddle"
		MiddleImages[i].Visible = false
		MiddleImages[i].BackgroundTransparency = 1
		MiddleImages[i].Image = IMAGE_DISK
		MiddleImages[i].ImageTransparency = MIDDLE_TRANSPARENCIES[i]
		MiddleImages[i].AnchorPoint = Vector2.new(0.5, 0.5)
		MiddleImages[i].Parent = ThumbstickFrame
	end
	
	local CameraChangedConn = nil
	local function onWorkspaceChanged(property)
		if property == 'CurrentCamera' then
			if CameraChangedConn then
				CameraChangedConn:Disconnect()
				CameraChangedConn = nil
			end
			local newCamera = workspace.CurrentCamera
			if newCamera then
				local size = newCamera.ViewportSize
				local portraitMode = size.X < size.Y
				layoutThumbstickFrame(portraitMode)
				CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
					size = newCamera.ViewportSize
					portraitMode = size.X < size.Y
					layoutThumbstickFrame(portraitMode)
				end)
			end
		end
	end
	workspace.Changed:Connect(onWorkspaceChanged)
	if workspace.CurrentCamera then
		onWorkspaceChanged('CurrentCamera')
	end
	
	local JumpTouchObject = nil
	local JumpTouchStartTime = nil
	local JumpTouchStartPosition = nil
	
	local MoveTouchObject = nil
	local MoveTouchStartTime = nil
	local MoveTouchStartPosition = nil
	
	local function doMove(direction)
		MasterControl:AddToPlayerMovement(-currentMoveVector)
		
		currentMoveVector = direction
		
		-- Scaled Radial Dead Zone
		local inputAxisMagnitude = currentMoveVector.magnitude
		if inputAxisMagnitude < RadiusOfDeadZone then
			currentMoveVector = Vector3.new()
		else
			currentMoveVector = currentMoveVector.unit*(1 - math.max(0, (RadiusOfMaxSpeed - currentMoveVector.magnitude)/RadiusOfMaxSpeed))
			currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y)
		end
		
		MasterControl:AddToPlayerMovement(currentMoveVector)
	end
	
	local TweenInAlphaStart = nil
	local TweenOutAlphaStart = nil
	OnRenderSteppedCn = RunService.RenderStepped:Connect(function(step)
		if TweenInAlphaStart ~= nil then
			local delta = tick() - TweenInAlphaStart
			local fadeInTime = (FadeInAndOutHalfDuration * 2 * FadeInAndOutBalance)
			ThumbstickFrame.BackgroundTransparency = 1 - FadeInAndOutMaxAlpha*math.min(delta/fadeInTime, 1)
			if delta > fadeInTime then
				TweenOutAlphaStart = tick()
				TweenInAlphaStart = nil
			end
		elseif TweenOutAlphaStart ~= nil then
			local delta = tick() - TweenOutAlphaStart
			local fadeOutTime = (FadeInAndOutHalfDuration * 2) - (FadeInAndOutHalfDuration * 2 * FadeInAndOutBalance)
			ThumbstickFrame.BackgroundTransparency = 1 - FadeInAndOutMaxAlpha + FadeInAndOutMaxAlpha*math.min(delta/fadeOutTime, 1)
			if delta > fadeOutTime  then
				TweenOutAlphaStart = nil
			end
		end
	end)
	
	local function layoutMiddleImages(startPos, endPos)
		local startDist = (ThumbstickSize / 2) + MiddleSize
		local dist = startDist
		local vector = endPos - startPos
		local totalDist = vector.magnitude - (ThumbstickSize / 2) - (ThumbstickRingSize / 2) - (MiddleSize / 2)
		local direction = vector.unit

		local distNeeded = dist + (MiddleSpacing * NUM_MIDDLE_IMAGES)		
		local spacing = 0
		
		if distNeeded > totalDist then
			spacing = MiddleSpacing
		else
			spacing = totalDist / NUM_MIDDLE_IMAGES
		end
			
		for i = 1, NUM_MIDDLE_IMAGES do
			local image = MiddleImages[i]
			local currentDist = startDist + (spacing * (i - 1))	
				
			if currentDist < totalDist then
				local pos = endPos - direction * currentDist
				
				image.Visible = true
				image.Position = UDim2.new(0, pos.X, 0, pos.Y)
				image.Size = UDim2.new(0, MiddleSize, 0, MiddleSize)
			else
				image.Visible = false
			end
		end
	end
	
	local function moveStick(pos)
		local startPos = Vector2.new(MoveTouchStartPosition.X, MoveTouchStartPosition.Y) - ThumbstickFrame.AbsolutePosition
		local endPos = Vector2.new(pos.X, pos.Y) - ThumbstickFrame.AbsolutePosition
		local relativePosition = endPos - startPos
		local length = relativePosition.magnitude
		local maxLength = ThumbstickFrame.AbsoluteSize.X
		
		length = math.min(length, maxLength)
		relativePosition = relativePosition*length
		
		EndImage.Position = UDim2.new(0, endPos.X, 0, endPos.Y)
		EndImage.Size = UDim2.new(0, ThumbstickSize, 0, ThumbstickSize)
		
		layoutMiddleImages(startPos, endPos)
	end
	
	-- input connections
	ThumbstickFrame.InputBegan:connect(function(inputObject)
		if inputObject.UserInputType ~= Enum.UserInputType.Touch or inputObject.UserInputState ~= Enum.UserInputState.Begin then
			return
		end
		if MoveTouchObject then
			if not JumpTouchObject then
				JumpTouchObject = inputObject
				JumpTouchStartTime = tick()
				JumpTouchStartPosition = inputObject.Position
			end
			return
		end
		
		MoveTouchObject = inputObject
		MoveTouchStartTime = tick()
		MoveTouchStartPosition = inputObject.Position
		local startPosVec2 = Vector2.new(inputObject.Position.X - ThumbstickFrame.AbsolutePosition.X, inputObject.Position.Y - ThumbstickFrame.AbsolutePosition.Y)

		StartImage.Visible = true
		StartImage.Position = UDim2.new(0, startPosVec2.X, 0, startPosVec2.Y)
		StartImage.Size = UDim2.new(0, ThumbstickRingSize, 0, ThumbstickRingSize)
		EndImage.Visible = true
		EndImage.Position = StartImage.Position
		EndImage.Size = UDim2.new(0, ThumbstickSize, 0, ThumbstickSize)
		
		if FadeInAndOutBackground then
			local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
			local hasFadedBackgroundInOrientation = false

			-- only fade in/out the background once per orientation
			if playerGui then
				if playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or 
					playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight then
						hasFadedBackgroundInOrientation = HasFadedBackgroundInLandscape
						HasFadedBackgroundInLandscape = true
				elseif playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait then
						hasFadedBackgroundInOrientation = HasFadedBackgroundInPortrait
						HasFadedBackgroundInPortrait = true
				end
			end

			if not hasFadedBackgroundInOrientation then
				FadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
				FadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
				TweenInAlphaStart = tick()
			end
		end
	end)
	
	OnTouchMovedCn = UserInputService.TouchMoved:connect(function(inputObject, isProcessed)
		if inputObject == MoveTouchObject then
			
			local direction = Vector2.new(inputObject.Position.x - MoveTouchStartPosition.x, inputObject.Position.y - MoveTouchStartPosition.y)
			if math.abs(direction.x) > 0 or math.abs(direction.y) > 0 then
				doMove(direction)
				moveStick(inputObject.Position)
			end
		end
	end)
	
	OnMoveTouchEnded = function()
		StartImage.Visible = false
		EndImage.Visible = false
		
		for i = 1, NUM_MIDDLE_IMAGES do
			MiddleImages[i].Visible = false
		end
		
		MoveTouchObject = nil
		
		MasterControl:AddToPlayerMovement(-currentMoveVector)
		currentMoveVector = Vector3.new(0,0,0)
	end

	--todo: remove this pcall when TouchTapInWorld is on for all platforms
	local success = pcall(function() 
		local function positionIntersectsGuiObject(position, guiObject)
			if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
				and position.X > guiObject.AbsolutePosition.X
				and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
				and position.Y > guiObject.AbsolutePosition.Y then
				return true
			end
			return false
		end

		TouchActivateCn = UserInputService.TouchTapInWorld:connect(function(touchPos, processed)
			if not processed and not IsAToolEquipped and positionIntersectsGuiObject(touchPos, ThumbstickFrame) then
				MasterControl:DoJump()
			end
		end)
	end)

	if not success then
		local function JumpIfTouchIsTap(startTime, startPosition, position)
			if (not IsAToolEquipped) and tick() - startTime < TOUCH_IS_TAP_TIME_THRESHOLD then
				if (position - startPosition).magnitude < TOUCH_IS_TAP_DISTANCE_THRESHOLD then
					MasterControl:DoJump()
				end
			end
		end
		
		OnTouchEndedCn = UserInputService.TouchEnded:connect(function(inputObject, isProcessed)
			if inputObject == MoveTouchObject then
				JumpIfTouchIsTap(MoveTouchStartTime, MoveTouchStartPosition, inputObject.Position) 
				OnMoveTouchEnded()
			elseif inputObject == JumpTouchObject then
				JumpIfTouchIsTap(JumpTouchStartTime, JumpTouchStartPosition, inputObject.Position)
				JumpTouchObject = nil
			end
		end)
	else
		OnTouchEndedCn = UserInputService.TouchEnded:connect(function(inputObject, isProcessed)
			if inputObject == MoveTouchObject then
				OnMoveTouchEnded()
			elseif inputObject == JumpTouchObject then
				JumpTouchObject = nil
			end
		end)
	end
	
	GuiService.MenuOpened:connect(function()
		if MoveTouchObject then
			OnMoveTouchEnded()
		end
		if JumpTouchObject then
			JumpTouchObject = nil
		end
	end)

	local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
	while not playerGui do
		LocalPlayer.ChildAdded:wait()
		playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
	end

	local playerGuiChangedConn = nil
	local originalScreenOrientationWasLandscape = playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
													playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight

	local function longShowBackground()
		FadeInAndOutHalfDuration = 2.5
		FadeInAndOutBalance = 0.05
		TweenInAlphaStart = tick()
	end

	playerGuiChangedConn = playerGui.Changed:connect(function(prop)
		if prop == "CurrentScreenOrientation" then
			if (originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait) or
				(not originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation ~= Enum.ScreenOrientation.Portrait) then
					playerGuiChangedConn:disconnect()
					longShowBackground()

					if originalScreenOrientationWasLandscape then
						HasFadedBackgroundInPortrait = true
					else
						HasFadedBackgroundInLandscape = true
					end
			end
		end
	end)
	
	ThumbstickFrame.Parent = parentFrame

	spawn(function()
		if game:IsLoaded() then
			longShowBackground()
		else
			game.Loaded:wait()
			longShowBackground()
		end
	end)
end

return Thumbstick