//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "quicktime_video.h"

#include "quicktime_common.h"
#include "quicktime_material.h"
#include "quicktime_recorder.h"


#include "filesystem.h"
#include "tier0/icommandline.h"
#include "tier1/strtools.h"
#include "tier1/utllinkedlist.h"
#include "tier1/KeyValues.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/MaterialSystemUtil.h"
#include "materialsystem/itexture.h"
#include "vtf/vtf.h"
#include "pixelwriter.h"
#include "tier2/tier2.h"
#include "platform.h"


#if defined ( WIN32 )
	#include <WinDef.h>
	#include <../dx9sdk/include/dsound.h>
#endif

#include "tier0/memdbgon.h"


// ===========================================================================
// Singleton to expose Quicktime video subsystem
// ===========================================================================
static CQuickTimeVideoSubSystem g_QuickTimeSystem;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CQuickTimeVideoSubSystem, IVideoSubSystem, VIDEO_SUBSYSTEM_INTERFACE_VERSION, g_QuickTimeSystem );


// ===========================================================================
// Convars used by Quicktime
//  - these need to be referenced somewhere to keep the compiler from 
//    optimizing them away
// ===========================================================================

ConVar QuickTime_EncodeGamma( "video_quicktime_encode_gamma", "3", FCVAR_ARCHIVE , "QuickTime Video Encode Gamma Target- 0=no gamma adjust  1=platform default gamma  2 = gamma 1.8  3 = gamma 2.2  4 = gamma 2.5", true, 0.0f, true, 4.0f );
ConVar QuickTime_PlaybackGamma( "video_quicktime_decode_gamma", "0", FCVAR_ARCHIVE , "QuickTime Video Playback Gamma Target- 0=no gamma adjust  1=platform default gamma  2 = gamma 1.8  3 = gamma 2.2  4 = gamma 2.5", true, 0.0f, true, 4.0f );

// ===========================================================================
// List of file extensions and features supported by this subsystem
// ===========================================================================
VideoFileExtensionInfo_t s_QuickTimeExtensions[] = 
{
	{ ".mov", VideoSystem::QUICKTIME,  VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE },
	{ ".mp4", VideoSystem::QUICKTIME,  VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE },
};

const int s_QuickTimeExtensionCount = ARRAYSIZE( s_QuickTimeExtensions );

const VideoSystemFeature_t	CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET = VideoSystemFeature::FULL_PLAYBACK | VideoSystemFeature::FULL_ENCODE;

#ifdef OSX
PFNGetGWorldPixMap GetGWorldPixMap = NULL;
PFNGetPixBaseAddr GetPixBaseAddr = NULL;
PFNLockPixels LockPixels = NULL;
PFNUnlockPixels UnlockPixels = NULL;
PFNDisposeGWorld DisposeGWorld = NULL;
PFNSetGWorld SetGWorld = NULL;
PFNGetPixRowBytes GetPixRowBytes = NULL;
#endif

// ===========================================================================
// CQuickTimeVideoSubSystem class
// ===========================================================================
CQuickTimeVideoSubSystem::CQuickTimeVideoSubSystem() :
	m_bQuickTimeInitialized( false ),
	m_LastResult( VideoResult::SUCCESS ),
	m_CurrentStatus( VideoSystemStatus::NOT_INITIALIZED ),
	m_AvailableFeatures( CQuickTimeVideoSubSystem::DEFAULT_FEATURE_SET ),
	m_pCommonServices( nullptr )
{

}


CQuickTimeVideoSubSystem::~CQuickTimeVideoSubSystem()
{
	ShutdownQuickTime();		// Super redundant safety check
}


// ===========================================================================
// IAppSystem methods
// ===========================================================================
bool CQuickTimeVideoSubSystem::Connect( CreateInterfaceFn factory )
{
	if ( !BaseClass::Connect( factory ) )
	{
		return false;
	}

	if ( g_pFullFileSystem == nullptr || materials == nullptr ) 
	{
		Msg( "QuickTime video subsystem failed to connect to missing a required system\n" );
		return false;
	}
	return true;
}


void CQuickTimeVideoSubSystem::Disconnect()
{
	BaseClass::Disconnect();
}


void* CQuickTimeVideoSubSystem::QueryInterface( const char *pInterfaceName )
{

	if ( IS_NOT_EMPTY( pInterfaceName ) )
	{
		if ( V_strncmp(	pInterfaceName, VIDEO_SUBSYSTEM_INTERFACE_VERSION, Q_strlen( VIDEO_SUBSYSTEM_INTERFACE_VERSION ) + 1) == STRINGS_MATCH )
		{
			return (IVideoSubSystem*) this;
		}
	}

	return nullptr;
}


InitReturnVal_t CQuickTimeVideoSubSystem::Init()
{
	InitReturnVal_t nRetVal = BaseClass::Init();
	if ( nRetVal != INIT_OK )
	{
		return nRetVal;
	}

	return INIT_OK;

}


void CQuickTimeVideoSubSystem::Shutdown()
{
	// Make sure we shut down quicktime
	ShutdownQuickTime();
	
	BaseClass::Shutdown();
}


// ===========================================================================
// IVideoSubSystem identification methods  
// ===========================================================================
VideoSystem_t CQuickTimeVideoSubSystem::GetSystemID()
{
	return VideoSystem::QUICKTIME;
}


VideoSystemStatus_t CQuickTimeVideoSubSystem::GetSystemStatus()
{
	return m_CurrentStatus;
}


VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFeatures()
{
	return m_AvailableFeatures;
}


const char* CQuickTimeVideoSubSystem::GetVideoSystemName()
{
	return "Quicktime";
}


// ===========================================================================
// IVideoSubSystem setup and shutdown services
// ===========================================================================
bool CQuickTimeVideoSubSystem::InitializeVideoSystem( IVideoCommonServices *pCommonServices )
{
	m_AvailableFeatures = DEFAULT_FEATURE_SET;			// Put here because of issue with static const int, binary OR and DEBUG builds
	
	AssertPtr( pCommonServices );
	m_pCommonServices = pCommonServices;
	
#ifdef OSX
	if ( !GetGWorldPixMap )
		GetGWorldPixMap = (PFNGetGWorldPixMap)dlsym( RTLD_DEFAULT, "GetGWorldPixMap" );
	if ( !GetPixBaseAddr )
		GetPixBaseAddr = (PFNGetPixBaseAddr)dlsym( RTLD_DEFAULT, "GetPixBaseAddr" );
	if ( !LockPixels )
		LockPixels = (PFNLockPixels)dlsym( RTLD_DEFAULT, "LockPixels" );
	if ( !UnlockPixels )
		UnlockPixels = (PFNUnlockPixels)dlsym( RTLD_DEFAULT, "UnlockPixels" );
	if ( !DisposeGWorld )
		DisposeGWorld = (PFNDisposeGWorld)dlsym( RTLD_DEFAULT, "DisposeGWorld" );
	if ( !SetGWorld )
		SetGWorld = (PFNSetGWorld)dlsym( RTLD_DEFAULT, "SetGWorld" );
	if ( !GetPixRowBytes )
		GetPixRowBytes = (PFNGetPixRowBytes)dlsym( RTLD_DEFAULT, "GetPixRowBytes" );
	if ( !GetGWorldPixMap || !GetPixBaseAddr || !LockPixels || !UnlockPixels || !DisposeGWorld || !SetGWorld || !GetPixRowBytes )
		return false;
#endif

	return ( m_bQuickTimeInitialized ) ? true : SetupQuickTime();
}


bool CQuickTimeVideoSubSystem::ShutdownVideoSystem()
{
	return (  m_bQuickTimeInitialized ) ? ShutdownQuickTime() : true;
}


VideoResult_t CQuickTimeVideoSubSystem::VideoSoundDeviceCMD( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData )
{
	switch ( operation ) 
	{
		case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE:
		{
			return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
		}
		
		case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE:
		case VideoSoundDeviceOperation::HOOK_X_AUDIO:
		{
			return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
		}
		
		default:
		{
			return SetResult( VideoResult::UNKNOWN_OPERATION );
		}
	}
}


// ===========================================================================
// IVideoSubSystem supported extensions & features
// ===========================================================================
int CQuickTimeVideoSubSystem::GetSupportedFileExtensionCount()
{
	return s_QuickTimeExtensionCount;
}

 
const char* CQuickTimeVideoSubSystem::GetSupportedFileExtension( int num )
{
	return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? nullptr : s_QuickTimeExtensions[num].m_FileExtension;
}

 
VideoSystemFeature_t CQuickTimeVideoSubSystem::GetSupportedFileExtensionFeatures( int num )
{
	 return ( num < 0 || num >= s_QuickTimeExtensionCount ) ? VideoSystemFeature::NO_FEATURES : s_QuickTimeExtensions[num].m_VideoFeatures;
}


// ===========================================================================
// IVideoSubSystem Video Playback and Recording Services
// ===========================================================================
VideoResult_t CQuickTimeVideoSubSystem::PlayVideoFileFullScreen( const char *filename, void *mainWindow, int windowWidth, int windowHeight, int desktopWidth, int desktopHeight, bool windowed, float forcedMinTime, VideoPlaybackFlags_t playbackFlags )
{
	OSErr status;

	// See if the caller is asking for a feature we can not support....
	VideoPlaybackFlags_t unsupportedFeatures = VideoPlaybackFlags::PRELOAD_VIDEO;

	if ( playbackFlags & unsupportedFeatures )
	{
		return SetResult( VideoResult::FEATURE_NOT_AVAILABLE );
	}

	// Make sure we are initialized and ready
	if ( !m_bQuickTimeInitialized )
	{
		SetupQuickTime();
	}
	
	AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );

	// Set graphics port 
#if defined ( WIN32 )
	SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil ); 
#elif defined ( OSX )
	SystemUIMode oldMode;
	SystemUIOptions oldOptions;
	GetSystemUIMode( &oldMode, &oldOptions );

	if ( !windowed )
	{
		status = SetSystemUIMode( kUIModeAllHidden, (SystemUIOptions) 0 );
		Assert( status == noErr );
	}
	SetGWorld( nil, nil );
#endif

	// -------------------------------------------------
	// Open the quicktime file with audio
	Movie				theQTMovie = NULL;
	Rect				theQTMovieRect;
	QTAudioContextRef	theAudioContext = NULL;

	Handle	MovieFileDataRef = nullptr;
	OSType	MovieFileDataRefType = 0;

	CFStringRef	imageStrRef = CFStringCreateWithCString ( NULL,  filename, 0 ); 
	AssertExitV( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) );
	
	status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType );
	AssertExitV( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) );

	CFRelease( imageStrRef );

    status = NewMovieFromDataRef( &theQTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType );
	SAFE_DISPOSE_HANDLE( MovieFileDataRef );
	
    if ( status != noErr )
    {
#if defined ( OSX )
		SetSystemUIMode( oldMode, oldOptions );
#endif
		Assert( false );
		return SetResult( VideoResult::FILE_ERROR_OCCURED );
    }
     
	// Get info about the video
	GetMovieNaturalBoundsRect(theQTMovie, &theQTMovieRect);

	TimeValue theQTMovieDuration = GetMovieDuration( theQTMovie );

	// what size do we set the output rect to?
	// Integral scaling is much faster, so always scale the video as such
	int	nNewWidth  = (int) theQTMovieRect.right;
	int nNewHeight = (int) theQTMovieRect.bottom;

	// Determine the window we are rendering video into
	int displayWidth  = windowWidth;
	int displayHeight = windowHeight;

	// on mac OSX, if we are fullscreen, quicktime is bypassing our targets and going to the display directly, so use its dimensions
	if ( IsOSX() && windowed == false )
	{
		displayWidth = desktopWidth;
		displayHeight = desktopHeight;
	}

	// get the size of the target video output
	int	nBufferWidth = nNewWidth;
	int nBufferHeight = nNewHeight;
	
	int displayXOffset = 0;
	int displayYOffset = 0;

	if ( !m_pCommonServices->CalculateVideoDimensions( nNewWidth, nNewHeight, displayWidth, displayHeight, playbackFlags, &nBufferWidth, &nBufferHeight, &displayXOffset, &displayYOffset ) )
	{
#if defined ( OSX )
		SetSystemUIMode( oldMode, oldOptions );
#endif
		return SetResult( VideoResult::VIDEO_ERROR_OCCURED );
	}

	theQTMovieRect.left = (short) displayXOffset;
	theQTMovieRect.right = (short) ( displayXOffset + nBufferWidth );
	theQTMovieRect.top = (short) displayYOffset;
	theQTMovieRect.bottom = (short) ( displayYOffset + nBufferHeight );

	SetMovieBox( theQTMovie, &theQTMovieRect );

	// Check to see if we should include audio playback
	bool enableMovieAudio = !BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::NO_AUDIO );

	if ( !CreateMovieAudioContext( enableMovieAudio, theQTMovie, &theAudioContext, true) )
	{
#if defined ( OSX )
		SetSystemUIMode( oldMode, oldOptions );
#endif
		return SetResult( VideoResult::AUDIO_ERROR_OCCURED );
	}

	// need to get the graphics port associated with the main window
#if defined( WIN32 )    
	CreatePortAssociation( mainWindow, NULL, 0 );
	GrafPtr theGrafPtr = GetNativeWindowPort( mainWindow );
#elif defined( OSX )
	GrafPtr theGrafPtr = GetWindowPort( (OpaqueWindowPtr*)mainWindow );
#endif

	// Setup the playback gamma according to the convar
	SetGWorldDecodeGamma( (CGrafPtr) theGrafPtr, VideoPlaybackGamma::USE_GAMMA_CONVAR );

	// Assign the GWorld to this movie
	SetMovieGWorld( theQTMovie, (CGrafPtr) theGrafPtr, NULL );
	
	// Setup the keyboard and message handler for fullscreen playback
	if ( SetResult( m_pCommonServices->InitFullScreenPlaybackInputHandler( playbackFlags, forcedMinTime, windowed ) ) != VideoResult::SUCCESS )
	{
#if defined ( OSX )
		SetSystemUIMode( oldMode, oldOptions );
#endif
		return GetLastResult();
	}

	// Other Movie playback	state init
	bool bPaused = false;

	// Init Movie info
    TimeRecord  movieStartTime;
	TimeRecord	moviePauseTime;
    GoToBeginningOfMovie( theQTMovie );
    GetMovieTime( theQTMovie, &movieStartTime );

	// Start movie playback 
	StartMovie( theQTMovie );

	// loop while movie is playing
	while ( true )
	{
		bool bAbortEvent, bPauseEvent, bQuitEvent;
	
		if ( m_pCommonServices->ProcessFullScreenInput( bAbortEvent, bPauseEvent, bQuitEvent ) )
		{
			// check for aborting the movie
			if ( bAbortEvent || bQuitEvent )
			{
				goto abort_playback;
			}
	
			// check for pausing the movie?
			if ( bPauseEvent )
			{
				if ( bPaused == false )		// pausing the movie?
				{
					GetMovieTime( theQTMovie, &moviePauseTime );
					StopMovie( theQTMovie );
					bPaused = true;
				}
				else	// unpause the movie
				{
					SetMovieTime( theQTMovie, &moviePauseTime );
					StartMovie( theQTMovie );
					bPaused = false;
				}
			}
		}
												  
		// hit the end of the movie?
		TimeValue now = GetMovieTime( theQTMovie, nullptr );
		if ( now >= theQTMovieDuration )
		{
			// Loop this movie until aborted?
			if ( playbackFlags & BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::LOOP_VIDEO ) )
			{
				Assert( ANY_BITFLAGS_SET( playbackFlags, VideoPlaybackFlags::ABORT_ON_ESC | VideoPlaybackFlags::ABORT_ON_RETURN | VideoPlaybackFlags::ABORT_ON_SPACE | VideoPlaybackFlags::ABORT_ON_ANY_KEY ) );			// Movie will loop forever otherwise
				StopMovie( theQTMovie );
				SetMovieTime( theQTMovie, &movieStartTime );
				StartMovie( theQTMovie );
			}
			else
			{
				break;		// finished actually, exit loop
			}
		}
		
		// if the movie is paused, sleep for 5ms to keeps the CPU from spinning so hard
		if ( bPaused )
		{
			ThreadSleep( 1 );
		}
		else
		{
			// Keep the movie running....
			MoviesTask( theQTMovie, 0L );
		}

	}

	// Close it all down
abort_playback:	
	StopMovie( theQTMovie );

	SAFE_RELEASE_AUDIOCONTEXT( theAudioContext );
	SAFE_DISPOSE_MOVIE( theQTMovie );

	m_pCommonServices->TerminateFullScreenPlaybackInputHandler();

#if defined ( OSX )
	SetSystemUIMode( oldMode, oldOptions );
#endif

	return SetResult( VideoResult::SUCCESS );	
}


// ===========================================================================
// IVideoSubSystem Video Material Services
//   note that the filename is absolute and has already resolved any paths
// ===========================================================================
IVideoMaterial* CQuickTimeVideoSubSystem::CreateVideoMaterial( const char *pMaterialName, const char *pVideoFileName, VideoPlaybackFlags_t flags )
{
	SetResult( VideoResult::BAD_INPUT_PARAMETERS );
	AssertExitN( m_CurrentStatus == VideoSystemStatus::OK && IS_NOT_EMPTY( pMaterialName ) || IS_NOT_EMPTY( pVideoFileName ) );

	CQuickTimeMaterial	*pVideoMaterial = new CQuickTimeMaterial();
	if ( pVideoMaterial == nullptr || pVideoMaterial->Init( pMaterialName, pVideoFileName, flags ) == false )
	{
		SAFE_DELETE( pVideoMaterial );
		SetResult( VideoResult::VIDEO_ERROR_OCCURED );
		return nullptr;
	}

	IVideoMaterial	*pInterface = (IVideoMaterial*) pVideoMaterial;
	m_MaterialList.AddToTail( pInterface );
	
	SetResult( VideoResult::SUCCESS );
	return pInterface;
}


VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoMaterial( IVideoMaterial *pVideoMaterial )
{
	AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
	AssertPtrExitV( pVideoMaterial, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );
	
	if ( m_MaterialList.Find( pVideoMaterial ) != -1 )
	{
		CQuickTimeMaterial *pObject = (CQuickTimeMaterial*) pVideoMaterial;
		pObject->Shutdown();
		delete pObject;
		
		m_MaterialList.FindAndFastRemove( pVideoMaterial );
		
		return SetResult( VideoResult::SUCCESS );
	}
	
	return SetResult (VideoResult::MATERIAL_NOT_FOUND );

}


// ===========================================================================
// IVideoSubSystem Video Recorder Services
// ===========================================================================
IVideoRecorder* CQuickTimeVideoSubSystem::CreateVideoRecorder()
{
	SetResult( VideoResult::SYSTEM_NOT_AVAILABLE );
	AssertExitN( m_CurrentStatus == VideoSystemStatus::OK );

	CQuickTimeVideoRecorder *pVideoRecorder = new CQuickTimeVideoRecorder();
	
	if ( pVideoRecorder != nullptr )
	{
		IVideoRecorder *pInterface = (IVideoRecorder*) pVideoRecorder;
		m_RecorderList.AddToTail( pInterface );
		
		SetResult( VideoResult::SUCCESS );
		return pInterface;
	}

	SetResult( VideoResult::VIDEO_ERROR_OCCURED );
	return nullptr;
}


VideoResult_t CQuickTimeVideoSubSystem::DestroyVideoRecorder( IVideoRecorder *pRecorder )
{
	AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
	AssertPtrExitV( pRecorder, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );

	if ( m_RecorderList.Find( pRecorder ) != -1 )
	{
		CQuickTimeVideoRecorder *pVideoRecorder = (CQuickTimeVideoRecorder*) pRecorder;
		delete pVideoRecorder;
		
		m_RecorderList.FindAndFastRemove( pRecorder );
		
		return SetResult( VideoResult::SUCCESS );
	}
	
	return SetResult( VideoResult::RECORDER_NOT_FOUND );
}

VideoResult_t CQuickTimeVideoSubSystem::CheckCodecAvailability( VideoEncodeCodec_t codec )
{
	AssertExitV( m_CurrentStatus == VideoSystemStatus::OK, SetResult( VideoResult::SYSTEM_NOT_AVAILABLE ) );
	AssertExitV( codec >= VideoEncodeCodec::DEFAULT_CODEC && codec < VideoEncodeCodec::CODEC_COUNT, SetResult( VideoResult::BAD_INPUT_PARAMETERS ) );

	// map the requested codec in 
	CodecType	theCodec;
	switch( codec )
	{
		case VideoEncodeCodec::MPEG2_CODEC:
		{
			theCodec = kMpegYUV420CodecType;
			break;
		}
		case VideoEncodeCodec::MPEG4_CODEC:
		{
			theCodec = kMPEG4VisualCodecType;
			break;
		}
		case VideoEncodeCodec::H261_CODEC:
		{
			theCodec = kH261CodecType;
			break;
		}
		case VideoEncodeCodec::H263_CODEC:
		{
			theCodec = kH263CodecType;
			break;
		}
		case VideoEncodeCodec::H264_CODEC:
		{
			theCodec = kH264CodecType;
			break;
		}
		case VideoEncodeCodec::MJPEG_A_CODEC:
		{
			theCodec = kMotionJPEGACodecType;
			break;
		}
		case VideoEncodeCodec::MJPEG_B_CODEC:
		{
			theCodec = kMotionJPEGBCodecType;
			break;
		}
		case VideoEncodeCodec::SORENSON3_CODEC:
		{
			theCodec = kSorenson3CodecType;
			break;
		}
		case VideoEncodeCodec::CINEPACK_CODEC:
		{
			theCodec = kCinepakCodecType;
			break;
		}
		default:										// should never hit this because we are already range checked
		{
			theCodec = CQTVideoFileComposer::DEFAULT_CODEC;
			break;
		}
	}

	// Determine if codec is available...
	CodecInfo	theInfo;
	
	OSErr status = GetCodecInfo( &theInfo, theCodec, 0 );
	if ( status == noCodecErr )
	{
		return SetResult( VideoResult::CODEC_NOT_AVAILABLE );;
	}
	AssertExitV( status == noErr, SetResult( VideoResult::CODEC_NOT_AVAILABLE ) );

	return SetResult( VideoResult::SUCCESS );
}


// ===========================================================================
// Status support
// ===========================================================================
VideoResult_t CQuickTimeVideoSubSystem::GetLastResult()
{
	return m_LastResult;
}


VideoResult_t CQuickTimeVideoSubSystem::SetResult( VideoResult_t status )
{
	m_LastResult = status;
	return status;
}


// ===========================================================================
// Quicktime Initialization & Shutdown
// ===========================================================================
bool CQuickTimeVideoSubSystem::SetupQuickTime()
{
	SetResult( VideoResult::INITIALIZATION_ERROR_OCCURED);
	AssertExitF( m_bQuickTimeInitialized == false );

	// This is set early to indicate we have already been through here, even if we error out for some reason
    m_bQuickTimeInitialized = true;
	m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
	m_AvailableFeatures = VideoSystemFeature::NO_FEATURES;
    
	if ( CommandLine()->FindParm( "-noquicktime" ) )
	{
		// Don't even try. leave status as NOT_INITIALIZED
		return true;
	}

  // Windows PC build 
#if defined ( WIN32 )
	OSErr status = InitializeQTML( 0 ); 
    
    // if -2903 (qtmlDllLoadErr) then quicktime not installed on this system
    if ( status != noErr )
    {
		if  ( status == qtmlDllLoadErr )
		{
			m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
			return true;
		}
		
		Msg( "Unknown QuickTime Initialization Error = %d \n", (int) status );
		Assert( false );
		
		m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
		return true;
    }
    
	// Make sure we have version 7.04 or greater of quicktime 
    long version = 0;
    status = Gestalt( gestaltQuickTime, &version );
    if ( ( status != noErr ) || ( version < 0x07608000 ) )
    {
		TerminateQTML();
		m_CurrentStatus = VideoSystemStatus::NOT_CURRENT_VERSION;
		Msg( "QuickTime Version reports to be ver= %8.8x, less than 7.6 (07608000) required\n", version );
		Assert( false );
		
        return true;
    }
    
#endif    
  
	// Windows PC, or Mac OSX build
	OSErr status2 = EnterMovies();           // Initialize QuickTime Movie Toolbox
	if ( status2 != noErr )
	{
		// Windows PC -- shutdown Quicktime
#if defined ( WIN32 )	
		TerminateQTML();
#endif
		
		Msg( "Quicktime Error when attempting to EnterMovies, err = %d \n", (int) status2 );
		Assert( false );
		
		m_CurrentStatus = VideoSystemStatus::INITIALIZATION_ERROR;
		return true;
	}
    
	m_CurrentStatus	 = VideoSystemStatus::OK;
	m_AvailableFeatures = DEFAULT_FEATURE_SET;

	// if the Library load didn't hook up the compression functions, remove them from our feature list
#pragma warning ( disable : 4551 )
	
    if ( !CompressImage )
    {
		m_AvailableFeatures = m_AvailableFeatures & ~( VideoSystemFeature::ENCODE_VIDEO_TO_FILE | VideoSystemFeature::ENCODE_AUDIO_TO_FILE );
    }
#pragma warning ( default : 4551 )

	// Note that we are now open for business....	
	m_bQuickTimeInitialized = true;
	SetResult( VideoResult::SUCCESS );
    
	return true;
}


bool CQuickTimeVideoSubSystem::ShutdownQuickTime()
{
	if ( m_bQuickTimeInitialized && m_CurrentStatus == VideoSystemStatus::OK )
	{
		ExitMovies();                               // Terminate QuickTime  

		// Windows PC only shutdown			
#if defined ( WIN32 )
		  TerminateQTML();                          // Terminate QTML  
#endif
	}
				
	m_bQuickTimeInitialized = false;
	m_CurrentStatus = VideoSystemStatus::NOT_INITIALIZED;
	m_AvailableFeatures = VideoSystemFeature::NO_FEATURES;
	SetResult( VideoResult::SUCCESS );

	return true;
}


// ===========================================================================
// Functions defined in Quicktime Common
// ===========================================================================

// makes a copy of a string
char *COPY_STRING( const char *pString )
{
	if ( pString == nullptr )
	{
		return nullptr;
	}

	size_t strLen = V_strlen( pString );
	
	char *pNewStr = new char[ strLen+ 1 ];
	if ( strLen > 0 )
	{
		V_memcpy( pNewStr, pString, strLen );
	}
	pNewStr[strLen] = nullchar;

	return pNewStr;
}




//-----------------------------------------------------------------------------
// Adapted from QuickTime Frame Rate code from Apple OSX Reference Library
// found at http://developer.apple.com/library/mac/#qa/qa2001/qa1262.html
//-----------------------------------------------------------------------------

#define   kCharacteristicHasVideoFrameRate  FOUR_CHAR_CODE('vfrr')
#define   kCharacteristicIsAnMpegTrack		FOUR_CHAR_CODE('mpeg')

bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals );

//-----------------------------------------------------------------------------
bool MovieGetStaticFrameRate( Movie inMovie, VideoFrameRate_t &theFrameRate )
{
	theFrameRate.Clear();

	AssertExitF( inMovie != nullptr );

	Boolean			isMPEG = false;
	Media			movieMedia = nullptr;
	MediaHandler	movieMediaHandler = nullptr;
	bool			success = false;


	// get the media identifier for the media that contains the first
	//  video track's sample data, and also get the media handler for this media.

	Track videoTrack = GetMovieIndTrackType( inMovie, 1, kCharacteristicHasVideoFrameRate, movieTrackCharacteristic | movieTrackEnabledOnly ); 	// get first video track 
	if ( videoTrack == nullptr || GetMoviesError() != noErr )
		goto error_out;
			
	// get media ref. for track's sample data 
	movieMedia = GetTrackMedia( videoTrack );
	if ( movieMedia == nullptr || GetMoviesError() != noErr )
		goto error_out;
		
	// get a reference to the media handler component
	movieMediaHandler = GetMediaHandler( movieMedia );
	if ( movieMediaHandler == nullptr || GetMoviesError() != noErr )
		goto error_out;

	// is this the MPEG-1/MPEG-2 media handler?
	if ( MediaHasCharacteristic( movieMediaHandler, kCharacteristicIsAnMpegTrack, &isMPEG ) != noErr )
		goto error_out;
		
	if (isMPEG)	// working with MPEG-1/MPEG-2 media
	{
		MHInfoEncodedFrameRateRecord encodedFrameRate;
		Size encodedFrameRateSize = sizeof( encodedFrameRate );

		// get the static frame rate
		if ( MediaGetPublicInfo( movieMediaHandler, kMHInfoEncodedFrameRate, &encodedFrameRate, &encodedFrameRateSize ) != noErr )
			goto error_out;

		TimeScale 	MovieTimeScale = GetMovieTimeScale( inMovie );
		
		Assert( MovieTimeScale > 0 && encodedFrameRate.encodedFrameRate > 0 );
		
		theFrameRate.SetRaw( MovieTimeScale, int ( (double) MovieTimeScale / Fix2X( encodedFrameRate.encodedFrameRate ) + 0.5 ) );
	}
	else  // working with non-MPEG-1/MPEG-2 media
	{
		if ( !MediaGetStaticFrameRate( movieMedia, theFrameRate, true ) )
			goto error_out;
	}
		
	
	success = true;
	
error_out:

	return success;
}



// ============================================================================
// Given a reference to the media that contains the sample data for a track,
// calculate the static frame rate.
// ============================================================================
bool MediaGetStaticFrameRate( Media inMovieMedia, VideoFrameRate_t &theFrameRate, bool AssumeConstantIntervals )
{
	Assert( inMovieMedia != nullptr );
	
	theFrameRate.Clear();

	// Method #1 - from Apple 
	if ( !AssumeConstantIntervals )	
	{
		// get the number of samples in the media
		long sampleCount = GetMediaSampleCount( inMovieMedia );
		if ( GetMoviesError() != noErr || sampleCount == 0 )
			return false;

		// find the media duration
		TimeValue64 duration = GetMediaDisplayDuration( inMovieMedia );
		if ( GetMoviesError() != noErr || duration == 0 )
			return false;

		// get the media time scale
		TimeValue64 timeScale = GetMediaTimeScale( inMovieMedia );
		if ( GetMoviesError() != noErr || timeScale == 0 )
			return false;

		// calculate the frame rate: = (sample count * media time scale) / media duration
		float FPS = (double) sampleCount * (double) timeScale / (double) duration;

		theFrameRate.SetFPS( FPS );
		return true;
	}
	
	// FPS rate method #2 - assumes all frames are at a constant interval
	// gets FPS in terms of units per second (preferred)
		
	TimeValue64  sample_time     = 0;
	TimeValue64  sample_duration = -1;
	
	GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, (TimeValue64) 0 , fixed1, &sample_time, &sample_duration );
	if ( sample_time == -1 || sample_duration == 0 || GetMoviesError() != noErr )
		return false;

	TimeValue64  sample_time2     = 0;
	TimeValue64  sample_duration2 = -1;

	GetMediaNextInterestingDisplayTime( inMovieMedia, nextTimeMediaSample | nextTimeEdgeOK, sample_time + sample_duration , fixed1, &sample_time2, &sample_duration2 );
	if ( sample_time2 == -1 || sample_duration2 == 0 || GetMoviesError() != noErr )
		return false;

	TimeScale	MediaTimeScale = GetMediaTimeScale( inMovieMedia );
	if ( MediaTimeScale <= 0 || GetMoviesError() != noErr )
		return false;

	Assert( sample_time2 == sample_time + sample_duration );
	Assert( sample_duration == sample_duration2 );

	theFrameRate.SetRaw( MediaTimeScale, (int) sample_duration );

	return true;
}



// ============================================================================
// SetGWorldDecodeGamma - configure a GWorld to perform any needed gamma 
//  correction
// 
//   kQTUsePlatformDefaultGammaLevel = 0,  /* When decompressing into this PixMap, gamma-correct to the platform's standard gamma. */
//   kQTUseSourceGammaLevel        = -1L,  /* When decompressing into this PixMap, don't perform gamma-correction. */
//   kQTCCIR601VideoGammaLevel     = 0x00023333 /* 2.2, standard television video gamma.*/
//   Fixed cGamma1_8 = 0x0001CCCC;		// Gamma 1.8
//   Fixed cGamma2_5 = 0x00028000;      // Gamma 2.5
// ============================================================================

bool SetGWorldDecodeGamma( CGrafPtr theGWorld, VideoPlaybackGamma_t gamma )
{
	AssertExitF( theGWorld != nullptr );
	AssertIncRange( gamma, VideoPlaybackGamma::USE_GAMMA_CONVAR, VideoPlaybackGamma::GAMMA_2_5 );

	Fixed decodeGamma = kQTUseSourceGammaLevel;

	if ( gamma == VideoPlaybackGamma::USE_GAMMA_CONVAR )
	{
		int useGamma = QuickTime_PlaybackGamma.GetInt();
		
		if ( useGamma < (int) VideoPlaybackGamma::NO_GAMMA_ADJUST || useGamma >= VideoPlaybackGamma::GAMMA_COUNT )
			return false;
			
		gamma = (VideoPlaybackGamma_t) useGamma;
	}
	
	switch( gamma )
	{
		case VideoPlaybackGamma::NO_GAMMA_ADJUST:
		{
			decodeGamma = kQTUseSourceGammaLevel;
			break;
		}
		case VideoPlaybackGamma::PLATFORM_DEFAULT_GAMMMA:
		{
			decodeGamma = kQTUsePlatformDefaultGammaLevel;
			break;
		}
		case VideoPlaybackGamma::GAMMA_1_8:
		{
			decodeGamma = 0x0001CCCC;		// Gamma 1.8
			break;
		}
		
		case VideoPlaybackGamma::GAMMA_2_2:
		{
			decodeGamma = 0x00023333;		// Gamma 2.2
			break;
		}
		case VideoPlaybackGamma::GAMMA_2_5:
		{
			decodeGamma = 0x00028000;      // Gamma 2.5
			break;
		}
		default:
		{
			Assert( false );
			break;
		}
	}

	// Get the pix map for the GWorld and adjust the gamma correction on it
	PixMapHandle thePixMap = GetGWorldPixMap( theGWorld );
	AssertExitF( thePixMap != nullptr );

	// Set the Gamma level for the pixmap
	OSErr Status = QTSetPixMapHandleGammaLevel( thePixMap, decodeGamma );
	AssertExitF( Status == noErr );

	// Set the Requested Gamma level for the pixmap
	Status = QTSetPixMapHandleRequestedGammaLevel( thePixMap, decodeGamma );
	AssertExitF( Status == noErr );

	return true;
}


// ============================================================================
// Setup a quicktime Audio Context for a movie
// ============================================================================
bool CreateMovieAudioContext( bool enableAudio, Movie inMovie, QTAudioContextRef *pAudioContext, bool setVolume, float *pCurrentVolume )
{
	AssertExitF( inMovie != nullptr && pAudioContext != nullptr );

	if ( enableAudio )
	{
#if defined ( WIN32 )
		WCHAR strGUID[39];
		int numBytes = StringFromGUID2( DSDEVID_DefaultPlayback, (LPOLESTR) strGUID, 39);			// CLSID_DirectSound is not what you want here

		// create the audio context
		CFStringRef deviceNameStrRef = CFStringCreateWithCharacters(kCFAllocatorDefault, (const UniChar*) strGUID, (CFIndex) (numBytes -1) );

		OSStatus result = QTAudioContextCreateForAudioDevice( NULL, deviceNameStrRef, NULL, pAudioContext );
		AssertExitF( result == noErr );
		
		SAFE_RELEASE_CFREF( deviceNameStrRef );
		
#elif defined ( OSX )
	
	    OSStatus result = QTAudioContextCreateForAudioDevice( NULL, NULL, NULL, pAudioContext );
		AssertExitF( result == noErr );
#endif
		// valid?
		AssertPtr( *pAudioContext );
	}
	else	// no audio
	{
		*pAudioContext = nullptr;
	}
	
	// Set the audio context
    OSStatus result = SetMovieAudioContext( inMovie, *pAudioContext );
    AssertExitF( result == noErr );
	
	if ( setVolume && *pAudioContext != nullptr )
	{
		ConVarRef volumeConVar( "volume" );
		float sysVolume = ( volumeConVar.IsValid() ) ? volumeConVar.GetFloat() : 1.0f;
		clamp( sysVolume, 0.0f, 1.0f );
		
		if ( pCurrentVolume != nullptr )
		{
			*pCurrentVolume = sysVolume;
		}
	
		short  movieVolume = (short) ( sysVolume * 256.0f );
	
		SetMovieVolume( inMovie, movieVolume );
	}

	return true;
}