--[[ The Module ]]

--
local VRBaseCamera = require(script.Parent:WaitForChild("VRBaseCamera"))
local VRCamera = setmetatable({}, VRBaseCamera)
VRCamera.__index = VRCamera

local FFlagUserFlagEnableVRUpdate3 do
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserFlagEnableVRUpdate3")
	end)
	FFlagUserFlagEnableVRUpdate3 = success and result
end

function VRCamera.new()
	local self = setmetatable(VRBaseCamera.new(), VRCamera)

	self.lastUpdate = tick()
	self:Reset()

	return self
end

function VRCamera:Reset()
	self.needsReset = true
	self.needsBlackout = true
	self.motionDetTime = 0.0
	self.blackOutTimer = 0
	self.lastCameraResetPosition = nil
	self.stepRotateTimeout = 0.0
	self.cameraOffsetRotation = 0
	self.cameraOffsetRotationDiscrete = 0
end

function VRCamera:Update(timeDelta)
	local camera = workspace.CurrentCamera
	local newCameraCFrame = camera.CFrame
	local newCameraFocus = camera.Focus

	local player = PlayersService.LocalPlayer
	local humanoid = self:GetHumanoid()
	local cameraSubject = camera.CameraSubject

	if self.lastUpdate == nil or timeDelta > 1 then
		self.lastCameraTransform = nil
	end

	self:StepZoom()
	-- update fullscreen effects
	self:UpdateFadeFromBlack(timeDelta)
	self:UpdateEdgeBlur(player, timeDelta)

	local lastSubjPos = self.lastSubjectPosition
	local subjectPosition: Vector3 = self:GetSubjectPosition()
	-- transition from another camera or from spawn
	if self.needsBlackout then 
		self:StartFadeFromBlack()

		local dt = math.clamp(timeDelta, 0.0001, 0.1)
		self.blackOutTimer += dt
		if self.blackOutTimer > CAMERA_BLACKOUT_TIME and game:IsLoaded() then
			self.needsBlackout = false
			self.needsReset = true
		end
	end

	if subjectPosition and player and camera then
		newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)

		if self:IsInFirstPerson() then
			-- update camera CFrame
			newCameraCFrame, newCameraFocus = self:UpdateFirstPersonTransform(
				timeDelta,newCameraCFrame, newCameraFocus, lastSubjPos, subjectPosition)
		else -- 3rd person
			-- update camera CFrame
			newCameraCFrame, newCameraFocus = self:UpdateThirdPersonTransform(
				timeDelta, newCameraCFrame, newCameraFocus, lastSubjPos, subjectPosition)
		end

		self.lastCameraTransform = newCameraCFrame
		self.lastCameraFocus = newCameraFocus
	end

	self.lastUpdate = tick()
	return newCameraCFrame, newCameraFocus
end

function VRCamera:UpdateFirstPersonTransform(timeDelta, newCameraCFrame, newCameraFocus, lastSubjPos, subjectPosition)
	-- transition from TP to FP
	if self.needsReset then
		self:StartFadeFromBlack()
		self.needsReset = false
		self.stepRotateTimeout = 0.25
		self.VRCameraFocusFrozen = true
		self.cameraOffsetRotation = 0
		self.cameraOffsetRotationDiscrete = 0
	end

	-- blur screen edge during movement
	local player = PlayersService.LocalPlayer
	local subjectDelta = lastSubjPos - subjectPosition
	if subjectDelta.magnitude > 0.01 then
		self:StartVREdgeBlur(player)
	end
	-- straight view, not angled down
	local cameraFocusP = newCameraFocus.p
	local cameraLookVector = self:GetCameraLookVector()
	cameraLookVector = Vector3.new(cameraLookVector.X, 0, cameraLookVector.Z).Unit

	if self.stepRotateTimeout > 0 then
		self.stepRotateTimeout -= timeDelta
	end

	-- step rotate in 1st person
	local rotateInput = CameraInput.getRotation()
	local yawDelta = 0
	if FFlagUserFlagEnableVRUpdate3 and UserGameSettings.VRSmoothRotationEnabled then
		yawDelta = rotateInput.X
	else
		if self.stepRotateTimeout <= 0.0 and math.abs(rotateInput.X) > 0.03 then
			yawDelta = 0.5
			if rotateInput.X < 0 then
				yawDelta = -0.5
			end
			self.needsReset = true
		end
	end

	local newLookVector = self:CalculateNewLookVectorFromArg(cameraLookVector, Vector2.new(yawDelta, 0))
	newCameraCFrame = CFrame.new(cameraFocusP - (FP_ZOOM * newLookVector), cameraFocusP)
	return newCameraCFrame, newCameraFocus
end

function VRCamera:UpdateThirdPersonTransform(timeDelta, newCameraCFrame, newCameraFocus, lastSubjPos, subjectPosition)
	local zoom = self:GetCameraToSubjectDistance()
	if zoom < 0.5 then
		zoom = 0.5
	end

	if lastSubjPos ~= nil and self.lastCameraFocus ~= nil then
		-- compute delta of subject since last update
		local player = PlayersService.LocalPlayer
		local subjectDelta = lastSubjPos - subjectPosition
		local moveVector = require(player:WaitForChild("PlayerScripts").PlayerModule:WaitForChild("ControlModule")):GetMoveVector()

		-- is the subject still moving?
		local isMoving = subjectDelta.magnitude > 0.01 or moveVector.magnitude > 0.01
		if isMoving then
			self.motionDetTime = 0.1
		end

		self.motionDetTime = self.motionDetTime - timeDelta
		if self.motionDetTime > 0 then
			isMoving = true
		end

		if isMoving and not self.needsReset then
			-- if subject moves keep old camera focus
			newCameraFocus = self.lastCameraFocus

			-- if the focus subject stopped, time to reset the camera
			self.VRCameraFocusFrozen = true
		else
			local subjectMoved = self.lastCameraResetPosition == nil or (subjectPosition - self.lastCameraResetPosition).Magnitude > 1

			-- compute offset for 3rd person camera rotation
			local rotateInput = CameraInput.getRotation()
			local userCameraPan = FFlagUserFlagEnableVRUpdate3 and rotateInput ~= Vector2.new()
			local panUpdate = false
			if userCameraPan then
				if rotateInput.X ~= 0 then
					local tempRotation = self.cameraOffsetRotation + rotateInput.X;
					if(tempRotation < -math.pi) then
						tempRotation = math.pi - (tempRotation + math.pi) 
					else
						if (tempRotation > math.pi) then
							tempRotation = -math.pi + (tempRotation - math.pi) 
						end
					end
					self.cameraOffsetRotation = math.clamp(tempRotation, -math.pi, math.pi)
					if UserGameSettings.VRSmoothRotationEnabled then
						self.cameraOffsetRotationDiscrete = self.cameraOffsetRotation

						-- get player facing direction
						local humanoid = self:GetHumanoid()
						local forwardVector = humanoid.Torso and humanoid.Torso.CFrame.lookVector or Vector3.new(1,0,0)
						-- adjust camera height
						local vecToCameraAtHeight = Vector3.new(forwardVector.X, 0, forwardVector.Z)
						local newCameraPos = newCameraFocus.Position - vecToCameraAtHeight * zoom
						-- compute new cframe at height level to subject
						local lookAtPos = Vector3.new(newCameraFocus.Position.X, newCameraPos.Y, newCameraFocus.Position.Z)

						local tempCF = CFrame.new(newCameraPos, lookAtPos)
						tempCF = tempCF * CFrame.fromAxisAngle(Vector3.new(0,1,0), self.cameraOffsetRotationDiscrete)
						newCameraPos = lookAtPos - (tempCF.LookVector * (lookAtPos - newCameraPos).Magnitude)

						newCameraCFrame = CFrame.new(newCameraPos, lookAtPos)
					else
						local tempRotDisc = math.floor(self.cameraOffsetRotation * 12 / 12)
						if tempRotDisc ~= self.cameraOffsetRotationDiscrete then
							self.cameraOffsetRotationDiscrete = tempRotDisc
							panUpdate = true
						end
					end
				end
			end

			-- recenter the camera on teleport
			if (self.VRCameraFocusFrozen and subjectMoved) or self.needsReset or panUpdate then
				if not panUpdate then
					self.cameraOffsetRotationDiscrete = 0
					self.cameraOffsetRotation = 0
				end

				VRService:RecenterUserHeadCFrame()

				self.VRCameraFocusFrozen = false
				self.needsReset = false
				self.lastCameraResetPosition = subjectPosition

				self:ResetZoom()
				self:StartFadeFromBlack()

				-- get player facing direction
				local humanoid = self:GetHumanoid()
				local forwardVector = humanoid.Torso and humanoid.Torso.CFrame.lookVector or Vector3.new(1,0,0)
				-- adjust camera height
				local vecToCameraAtHeight = Vector3.new(forwardVector.X, 0, forwardVector.Z)
				local newCameraPos = newCameraFocus.Position - vecToCameraAtHeight * zoom
				-- compute new cframe at height level to subject
				local lookAtPos = Vector3.new(newCameraFocus.Position.X, newCameraPos.Y, newCameraFocus.Position.Z)

				if FFlagUserFlagEnableVRUpdate3 and self.cameraOffsetRotation ~= 0 then
					local tempCF = CFrame.new(newCameraPos, lookAtPos)
					tempCF = tempCF * CFrame.fromAxisAngle(Vector3.new(0,1,0), self.cameraOffsetRotationDiscrete)
					newCameraPos = lookAtPos - (tempCF.LookVector * (lookAtPos - newCameraPos).Magnitude)
				end

				newCameraCFrame = CFrame.new(newCameraPos, lookAtPos)
			end
		end
	end

	return newCameraCFrame, newCameraFocus
end

function VRCamera:EnterFirstPerson()
	self.inFirstPerson = true
	self:UpdateMouseBehavior()
end

function VRCamera:LeaveFirstPerson()
	self.inFirstPerson = false
	self.needsReset = true
	self:UpdateMouseBehavior()

	if self.VRBlur then
		self.VRBlur.Visible = false
	end
end

return VRCamera