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


#include "filesystem.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 "tier3/tier3.h"
#include "platform.h"
#include "bink_material.h"
#include "tier0/memdbgon.h"

extern "C" {
#include "yuv_rgb.h"
}


// 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;
}

int open_codec_context(int *stream_idx, AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{
	int ret, stream_index;
	AVStream *st;
	const AVCodec *dec = NULL;

	ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
	if (ret < 0)
	{
		Warning("Could not find %s stream\n",
				av_get_media_type_string(type));
		return ret;
	}
	else
	{
		stream_index = ret;
		st = fmt_ctx->streams[stream_index];

		/* find decoder for the stream */
		dec = avcodec_find_decoder(st->codecpar->codec_id);
		if (!dec)
		{
			Warning("Failed to find %s codec\n",
					av_get_media_type_string(type));
			return AVERROR(EINVAL);
		}

		/* Allocate a codec context for the decoder */
		*dec_ctx = avcodec_alloc_context3(dec);
		if (!*dec_ctx)
		{
			Warning("Failed to allocate the %s codec context\n",
					av_get_media_type_string(type));
			return AVERROR(ENOMEM);
		}

		/* Copy codec parameters from input stream to output codec context */
		if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0)
		{
			Warning("Failed to copy %s codec parameters to decoder context\n",
					av_get_media_type_string(type));
			return ret;
		}

		/* Init the decoders */
		if ((ret = avcodec_open2(*dec_ctx, dec, NULL)) < 0)
		{
			Warning("Failed to open %s codec\n",
					av_get_media_type_string(type));
			return ret;
		}

		*stream_idx = stream_index;
	}

	return 0;
}

// ===========================================================================
// CBinkMaterialRGBTextureRegenerator - Inherited from ITextureRegenerator
//	Copies and converts the buffer bits to texture bits
//	Currently only supports 32-bit BGR
// ===========================================================================
CBinkMaterialRGBTextureRegenerator::CBinkMaterialRGBTextureRegenerator() :
	m_nSourceWidth( 0 ),
	m_nSourceHeight( 0 )
{
}


CBinkMaterialRGBTextureRegenerator::~CBinkMaterialRGBTextureRegenerator() 
{
	// nothing to do
}

void CBinkMaterialRGBTextureRegenerator::SetSourceImage( uint8_t *SrcImage, int nWidth, int nHeight )
{
	m_SrcImage = SrcImage;
	m_nSourceWidth	= nWidth;
	m_nSourceHeight = nHeight;
}

void CBinkMaterialRGBTextureRegenerator::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
{
	AssertExit( pVTFTexture != nullptr );

	// Error condition, should only have 1 frame, 1 face, 1 mip level
	if ( ( pVTFTexture->FrameCount() > 1 ) || ( pVTFTexture->FaceCount() > 1 ) || ( pVTFTexture->MipCount() > 1 ) || ( pVTFTexture->Depth() > 1 ) )
	{
		WarningAssert( "Texture Properties Incorrect ");
		memset( pVTFTexture->ImageData(), 0xAA, pVTFTexture->ComputeTotalSize() );
		return;
	}

	// Make sure we have a valid video image source	
/*	if ( m_SrcGWorld == nullptr )
	{
		WarningAssert( "Video texture source not set" );
		memset( pVTFTexture->ImageData(), 0xCC, pVTFTexture->ComputeTotalSize() );
		return;
	}*/

	// Verify the destination texture is set up correctly
	Assert( pVTFTexture->Format() == IMAGE_FORMAT_RGB888 );
	Assert( pVTFTexture->RowSizeInBytes( 0 ) >= pVTFTexture->Width() * 4 );
	Assert( pVTFTexture->Width() >= m_nSourceWidth );
	Assert( pVTFTexture->Height() >= m_nSourceHeight );

	// Copy directly from the Quicktime GWorld
	BYTE   *pImageData	= pVTFTexture->ImageData();
	int dstStride = pVTFTexture->RowSizeInBytes( 0 );

	BYTE *pSrcData = m_SrcImage;

	for (int y = 0; y < m_nSourceHeight; y++ )
	{
		memcpy( pImageData, pSrcData, m_nSourceWidth*3 );

		pImageData += dstStride;
		pSrcData += m_nSourceWidth*3;
	}
}


void CBinkMaterialRGBTextureRegenerator::Release()
{
	// we don't invoke the destructor here, we're not using the no-release extensions
}



// ===========================================================================
// CBinkMaterial class - creates a material, opens a QuickTime movie
//		   and plays the movie onto the material
// ===========================================================================

//-----------------------------------------------------------------------------
// CBinkMaterial Constructor
//-----------------------------------------------------------------------------
CBinkMaterial::CBinkMaterial() :
	m_pFileName( nullptr ),
	m_bInitCalled( false ),
	m_AVFrame( nullptr ),
	m_AVPkt( nullptr )
{
	memset( m_AVVideoData, 0, sizeof(m_AVVideoData) );
	memset( m_AVVideoLinesize, 0, sizeof(m_AVVideoLinesize) );

	Reset();
}


//-----------------------------------------------------------------------------
// CBinkMaterial Destructor
//-----------------------------------------------------------------------------
CBinkMaterial::~CBinkMaterial()
{
	SetFileName( nullptr );

	DestroyProceduralTexture();
	DestroyProceduralMaterial();

	av_frame_free( &m_AVFrame );
	av_packet_free( &m_AVPkt );

	if( m_AVVideoData[0] )
		av_free(m_AVVideoData[0]);

	if( m_AVFmtCtx )
		avformat_close_input( &m_AVFmtCtx );
}


void CBinkMaterial::Reset()
{
	printf("CBinkMaterial::Reset()\n");

	SetFileName( nullptr );

	DestroyProceduralTexture();
	DestroyProceduralMaterial();

	m_TexCordU = 0.0f;
	m_TexCordV = 0.0f;

	m_VideoFrameWidth = 0;
	m_VideoFrameHeight = 0;

	m_AVPixFormat = 0;
	m_PlaybackFlags = VideoPlaybackFlags::NO_PLAYBACK_OPTIONS;

	m_bMovieInitialized = false;
	m_bMoviePlaying = false;
	m_bMovieFinishedPlaying = false;
	m_bMoviePaused = false;
	m_bLoopMovie = false;

	m_bHasAudio = false;
	m_bMuted = false;

	m_CurrentVolume = 0.0f;

	m_QTMovieTimeScale = 0;
	m_QTMovieDuration = 0;
	m_QTMovieDurationinSec = 0.0f;
	m_QTMovieFrameRate.SetFPS( 0, false );

	if( !m_AVFrame )
		m_AVFrame = av_frame_alloc();
	if( !m_AVPkt)
		m_AVPkt = av_packet_alloc();

	m_RGBData = nullptr;

	m_AVFmtCtx = nullptr;
	m_AVAudioStream = nullptr;
	m_AVVideoStream = nullptr;

	AssertMsg( m_AVFrame, "av_frame_alloc return nullptr\n" );
	AssertMsg( m_AVPkt, "av_packet_alloc return nullptr\n"  );

	if( m_AVVideoData[0] )
	{
		av_free(m_AVVideoData[0]);
		m_AVVideoData[0] = nullptr;
	}

	if( m_AVFmtCtx )
	{
		avformat_close_input( &m_AVFmtCtx );
		m_AVFmtCtx = nullptr;
	}

	m_LastResult = VideoResult::SUCCESS;
}


void CBinkMaterial::SetFileName( const char *theMovieFileName )
{
	SAFE_DELETE_ARRAY( m_pFileName );

	if ( theMovieFileName != nullptr )
	{
		AssertMsg( V_strlen( theQTMovieFileName ) <= MAX_FILENAME_LEN, "Bad Quicktime Movie Filename" );
		m_pFileName = COPY_STRING( theMovieFileName );
	}
}


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


//-----------------------------------------------------------------------------
// Video information functions
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Returns the resolved filename of the video, as it might differ from
// what the user supplied, (also with absolute path)
//-----------------------------------------------------------------------------
const char *CBinkMaterial::GetVideoFileName()
{
	return m_pFileName;
}


VideoFrameRate_t &CBinkMaterial::GetVideoFrameRate()
{
	return m_QTMovieFrameRate;
}


VideoResult_t CBinkMaterial::GetLastResult()
{
	return m_LastResult;
}


//-----------------------------------------------------------------------------
// Audio Functions
//-----------------------------------------------------------------------------
bool CBinkMaterial::HasAudio()
{
	return m_bHasAudio;
}


bool CBinkMaterial::SetVolume( float fVolume )
{
	clamp( fVolume, 0.0f, 1.0f );

	m_CurrentVolume = fVolume;

	SetResult( VideoResult::AUDIO_ERROR_OCCURED );
	return false;
}


float CBinkMaterial::GetVolume()
{
	return m_CurrentVolume;
}


void CBinkMaterial::SetMuted( bool bMuteState )
{
	AssertExitFunc( m_bMoviePlaying, SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE) );

	SetResult( VideoResult::SUCCESS );

	if ( bMuteState == m_bMuted  )		// no change?
	{
		return;
	}

	m_bMuted = bMuteState;

	if ( m_bHasAudio )
	{

	}

	SetResult( VideoResult::SUCCESS );
}


bool CBinkMaterial::IsMuted()
{
	return m_bMuted;
}


VideoResult_t CBinkMaterial::SoundDeviceCommand( VideoSoundDeviceOperation_t operation, void *pDevice, void *pData )
{
	AssertExitV( m_bMovieInitialized || m_bMoviePlaying, VideoResult::OPERATION_OUT_OF_SEQUENCE );

	switch( operation )
	{
		// On win32, we try and create an audio context from a GUID
		case VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE:
		{
#if defined ( WIN32 )
			SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
			return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
#else
			// On any other OS, we don't support this operation
			return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
#endif
		}
		case VideoSoundDeviceOperation::SET_SOUND_MANAGER_DEVICE:
		{
#if defined ( OSX )
			SAFE_RELEASE_AUDIOCONTEXT( m_AudioContext );
			return ( CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext ) ? SetResult( VideoResult::SUCCESS ) : SetResult( VideoResult::AUDIO_ERROR_OCCURED ) );
#else
			// On any other OS, we don't support this operation
			return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
#endif
		}
		case VideoSoundDeviceOperation::SET_LIB_AUDIO_DEVICE:
		case VideoSoundDeviceOperation::HOOK_X_AUDIO:
		case VideoSoundDeviceOperation::SET_MILES_SOUND_DEVICE:
		{
			return SetResult( VideoResult::OPERATION_NOT_SUPPORTED );
		}
		default:
		{
			return SetResult( VideoResult::BAD_INPUT_PARAMETERS );
		}
	}

}


//-----------------------------------------------------------------------------
// Initializes the video material
//-----------------------------------------------------------------------------
bool CBinkMaterial::Init( const char *pMaterialName, const char *pFileName, VideoPlaybackFlags_t flags )
{
	printf("CBinkMaterial::Init\n");

	SetResult( VideoResult::BAD_INPUT_PARAMETERS );
	AssertExitF( IS_NOT_EMPTY( pFileName ) );
	AssertExitF( m_bInitCalled == false );

	m_PlaybackFlags	= flags;

	OpenMovie( pFileName );	// Open up the Quicktime file

	if ( !m_bMovieInitialized )
	{
		return false;					// Something bad happened when we went to open
	}

	// Now we can properly setup our regenerators
//	m_TextureRegen.SetSourceGWorld( m_MovieGWorld, m_VideoFrameWidth, m_VideoFrameHeight );

	CreateProceduralTexture( pMaterialName );
	CreateProceduralMaterial( pMaterialName );

	// Start movie playback
	if ( !BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::DONT_AUTO_START_VIDEO ) )
	{
		StartVideo();
	}

	m_bInitCalled = true;				// Look, if you only got one shot...

	return true;
}


void CBinkMaterial::Shutdown( void )
{
	StopVideo();
	Reset();
}


//-----------------------------------------------------------------------------
// Video playback state functions
//-----------------------------------------------------------------------------
bool CBinkMaterial::IsVideoReadyToPlay()
{
	return m_bMovieInitialized;
}


bool CBinkMaterial::IsVideoPlaying()
{
	return m_bMoviePlaying;
}


//-----------------------------------------------------------------------------
// Checks to see if the video has a new frame ready to be rendered and 
// downloaded into the texture and eventually display
//-----------------------------------------------------------------------------
bool CBinkMaterial::IsNewFrameReady( void )
{
	// Are we waiting to start playing the first frame? if so, tell them we are ready!
	if ( m_bMovieInitialized == true  )
	{
		return true;
	}

	// paused?
	if ( m_bMoviePaused )
	{
		return false;
	}

//	float curMovieTime;
	// Enough time passed to get to next frame??
/*	if ( curMovieTime < m_NextInterestingTimeToPlay )
	{
		// nope.. use the previous frame
		return false;
	}*/

	// we have a new frame we want then..
	return true;
}


bool CBinkMaterial::IsFinishedPlaying()
{
	return m_bMovieFinishedPlaying;
}


void CBinkMaterial::SetLooping( bool bLoopVideo )
{
	m_bLoopMovie = bLoopVideo;
}


bool CBinkMaterial::IsLooping()
{
	return m_bLoopMovie;
}


void CBinkMaterial::SetPaused( bool bPauseState )
{
	if ( !m_bMoviePlaying || m_bMoviePaused == bPauseState )
	{
		Assert( m_bMoviePlaying );
		return;
	}

	if ( bPauseState )			// Pausing the movie?
	{
		// Save off current time and set paused state
//		m_MoviePauseTime = GetMovieTime( m_QTMovie, nullptr );
//		StopMovie( m_QTMovie );
	}
	else  // unpausing the movie
	{
		// Reset the movie to the paused time
//		SetMovieTimeValue( m_QTMovie, m_MoviePauseTime );
//		StartMovie( m_QTMovie );
//		Assert( GetMoviesError() == noErr );
	}

	m_bMoviePaused = bPauseState;
}


bool CBinkMaterial::IsPaused()
{
	return ( m_bMoviePlaying ) ? m_bMoviePaused : false;
}


// Begins playback of the movie
bool CBinkMaterial::StartVideo()
{
	if ( !m_bMovieInitialized )
	{
		Assert( false );
		SetResult( VideoResult::OPERATION_ALREADY_PERFORMED );
		return false;
	}

	m_NextInterestingTimeToPlay = Plat_FloatTime();

	printf("Movie start time = %lf\n", Plat_FloatTime());

	// Transition to playing state
	m_bMovieInitialized = false;
	m_bMoviePlaying = true;

	Update();

	return true;
}


// stops movie for good, frees resources, but retains texture & material of last frame rendered
bool CBinkMaterial::StopVideo()
{
	if ( !m_bMoviePlaying )
	{
		SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
		return false;
	}

	m_bMoviePlaying = false;
	m_bMoviePaused = false;
	m_bMovieFinishedPlaying = true;

	// free resources
	CloseFile();

	SetResult( VideoResult::SUCCESS );
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Updates our scene
// Output : true = movie playing ok, false = time to end movie
// supposed to be: Returns true on a new frame of video being downloaded into the texture
//-----------------------------------------------------------------------------
bool CBinkMaterial::Update( void )
{
	AssertExitF( m_bMoviePlaying );


	// are we paused? can't update if so...
	if ( m_bMoviePaused )
		return true;			// reuse the last frame

	// Get current time in the movie
	float curMovieTime; // = GetMovieTime( m_QTMovie, nullptr );

	if( m_NextInterestingTimeToPlay > Plat_FloatTime() )
		return true;

	m_NextInterestingTimeToPlay += m_MovieFrameDuration;

	/* read frames from the file */

	int ret;
	while( (ret = av_read_frame(m_AVFmtCtx, m_AVPkt)) >= 0 )
	{
		if (m_AVPkt->stream_index == m_AVVideoStreamID)
		{
			avcodec_send_packet(m_AVVideoDecCtx, m_AVPkt);

			ret = avcodec_receive_frame(m_AVVideoDecCtx, m_AVFrame);
			if (ret < 0)
			{
				av_packet_unref(m_AVPkt);
				return true;
			}

			// write the frame data to output file
			if (m_AVVideoDecCtx->codec->type == AVMEDIA_TYPE_VIDEO)
			{
				av_image_copy(m_AVVideoData, m_AVVideoLinesize, (const uint8_t **)(m_AVFrame->data), m_AVFrame->linesize, m_AVPixFormat, m_VideoFrameWidth, m_VideoFrameHeight);
			}

			av_frame_unref(m_AVFrame);
			break;
		}

		av_packet_unref(m_AVPkt);
	}


	if( ret < 0 )
	{
		StopVideo();
		return false;
	}



	yuv420_rgb24_std( m_VideoFrameWidth, m_VideoFrameHeight, m_AVVideoData[0],
			m_AVVideoData[0]+m_VideoFrameHeight*m_VideoFrameWidth,
			m_AVVideoData[0]+m_VideoFrameWidth*m_VideoFrameHeight+((m_VideoFrameWidth+1)/2)*((m_VideoFrameHeight+1)/2),
			m_VideoFrameWidth, (m_VideoFrameWidth+1)/2, m_RGBData, m_VideoFrameWidth*3, YCBCR_601
		);

	m_Texture->Download();

	SetResult( VideoResult::SUCCESS );
	return true;
}


//-----------------------------------------------------------------------------
// Returns the material
//-----------------------------------------------------------------------------
IMaterial *CBinkMaterial::GetMaterial()
{
	return m_Material;
}


//-----------------------------------------------------------------------------
// Returns the texcoord range
//-----------------------------------------------------------------------------
void CBinkMaterial::GetVideoTexCoordRange( float *pMaxU, float *pMaxV )
{
	AssertExit( pMaxU != nullptr && pMaxV != nullptr );

	if ( m_Texture == nullptr )		// no texture?
	{
		*pMaxU = *pMaxV = 1.0f;
		return;
	}

	*pMaxU = m_TexCordU;
	*pMaxV = m_TexCordV;
}


//-----------------------------------------------------------------------------
// Returns the frame size of the QuickTime Video in pixels 
//-----------------------------------------------------------------------------
void CBinkMaterial::GetVideoImageSize( int *pWidth, int *pHeight )
{
	Assert( pWidth != nullptr && pHeight != nullptr );

	*pWidth  = m_VideoFrameWidth;
	*pHeight = m_VideoFrameHeight;
}


float CBinkMaterial::GetVideoDuration()
{
	return m_QTMovieDurationinSec;
}


int CBinkMaterial::GetFrameCount()
{
	return m_QTMovieFrameCount;
}


//-----------------------------------------------------------------------------
// Sets the frame for an QuickTime  Material (use instead of SetTime)
//-----------------------------------------------------------------------------
bool CBinkMaterial::SetFrame( int FrameNum )
{
	if ( !m_bMoviePlaying )
	{
		Assert( false );
		SetResult( VideoResult::OPERATION_OUT_OF_SEQUENCE );
		return false;
	}

	float	theTime = (float) FrameNum * m_QTMovieFrameRate.GetFPS();
	return SetTime( theTime );
}


int CBinkMaterial::GetCurrentFrame()
{
	AssertExitV( m_bMoviePlaying, -1 );

	float curTime; // = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );

	return curTime / m_QTMovieFrameRate.GetUnitsPerFrame();
}


float CBinkMaterial::GetCurrentVideoTime()
{
	AssertExitV( m_bMoviePlaying, -1.0f );

	float curTime; // = m_bMoviePaused ? m_MoviePauseTime : GetMovieTime( m_QTMovie, nullptr );

	return curTime / m_QTMovieFrameRate.GetUnitsPerSecond();
}


bool CBinkMaterial::SetTime( float flTime )
{
	AssertExitF( m_bMoviePlaying );
	AssertExitF( flTime >= 0 && flTime < m_QTMovieDurationinSec );

	float newTime = ( flTime * m_QTMovieFrameRate.GetUnitsPerSecond() + 0.5f) ;

	clamp( newTime,  m_MovieFirstFrameTime, m_QTMovieDuration ); 

	// Are we paused?
	if ( m_bMoviePaused )
	{
		m_MoviePauseTime = newTime;
		return true;
	}

	float curMovieTime; // = GetMovieTime( m_QTMovie, nullptr );

	// Don't stop and reset movie if we are within 1 frame of the requested time
	if ( newTime <= curMovieTime - m_QTMovieFrameRate.GetUnitsPerFrame() || newTime >= curMovieTime + m_QTMovieFrameRate.GetUnitsPerFrame() )
	{
		// Reset the movie to the requested time
/*		StopMovie( m_QTMovie );
		SetMovieTimeValue( m_QTMovie, newTime );
		StartMovie( m_QTMovie );

		Assert( GetMoviesError() == noErr );*/
	}

	return true;
}


//-----------------------------------------------------------------------------
// Initializes, shuts down the procedural texture
//-----------------------------------------------------------------------------
void CBinkMaterial::CreateProceduralTexture( const char *pTextureName )
{
	printf("CBinkMaterial::CreateProceduralTexture\n");

	AssertIncRange( m_VideoFrameWidth, cMinVideoFrameWidth, cMaxVideoFrameWidth );
	AssertIncRange( m_VideoFrameHeight, cMinVideoFrameHeight, cMaxVideoFrameHeight );
	AssertStr( pTextureName );

	// Either make the texture the same dimensions as the video,
	// or choose power-of-two textures which are at least as big as the video
	bool actualSizeTexture = BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::TEXTURES_ACTUAL_SIZE );

	int nWidth  = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameWidth, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameWidth ); 
	int nHeight = ( actualSizeTexture ) ? ALIGN_VALUE( m_VideoFrameHeight, TEXTURE_SIZE_ALIGNMENT ) : ComputeGreaterPowerOfTwo( m_VideoFrameHeight ); 

	// initialize the procedural texture as 32-it RGBA, w/o mipmaps
	m_Texture.InitProceduralTexture( pTextureName, "VideoCacheTextures", nWidth, nHeight, 
				IMAGE_FORMAT_RGB888, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP |
				TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_NOLOD );

	// Use this to get the updated frame from the remote connection	
	m_Texture->SetTextureRegenerator( &m_TextureRegen /* , false */ );

	// compute the texcoords
	int nTextureWidth = m_Texture->GetActualWidth();
	int nTextureHeight = m_Texture->GetActualHeight();

	m_TexCordU = ( nTextureWidth > 0 ) ? (float) m_VideoFrameWidth / (float) nTextureWidth : 0.0f;
	m_TexCordV = ( nTextureHeight > 0 ) ? (float) m_VideoFrameHeight / (float) nTextureHeight : 0.0f;
}


void CBinkMaterial::DestroyProceduralTexture()
{
	if ( m_Texture != nullptr )
	{
		// DO NOT Call release on the Texture Regenerator, as it will destroy this object!  bad bad bad
		// instead we tell it to assign a NULL regenerator and flag it to not call release
		m_Texture->SetTextureRegenerator( nullptr /*, false */ );
		// Texture, texture go away...
		m_Texture.Shutdown( true );
	}
}


//-----------------------------------------------------------------------------
// Initializes, shuts down the procedural material
//-----------------------------------------------------------------------------
void CBinkMaterial::CreateProceduralMaterial( const char *pMaterialName )
{
	// create keyvalues if necessary
	KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" );
	{
		pVMTKeyValues->SetString( "$basetexture", m_Texture->GetName() );
		pVMTKeyValues->SetInt( "$nobasetexture", 1 );
		pVMTKeyValues->SetInt( "$nofog", 1 );
		pVMTKeyValues->SetInt( "$spriteorientation", 3 );
		pVMTKeyValues->SetInt( "$translucent", 1 );
		pVMTKeyValues->SetInt( "$nolod", 1 );
		pVMTKeyValues->SetInt( "$nomip", 1 );
		pVMTKeyValues->SetInt( "$gammacolorread", 0 );
	}

	// FIXME: gak, this is backwards.  Why doesn't the material just see that it has a funky basetexture?
	m_Material.Init( pMaterialName, pVMTKeyValues );
	m_Material->Refresh();
}


void CBinkMaterial::DestroyProceduralMaterial()
{
	// Store the internal material pointer for later use
	IMaterial *pMaterial = m_Material;
	m_Material.Shutdown();
	materials->UncacheUnusedMaterials();

	// Now be sure to free that material because we don't want to reference it again later, we'll recreate it!
	if ( pMaterial != nullptr )
	{
		pMaterial->DeleteIfUnreferenced();
	}
}



//-----------------------------------------------------------------------------
// Opens a movie file using quicktime
//-----------------------------------------------------------------------------
void CBinkMaterial::OpenMovie( const char *theMovieFileName )
{
	AssertExit( IS_NOT_EMPTY( theMovieFileName ) );
/*
	// Set graphics port 
#if defined ( WIN32 )
	SetGWorld ( (CGrafPtr) GetNativeWindowPort( nil ), nil ); 
#elif defined ( OSX		)
	SetGWorld( nil, nil );
#endif
*/

	SetFileName( theMovieFileName );
	printf("CBinkMaterial::OpenMovie( \"%s\" )\n", theMovieFileName);

	if (avformat_open_input(&m_AVFmtCtx, theMovieFileName, NULL, NULL) < 0)
 	{
		Warning("Could not open source file %s\n", theMovieFileName);
 		SetResult( VideoResult::FILE_ERROR_OCCURED ) ;
		Reset();
		return;
	}

	if (avformat_find_stream_info(m_AVFmtCtx, NULL) < 0)
	{
		Warning("Could not find stream information for %s\n", theMovieFileName);
 		SetResult( VideoResult::FILE_ERROR_OCCURED ) ;
		Reset();
		return;
	}

	if (open_codec_context(&m_AVVideoStreamID, &m_AVVideoDecCtx, m_AVFmtCtx, AVMEDIA_TYPE_VIDEO) == 0)
	{
		m_AVVideoStream = m_AVFmtCtx->streams[m_AVVideoStreamID];

		/* allocate image where the decoded image will be put */
		m_VideoFrameWidth = m_AVVideoDecCtx->width;
		m_VideoFrameHeight = m_AVVideoDecCtx->height;
		m_AVPixFormat = m_AVVideoDecCtx->pix_fmt;
		size_t size = av_image_alloc(m_AVVideoData, m_AVVideoLinesize,
							m_VideoFrameWidth, m_VideoFrameHeight, m_AVPixFormat, 1);

		m_RGBData = calloc( m_VideoFrameWidth*m_VideoFrameHeight*3, 1 );

		printf("m_AVVideoData size = %zu\nm_VideoFrameWidth=%d\nm_VideoFrameHeight=%d\n", size, m_VideoFrameWidth, m_VideoFrameHeight);

		if (size < 0)
		{
			Warning("Could not allocate raw video buffer\n", theMovieFileName);
 			SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) ;
			Reset();
 			return;
		}
	}
	else
	{
			Warning("open_codec_context failed for %s\n", theMovieFileName);
 			SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) ;
			Reset();
			return;
	}

	m_MovieFrameDuration = 1.0/((double)m_AVVideoStream->r_frame_rate.num/(double)m_AVVideoStream->r_frame_rate.den);
	m_TextureRegen.SetSourceImage( m_RGBData, m_VideoFrameWidth, m_VideoFrameHeight );
	printf("Video FPS: %lf\n", (double)m_AVVideoStream->r_frame_rate.num/(double)m_AVVideoStream->r_frame_rate.den);

#if 0
	Handle	MovieFileDataRef = nullptr;
	OSType	MovieFileDataRefType = 0;

	CFStringRef	imageStrRef = CFStringCreateWithCString ( NULL,  theQTMovieFileName, 0 ); 
	AssertExitFunc( imageStrRef != nullptr, SetResult( VideoResult::SYSTEM_ERROR_OCCURED ) );

	OSErr status = QTNewDataReferenceFromFullPathCFString( imageStrRef, (QTPathStyle) kQTNativeDefaultPathStyle, 0, &MovieFileDataRef, &MovieFileDataRefType );
	AssertExitFunc( status == noErr, SetResult( VideoResult::FILE_ERROR_OCCURED ) );

	CFRelease( imageStrRef );

//	status = NewMovieFromDataRef( &m_QTMovie, newMovieActive, nil, MovieFileDataRef, MovieFileDataRefType );
//	SAFE_DISPOSE_HANDLE( MovieFileDataRef );

	if ( status != noErr )
	{
		Assert( false );
		Reset();
		SetResult( VideoResult::VIDEO_ERROR_OCCURED );
		return;
	}

	// disabling audio?
	if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::NO_AUDIO ) )
	{
		m_bHasAudio = false;
	}
	else
	{
		// does movie have audio?
//		Track audioTrack = GetMovieIndTrackType( m_QTMovie, 1, SoundMediaType, movieTrackMediaType );
//		m_bHasAudio = ( audioTrack != nullptr );
	}

	// Now we need to extract the time info from the QT Movie 
//	m_QTMovieTimeScale	= GetMovieTimeScale( m_QTMovie );
//	m_QTMovieDuration	= GetMovieDuration( m_QTMovie );

	// compute movie duration	
/*	m_QTMovieDurationinSec = float ( double( m_QTMovieDuration ) / double( m_QTMovieTimeScale ) );
	if ( !MovieGetStaticFrameRate( m_QTMovie, m_QTMovieFrameRate ) )
	{
		WarningAssert( "Couldn't Get Frame Rate" );
	}*/
	
	// and get an estimated frame count
	m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieTimeScale;
	
	if ( m_QTMovieFrameRate.GetUnitsPerSecond() == m_QTMovieTimeScale )
	{
		m_QTMovieFrameCount = m_QTMovieDuration / m_QTMovieFrameRate.GetUnitsPerFrame();
	}
	else
	{
		m_QTMovieFrameCount = (int) ( (float) m_QTMovieDurationinSec * m_QTMovieFrameRate.GetFPS() + 0.5f );
	}
	
	// what size do we set the output rect to?
//	GetMovieNaturalBoundsRect(m_QTMovie, &m_QTMovieRect);
	
	m_VideoFrameWidth = m_QTMovieRect.right;
	m_VideoFrameHeight = m_QTMovieRect.bottom;
	
	// Sanity check...
	AssertExitFunc( m_QTMovieRect.top == 0 && m_QTMovieRect.left == 0 &&
					m_QTMovieRect.right >= cMinVideoFrameWidth && m_QTMovieRect.right <= cMaxVideoFrameWidth && 
					m_QTMovieRect.bottom >= cMinVideoFrameHeight && m_QTMovieRect.bottom <= cMaxVideoFrameHeight &&
					m_QTMovieRect.right % 4 == 0,
					SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );

	// Setup the QuiuckTime Graphics World for the Movie
/*	status = QTNewGWorld( &m_MovieGWorld, k32BGRAPixelFormat, &m_QTMovieRect, nil, nil, 0 );
	AssertExit( status == noErr );

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

	// Assign the GWorld to this movie
	SetMovieGWorld( m_QTMovie, m_MovieGWorld, nil );

	// Setup Movie Audio, unless suppressed
	if ( !CreateMovieAudioContext( m_bHasAudio, m_QTMovie, &m_AudioContext, true, &m_CurrentVolume ) )
	{
		SetResult( VideoResult::AUDIO_ERROR_OCCURED );
		WarningAssert( "Couldn't Set Audio" );
	}
	
	// Get the time of the first frame
	OSType	qTypes[1] = { VisualMediaCharacteristic };
	short	qFlags = nextTimeStep | nextTimeEdgeOK;			// use nextTimeStep instead of nextTimeMediaSample for MPEG 1-2 compatibility
		
	GetMovieNextInterestingTime( m_QTMovie, qFlags, 1, qTypes, (TimeValue) 0, fixed1, &m_MovieFirstFrameTime, NULL );
	AssertExitFunc( GetMoviesError() == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );

	// Preroll the movie
	if ( BITFLAGS_SET( m_PlaybackFlags, VideoPlaybackFlags::PRELOAD_VIDEO ) )
	{
		Fixed playRate = GetMoviePreferredRate( m_QTMovie );
		status = PrerollMovie( m_QTMovie, m_MovieFirstFrameTime, playRate );
		AssertExitFunc( status == noErr, SetResult( VideoResult::VIDEO_ERROR_OCCURED ) );
	}*/
	
#endif
	m_bMovieInitialized = true;
}


void CBinkMaterial::CloseFile()
{
	av_freep( &m_AVVideoData[0] );
	avformat_close_input( &m_AVFmtCtx );
	m_AVFmtCtx = nullptr;
	free(m_RGBData);

	SetFileName( nullptr );
}