--[[****************************************************************************
  * LibCamera by Saiket                                                        *
  * LibCamera-1.0.lua - Keeps track of the camera's position.                  *
  ****************************************************************************]]


local MAJOR, MINOR = "LibCamera-1.0", 6;

local lib = LibStub:NewLibrary( MAJOR, MINOR );
if ( not lib ) then
	return;
end
lib.Events = lib.Events or LibStub( "CallbackHandler-1.0" ):New( lib, nil, nil, false );
lib.Updater = lib.Updater or CreateFrame( "Frame", nil, WorldFrame );


local Pitch;
local Yaw;
local Distance;

local YawOffset = 0;

local IsCameraMoving = false;

local EventsUsed = 0; -- Number of events registered for by addons


local AngleMinDelta = 1e-5; -- Minimum angle change to trigger an OnChanged event
local PI = math.pi;
local DegreesToRadians = PI / 180;
local EVENT_UPDATE, EVENT_UPDATEDISTANCE = "LibCamera_Update", "LibCamera_UpdateDistance";




--[[****************************************************************************
  * Function: lib.Updater:OnUpdate                                             *
  * Description: Saves the camera position every frame.                        *
  ****************************************************************************]]
do
	local SaveView = SaveView;
	local IsMouselooking = IsMouselooking;
	local GetPlayerFacing = GetPlayerFacing;
	local IsFlying = IsFlying;
	local GetCVar = GetCVar;
	local SetCVar = SetCVar;
	local tonumber = tonumber;
	local abs = abs;
	local Cos = math.cos;

	local Changed, NewPitch, NewYaw, NewDistance;
	local OriginalPitch, OriginalYaw, OriginalDistance;

	function lib.Updater:OnUpdate ( Elapsed )
		Changed = false;

		-- Backup any existing camera data
		OriginalPitch = GetCVar( "cameraPitchE" );
		OriginalYaw = GetCVar( "cameraYawE" );
		OriginalDistance = GetCVar( "cameraDistanceE" );

		-- Cache current camera information
		SaveView( 5 );

		NewYaw = DegreesToRadians * GetCVar( "cameraYawE" ) + YawOffset;
		if ( not ( IsCameraMoving or IsMouselooking() ) ) then -- Camera angle relative to player face
			NewYaw = NewYaw + GetPlayerFacing();
		end

		NewPitch = DegreesToRadians * GetCVar( "cameraPitchE" );
		if ( not IsFlying() ) then -- Upside-down cameras only allowed while flying
			if ( Cos( NewPitch ) < 0 ) then -- Flipped
				NewPitch = PI / 2 - ( NewPitch + PI / 2 ) % PI;
				NewYaw = NewYaw + PI;
			end
		end
		-- Note: A pitch of zero (exactly) indicates an error related to pitchLimit CVar, so disregard
		if ( not Pitch or ( NewPitch ~= 0 and abs( NewPitch - Pitch ) >= AngleMinDelta ) ) then
			Pitch = NewPitch;
			Changed = true;
		end

		NewYaw = NewYaw % ( PI * 2 );
		if ( not Yaw or abs( NewYaw - Yaw ) >= AngleMinDelta ) then
			Yaw = NewYaw;
			Changed = true;
		end

		NewDistance = tonumber( GetCVar( "cameraDistanceE" ) );
		if ( NewDistance ~= Distance ) then
			Distance = NewDistance;
			Changed = true;
			lib:OnChangedDistance();
		end


		-- Restore original camera data
		SetCVar( "cameraPitchE", OriginalPitch );
		SetCVar( "cameraYawE", OriginalYaw );
		SetCVar( "cameraDistanceE", OriginalDistance );


		if ( Changed ) then
			lib:OnChanged();
		end
	end
end


--[[****************************************************************************
  * Function: lib.Events:OnUsed                                                *
  * Description: Begins updating camera positions.                             *
  ****************************************************************************]]
function lib.Events:OnUsed ( Target, Event )
	if ( Event == EVENT_UPDATE or Event == EVENT_UPDATEDISTANCE ) then
		if ( EventsUsed == 0 ) then
			Target.Updater:Show();
			Target.Updater:OnUpdate( math.huge ); -- Force instant update
		end
		EventsUsed = EventsUsed + 1;
	end
end
--[[****************************************************************************
  * Function: lib.Events:OnUnused                                              *
  * Description: Stops updating camera positions.                              *
  ****************************************************************************]]
function lib.Events:OnUnused ( Target, Event )
	if ( Event == EVENT_UPDATE or Event == EVENT_UPDATEDISTANCE ) then
		EventsUsed = EventsUsed - 1;
		if ( EventsUsed == 0 ) then
			Target.Updater:Hide();
			Pitch, Yaw, Distance = nil; -- Clear cache
		end
	end
end


--[[****************************************************************************
  * Function: lib.GetCameraDistance                                            *
  * Description: Returns the camera's distance from the player in yards, or    *
  *   nil if unknown.  At lease one callback must be registered for this       *
  *   function to update.                                                      *
  ****************************************************************************]]
function lib.GetCameraDistance ()
	return Distance;
end
--[[****************************************************************************
  * Function: lib.GetCameraPosition                                            *
  * Description: Returns the camera pitch and yaw in radians and distance from *
  *   the player in yards, or nil if unknown.  At lease one callback must be   *
  *   registered for this function to update.                                  *
  ****************************************************************************]]
function lib.GetCameraPosition ()
	return Pitch, Yaw, lib.GetCameraDistance();
end
--[[****************************************************************************
  * Function: lib:OnChangedDistance                                            *
  * Description: Fired when the player's camera zoom changes.                  *
  ****************************************************************************]]
function lib:OnChangedDistance ()
	self.Events:Fire( EVENT_UPDATEDISTANCE, self.GetCameraDistance() );
end
--[[****************************************************************************
  * Function: lib:OnChanged                                                    *
  * Description: Fired when the player's camera moves.                         *
  ****************************************************************************]]
function lib:OnChanged ()
	self.Events:Fire( EVENT_UPDATE, self.GetCameraPosition() );
end




--------------------------------------------------------------------------------
-- Function Hooks / Execution
-----------------------------

do
	lib.Updater:Hide();
	lib.Updater:SetScript( "OnUpdate", lib.Updater.OnUpdate );

	-- Hook camera movement
	hooksecurefunc( "CameraOrSelectOrMoveStart", function ()
		IsCameraMoving = true;
	end );
	hooksecurefunc( "CameraOrSelectOrMoveStop", function ()
		IsCameraMoving = false;
	end );
	hooksecurefunc( "FlipCameraYaw", function ( Offset )
		YawOffset = ( YawOffset + Offset * DegreesToRadians ) % ( PI * 2 );
	end );
end
