//
// Asura_Bink.cpp
//
// Created by KOR - 09 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_USE_BINK

// This file's header.
#include "Bink/Asura_Bink.h"

// asura
#include "Core/Asura_Maths.h"
#include "Core/Asura_String.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Input/Asura_Input.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"
#include "Sound/Asura_Music.h"
#include "Sound/Asura_Sound.h"
#include "FileAccess/Asura_PlatformFileAccess.h"
#include "FileAccess/Asura_PlatformFileAccessUtils.h"

// Asura Wii
#include "Core/Asura_Wii_MEM2.h"
#include "HomeButton/Asura_Wii_HomeButtonMenu.h"
#include "Input/Asura_Wii_Input.h"
#include "Rendering/Asura_GX.h"

#if defined(_DEBUG)
#include "Debugging/Asura_ScreenShot_System.h"
#endif

// Library headers
#include <revolution/os.h>
#include <revolution/ax.h>
#include <revolution/gx.h>
#include <revolution/sc.h>

#include "binktextures.h"

// Use this to test the amount of stack being used by the open thread.
//#define ASURA_WII_BINK_TEST_STACK_SIZE (1)


#ifdef ASURA_WII_BINK_TEST_STACK_SIZE
#include "Core/Memory/Asura_Mem.h"

static u_int g_uMaxStackUsed = 0;
DEBUGVAR u_char dbg_ucStackFillValue = 0x66;
#endif//def ASURA_WII_BINK_TEST_STACK_SIZE

bool						Asura_Wii_Bink::s_bInitialised = false;
Asura_Wii_Bink_Open_Task	Asura_Wii_Bink::s_axOpenQueue[uASURA_WII_BINK_MAX_QUEUE_LENGTH];
u_int						Asura_Wii_Bink::s_uQueueHead = 0;
u_int						Asura_Wii_Bink::s_uQueueTail = 0;
OSThread					Asura_Wii_Bink::s_xOpenThread;
char						Asura_Wii_Bink::s_acOpenThreadStack[uASURA_WII_BINK_OPENTHREAD_STACK_SIZE] __attribute__ ((aligned (32))) = { 0 };
Asura_Mutex					Asura_Wii_Bink::s_xQueueMutex;

DEBUGVAR bool dbg_bDontPrecache = true;
DEBUGVAR bool dbg_bAsyncOpen = true;

static void *aram_malloc(U32 num_bytes)
{
	return ASURA_WII_MEM2_ALLOC( num_bytes );
}

static void aram_free(void * ptr)
{
	Asura_Wii_MEM2::Free( ptr );
}

static void* alloc32align(U32 num_bytes)
{
	return ASURA_WII_MEM2_ALLOC( OSRoundUp32B(num_bytes) );
}

static void free32align(void* ptr)
{
	Asura_Wii_MEM2::Free( ptr );
}

void Asura_Wii_Bink::Initialise()
{
	Asura_Assert(!s_bInitialised, "Bink system already initialised.");
	if(s_bInitialised) { return; }

	RADARAMCALLBACKS aram_callbacks = { aram_malloc, aram_free };
	AXSetCompressor(AX_COMPRESSOR_OFF);

	BinkSoundUseAX(&aram_callbacks);
	char* szError = BinkGetError();
	if(szError && szError[0])
	{
		Asura_OutputToDebugger(("Bink error: %s\n", szError));
	}

	BinkSetMemory(&alloc32align, &free32align);

	// Start queue thread
	char* pcStackEnd = s_acOpenThreadStack + uASURA_WII_BINK_OPENTHREAD_STACK_SIZE;

#ifdef ASURA_WII_BINK_TEST_STACK_SIZE
	Asura_Mem::Set(s_acOpenThreadStack, dbg_ucStackFillValue, uASURA_WII_BINK_OPENTHREAD_STACK_SIZE);
#endif

	const bool bCreatedThread =
		OSCreateThread(
		&s_xOpenThread,							// Pointer to thread control block to initialize.
		OpenThreadFunc,							// Pointer to  function from which to start execution.
		NULL,									// Arguments to be passed to the start function.
		reinterpret_cast<void*>(pcStackEnd),	// Address of initial stack pointer. Note that stacks grow DOWN, so this should be the higher address of the stack
		uASURA_WII_BINK_OPENTHREAD_STACK_SIZE,	// Size of the stack in bytes.  This is used for sanity checking
		16,										// Base scheduling priority of thread.  0 is the highest priority, 31 is the lowest. The default thread calling the main() function (created by OSInit()) has a priority of 16.
		OS_THREAD_ATTR_DETACH					// OR-ed values of attributes.
		);

	// Should leave it asleep until it is required?
	if(bCreatedThread)
	{
		const int iPrevSuspendCount = OSResumeThread(&s_xOpenThread);
		Asura_Assertf(iPrevSuspendCount == 1, ("Expected thread to be resumed on this call. Previous suspend count: %i", iPrevSuspendCount));
	}

	s_bInitialised = true;
}

void Asura_Wii_Bink::Update()
{

#ifdef ASURA_WII_BINK_TEST_STACK_SIZE
	// first four bytes are "deadbabe"
	for(u_int u = 4; u < uASURA_WII_BINK_OPENTHREAD_STACK_SIZE; ++u)
	{
		if(s_acOpenThreadStack[u] != dbg_ucStackFillValue)
		{
			const u_int uStackUsed = uASURA_WII_BINK_OPENTHREAD_STACK_SIZE - u;
			if(uStackUsed > g_uMaxStackUsed)
			{
				g_uMaxStackUsed = uStackUsed;
				OSReport("Bink Open stack required is larger than %u\n", uStackUsed);
			}
			break;
		}
	}
#endif

	if(m_pxOpenTask)
	{
		m_pxOpenTask->m_xMutex.Lock();
		if(m_pxOpenTask->m_bTaskComplete)
		{
			m_pxBinkHandle = m_pxOpenTask->m_pxBinkHandle;
			CalculateLength();
			UpdateVolume();
			if(m_bPendingPlay)
			{
				BinkPause(m_pxBinkHandle, 0);
				m_bFinished = false;
			}
			m_pxOpenTask->m_xMutex.Unlock();
			m_pxOpenTask = NULL;
		}
		else
		{
			m_pxOpenTask->m_xMutex.Unlock();
			return;
		}
	}

	if (!m_pxBinkHandle) { return; }

	if(!BinkWait(m_pxBinkHandle))
	{
		BinkDoFrame(m_pxBinkHandle);
        if ((!m_bLooping) && (m_pxBinkHandle->Frames == m_pxBinkHandle->FrameNum))
        {
			m_bFinished = true;
            Stop();
        }
        else
        {
		    BinkNextFrame(m_pxBinkHandle);
        }
	}
}

bool Asura_Wii_Bink::Create(const char* szFilename)
{
	static char szBuffer[256];
	Asura_Assert(strlen(szFilename) < 256, "Bink Filename too long");

	strcpy(szBuffer, szFilename);
	REPLACE_EXTENSION(szBuffer, "bik", 256);
	REPLACE_SLASHES(szBuffer);

	return CreateFromResource(szBuffer, 0);
}


bool Asura_Wii_Bink::CreateFromResource(const char* szResourceFilename, int iOffset)
{
	Asura_Assert(!(iOffset % 32), "Bink is not 32 byte aligned within file");

	Asura_OutputToDebugger(("Loading bink file from %s at offset 0x%08x\n", szResourceFilename, iOffset));
	s32 iEntryNum = DVDConvertPathToEntrynum(szResourceFilename);
	Asura_Assertf(iEntryNum != -1, ("Could not open %s for reading bink", szResourceFilename));
	if(iEntryNum >= 0)
	{
		return CreateFromResource(iEntryNum, iOffset);
	}
	return false;
}

bool Asura_Wii_Bink::CreateFromResource(int iDVDEntryNum, int iOffset, int iSize /* = -1 */)
{
	Asura_Assert(!m_pxBinkHandle, "Already have a Bink handle");

	if(DVDFastOpen(iDVDEntryNum, &m_xFileInfo))
	{
		m_xFileInfo.cb.userData = (void*)iOffset;
		m_fLength = -1.f;

		if(dbg_bAsyncOpen)
		{
			s_xQueueMutex.Lock();
			if((s_uQueueTail + 1) % uASURA_WII_BINK_MAX_QUEUE_LENGTH == s_uQueueHead)
			{
				Asura_Assert(0, "Too many bink open tasks in queue.");
				DVDClose(&m_xFileInfo);
				s_xQueueMutex.Unlock();
				return false;
			}
			Asura_Assert(!m_pxOpenTask, "Already have an open task");
			m_pxOpenTask = &s_axOpenQueue[s_uQueueTail];
			m_pxOpenTask->m_xMutex.Lock();
			m_pxOpenTask->m_pxBinkHandle = NULL;
			m_pxOpenTask->m_xFileInfo = m_xFileInfo;
			m_pxOpenTask->m_bCloseOnComplete = false;
			m_pxOpenTask->m_bTaskComplete = false;
			m_pxOpenTask->m_xMutex.Unlock();
			
			s_uQueueTail = (s_uQueueTail + 1) % uASURA_WII_BINK_MAX_QUEUE_LENGTH;
			s_xQueueMutex.Unlock();
			return true;
		}
		else
		{
			m_pxBinkHandle = BinkOpen((char*)&m_xFileInfo, BINKFILEHANDLE | (dbg_bDontPrecache ? BINKNOFILLIOBUF : 0));

			if(m_pxBinkHandle)
			{
				BinkPause(m_pxBinkHandle, 1);
				CalculateLength();
				return true;
			}
			else
			{
				char* szError = BinkGetError();
				Asura_OutputToDebugger(("Bink open error: %s\n", szError));
			}
		}
	}
	Asura_Assert(0, "Could not open file for reading bink");
	return false;
}

static bool IsDiscError()
{
	switch(DVDGetDriveStatus())
	{
		case DVD_STATE_FATAL_ERROR:	
		case DVD_STATE_NO_DISK:
		case DVD_STATE_WRONG_DISK:
		case DVD_STATE_RETRY:
		{
			return true;
		}
	}
	return false;
}

void* Asura_Wii_Bink::OpenThreadFunc(void* pvBink)
{
	while(1)
	{
		Asura_Wii_Bink_Open_Task* pxTask = NULL;
		
		s_xQueueMutex.Lock();
		if(s_uQueueHead != s_uQueueTail)
		{
			pxTask = &s_axOpenQueue[s_uQueueHead];
		}
		s_xQueueMutex.Unlock();

		if(pxTask)
		{
			HBINK pxBinkHandle = NULL;

			while(!pxBinkHandle)
			{
				// Don't put this inside the mutex, as it is the part that stalls for ages.
				pxBinkHandle = BinkOpen((char*)(&pxTask->m_xFileInfo), BINKFILEHANDLE | (dbg_bDontPrecache ? BINKNOFILLIOBUF : 0));
				if(!pxBinkHandle)
				{
					// Test for disc error.
					u_char aucDummy[32] __attribute__ ((aligned (32))) = { 0 };
					DVDReadAsync(&pxTask->m_xFileInfo, &aucDummy, sizeof(aucDummy), 0, NULL);
					if(IsDiscError())
					{
						while(IsDiscError()) {}
					}
					else
					{
						// Wasn't due to a disc error; give up.
						char* szError = BinkGetError();
						Asura_OutputToDebugger(("Bink open error: %s\n", szError));
						break;
					}
				}
			}
			pxTask->m_xMutex.Lock();
			
			if(pxBinkHandle)
			{
				if(pxTask->m_bCloseOnComplete)
				{
					//OSReport("Executing pending close.\n");
					BinkClose(pxBinkHandle);
					DVDClose(&pxTask->m_xFileInfo);
				}
				else
				{
					BinkPause(pxBinkHandle, 1);
				}
				pxTask->m_pxBinkHandle = pxBinkHandle;
			}
			pxTask->m_bTaskComplete = true;
			pxTask->m_xMutex.Unlock();

			s_uQueueHead = (s_uQueueHead + 1) % uASURA_WII_BINK_MAX_QUEUE_LENGTH;
		}
		OSYieldThread();
	}
}

void Asura_Wii_Bink::Play(bool bLooping)
{
	if(m_pxOpenTask)
	{
		// We are still loading.
		m_bPendingPlay = true;
		m_bLooping = bLooping;
		m_bFinished = false;
		return;
	}
    if (!m_pxBinkHandle) 
    {
        Asura_Assert(false, "");
        return;
    }

    BinkPause(m_pxBinkHandle, 0);
    m_bLooping = bLooping;
	m_bFinished = false;
}

void Asura_Wii_Bink::Stop()
{
	if(m_pxOpenTask)
	{
		// We are still loading.
		m_bPendingPlay = false;
		return;
	}
    if (!m_pxBinkHandle) 
    {
        Asura_Assert(false, "");
        return;
    }

    // pause and goto start
    BinkPause(m_pxBinkHandle, 1);
    BinkGoto(m_pxBinkHandle, 0, 0);
}

void Asura_Wii_Bink::Release()
{
	if(m_pxOpenTask)
	{
		Asura_Assert(!m_pxBinkHandle, "Shouldn't have a Bink Handle and an open task simultaneously");

		m_pxOpenTask->m_xMutex.Lock();
		if(m_pxOpenTask->m_bTaskComplete)
		{
			BinkClose(m_pxOpenTask->m_pxBinkHandle);
			DVDClose(&m_xFileInfo);
		}
		else
		{
			m_pxOpenTask->m_bCloseOnComplete = true;
		}

		m_pxOpenTask->m_xMutex.Unlock();
		m_pxOpenTask = NULL;
		return;
	}

    if (!m_pxBinkHandle) 
    {
        return;
    }
    
    BinkClose(m_pxBinkHandle);
    m_pxBinkHandle = 0;
	if(!DVDClose(&m_xFileInfo))
	{
		Asura_Assert(0, "Error attempting to close bink file in resource.");
	}
}

void Asura_Wii_Bink::Pause()
{
	if(m_pxOpenTask)
	{
		// We are still loading.
		m_bPendingPlay = false;
		return;
	}

	if (!m_pxBinkHandle) 
    {
        Asura_Assert(false, "");
        return;
    }

    BinkPause(m_pxBinkHandle, 1);
}

void Asura_Wii_Bink::Resume()
{
	if(m_pxOpenTask)
	{
		// We are still loading.
		m_bPendingPlay = true;
		return;
	}

	if (!m_pxBinkHandle) 
    {
        Asura_Assert(false, "");
        return;
    }

    BinkPause(m_pxBinkHandle, 0);
}

void Asura_Wii_Bink::SetVolume(float fVol)
{
	m_fVolume = fVol;
	UpdateVolume();
}

static u_char * s_puRGBResizeBuffer;
static GXTexObj s_texRGBResizeBuffer;

static void AllocateResizeBuffer()
{
	int iImageHeight = OSRoundUp32B(Asura_GX::GetScreenHeight());
	u_int uSize = GXGetTexBufferSize(OSRoundUp32B(640), iImageHeight, GX_TF_RGBA8, GX_FALSE, 0);

	s_puRGBResizeBuffer = (u_char*)ASURA_WII_MEM2_ALLOC(uSize);
	GXInitTexObj( &s_texRGBResizeBuffer, s_puRGBResizeBuffer, 640, Asura_GX::GetScreenHeight(), GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_DISABLE );
	GXInitTexObjLOD( &s_texRGBResizeBuffer, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_DISABLE, GX_DISABLE, GX_ANISO_1 );
}

static void FreeResizeBuffer()
{
	ASURA_WII_MEM2_FREE(s_puRGBResizeBuffer);
	s_puRGBResizeBuffer = NULL;
}

static void Show_frame( HBINK bink, BINKTEXTURESET * set, Asura_Wii_Bink::Movie_Render_Params xParams )
{
	float fScreenWidth = Asura_GX::GetScreenWidth(), fScreenHeight = Asura_GX::GetScreenHeight();

	Draw_Bink_textures( set,
		bink->Width, bink->Height,
		xParams.Pass[0].fX, xParams.Pass[0].fY,
		xParams.Pass[0].fDstWidth / fScreenWidth, xParams.Pass[0].fDstHeight / fScreenHeight,
		1.0f, 1.f );

	if (xParams.Pass[1].fDstWidth != xParams.Pass[0].fDstWidth || xParams.Pass[1].fDstHeight != xParams.Pass[0].fDstHeight)
	{
		Asura_GX::Set3DProjection(false);
		GXSetAlphaCompare( GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0 );
		GXSetZCompLoc( GX_TRUE );
		GXSetCullMode(GX_CULL_NONE);
		Asura_GX::SetZBufferMode(false, ASURA_ZBUFFERCMP_ALWAYS, false);
		GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_NOOP);


		GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_POS,	GX_POS_XY,		GX_F32,		0);
		GXSetVtxAttrFmt(GX_VTXFMT1, GX_VA_TEX0, GX_TEX_ST,		GX_F32,		0);
		GXClearVtxDesc( );
		GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
		GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );

		GXSetTexCopySrc( 0, 0, fScreenWidth, fScreenHeight );
		GXSetTexCopyDst(fScreenWidth, fScreenHeight, GX_TF_RGBA8, GX_DISABLE );
		GXCopyTex( s_puRGBResizeBuffer, TRUE );

		// Colour channels - we are not using rasterised colour at all.
		GXSetNumChans    (0);

		// Texgens - unit, shift S, shift T.
		GXSetNumTexGens  (1);
		GXSetNumTevStages  (1);
		GXSetNumIndStages( 0 );

		GXSetTevDirect( GX_TEVSTAGE0);
		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
		GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
		GXSetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL );
		GXSetTevColorIn( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC );
		GXSetTevColorOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
		GXSetTevAlphaIn( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
		GXSetTevAlphaOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ADDHALF, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
		GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );

		GXPixModeSync();
		// flush texture cache
		GXInvalidateTexAll();
		GXLoadTexObj( &s_texRGBResizeBuffer, 	GX_TEXMAP0 );

		GXBegin(GX_QUADS, GX_VTXFMT1, 4);
		GXPosition2f32(xParams.Pass[1].fX, xParams.Pass[1].fY);
		GXTexCoord2f32(0.0F, 0.0F);
		GXPosition2f32(xParams.Pass[1].fX + xParams.Pass[1].fDstWidth, xParams.Pass[1].fY);
		GXTexCoord2f32(1.0F, 0.0F);
		GXPosition2f32(xParams.Pass[1].fX + xParams.Pass[1].fDstWidth, xParams.Pass[1].fY + xParams.Pass[1].fDstHeight);
		GXTexCoord2f32(1.0F, 1.0F);
		GXPosition2f32(xParams.Pass[1].fX, xParams.Pass[1].fY + xParams.Pass[1].fDstHeight);
		GXTexCoord2f32(0.0F, 1.0F);
		GXEnd();

	}

}

void InitialiseMovieRenderParamsFromBink(HBINK pxBinkHandle, Asura_Wii_Bink::Movie_Render_Params &xParams)
{
	xParams.bWideScreen = SCGetAspectRatio() ==  SC_ASPECT_RATIO_16x9;
	xParams.fSrcWidth = pxBinkHandle->Width;
	xParams.fSrcHeight = pxBinkHandle->Height;
	xParams.Pass[0].fDstWidth = Asura_GX::GetScreenWidth();
	xParams.Pass[0].fDstHeight = Asura_GX::GetScreenHeight();
	xParams.Pass[0].fX = (xParams.Pass[0].fDstWidth - xParams.fSrcWidth) * 0.5f;
	xParams.Pass[0].fY = (xParams.Pass[0].fDstHeight - xParams.fSrcHeight) * 0.5f;
	xParams.Pass[1].fX = 0.0f;
	xParams.Pass[1].fY = 0.0f;
	xParams.Pass[1].fDstWidth = Asura_GX::GetScreenWidth();
	xParams.Pass[1].fDstHeight = Asura_GX::GetScreenHeight();
}

int Asura_Wii_Bink::PlayFMV(const char* pszFilename, unsigned int uTrackNo /* = 0 */, bool bSkipAllowed /* = true */, CBRenderChange pCB /*= NULL*/, RENDER_SUBTITLE_CB_FN pfnRenderSubtitleCB /*= NULL*/)
{
	if (!Asura_GX::ReadyToDraw())
	{
		Asura_OutputToDebugger(("Reached bink video while strap reminder still up, waiting.\n"));
	}

	while (!Asura_GX::ReadyToDraw())
	{
		Asura_Input::Update();
		Asura_Render::BeginFrame();
		Asura_Render::EndFrame();
	}
	//Asura_Bounding_Rectangle xRect(0.0f, 200.0f, 540.0f, 280.0f);
	if(!s_bInitialised) Initialise();

	char szFilename[128];
	//sprintf( szFilename, "FMV/%s.bik", pszFilename );
	strcpy( szFilename, "FMV/" );
	strcat( szFilename, pszFilename );
	strcat( szFilename, ".bik" );

	if(!Asura_FileAccess::FileExists(szFilename))
	{
		return -1;
	}

	HBINK pxBinkHandle;

	// Stop any music
	Asura_Sound_System::PauseAllSystems();
	Asura_Wii_HomeButtonMenu::Allow(false);
	
	// Render a couple of blank frames
	bool bPrevClrScr = Asura_Render::GetEnableScreenClear();
	Asura_Render::SetEnableScreenClear(true);
    Asura_Render::Overlay.Colour           = 0x00000000;
    Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;
    Asura_Render::Overlay.TextureIndex     = ASURA_TEXTURE_NONE;
    Asura_Render::Overlay.SetRectXY( 0.0f, 0.0f, 640.0f, 528.0f );
	Asura_Render::BeginFrame();
    Asura_Render::Overlay.Render();   	
	Asura_Render::EndFrame();
	Asura_Render::BeginFrame();
    Asura_Render::Overlay.Render();   	
	Asura_Render::EndFrame();

	AllocateResizeBuffer();
//	GXDrawSyncCallback pOldDrawSyncCB = GXSetDrawSyncCallback(NULL);
	{
		Asura_OutputToDebugger(("Attempting to open Bink handle.\n"));
		BinkSetSoundTrack(1, &uTrackNo);
		Asura_Handle iFileTest = Asura_FileAccess::Open(szFilename);
		Asura_PlatformFileAccessUtils::CheckDiscStatus();
		Asura_FileAccess::Close(iFileTest);
		pxBinkHandle = BinkOpen(szFilename, BINKNOFRAMEBUFFERS | BINKSNDTRACK);
		if ( pxBinkHandle )
		{
			Asura_OutputToDebugger(("Got Bink handle.\n"));
			int iVol = Asura_Maths::FloatToInt(Asura_Sound_System::GetSoundGroupVolume(Asura_Sound_System::SoundGroup_SoundFX) * 32768.0f);
			if(iVol < 100) iVol = 100;//(setting volume to 0 appears to result in max volume)
			BinkSetVolume(pxBinkHandle, 0, iVol);

			BINKTEXTURESET texture_set;
			BinkGetFrameBuffersInfo( pxBinkHandle, &texture_set.bink_buffers );
			Movie_Render_Params xParams;
			
			InitialiseMovieRenderParamsFromBink(pxBinkHandle, xParams);
			if (pCB)
				pCB(xParams);

			if (  Create_Bink_textures( &texture_set ) )
			{
				// Register our locked texture pointers with Bink
				BinkRegisterFrameBuffers( pxBinkHandle, &texture_set.bink_buffers );

				while(1)
				{
					//Asura_OutputToDebugger("Loop\t");
					if(!BinkWait(pxBinkHandle))
					{
						Wait_for_Bink_textures( &texture_set );

						// Decompress the Bink frame.
						if(BinkDoFrame(pxBinkHandle))
						{
							Asura_OutputToDebugger(("Frame was skipped"));
						}

						// do we need to skip a frame?
						while ( BinkShouldSkip( pxBinkHandle ) )
						{
							BinkNextFrame( pxBinkHandle );
							BinkDoFrame( pxBinkHandle );
						}

						Sync_Bink_textures( &texture_set );

						Asura_Input::Update();

						Asura_GX::BeginFrame();

						// Draw the next frame.
						Show_frame( pxBinkHandle, &texture_set, xParams );

						if (pfnRenderSubtitleCB)
						{
							// Make render states think there's nothing loaded, so it doesn't get confused. Much.
							Asura_Render::CurrentStates.RequestTextureIndex(0, ASURA_TEXTURE_NONE);
							Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
							Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
							Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CW);
							Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);
							Asura_Render::CurrentStates.RequestAlphaTest(true);
							Asura_Render::CurrentStates.RequestAlphaTest(false);
							GXSetTevDirect(GX_TEVSTAGE0);
							GXSetTevDirect(GX_TEVSTAGE1);
							GXSetTevDirect(GX_TEVSTAGE2);
							GXSetTevDirect(GX_TEVSTAGE3);

							Asura_GX::InitVertexFormats();


							pfnRenderSubtitleCB(float(pxBinkHandle->FrameNum * pxBinkHandle->FrameRateDiv) / float(pxBinkHandle->FrameRate));
						}

						Asura_Wii_HomeButtonMenu::Render();

#if defined(_DEBUG) && defined(ASURA_USE_SCREENSHOT_SYSTEM)
						if(Asura_Input::IsDebouncedKeyPressed(0, ASURA_WII_INPUT_2))
						{
							Asura_ScreenShot_System::TakeNormalScreenShot();
						}
#endif

						Asura_GX::EndFrame();

						// Did we hit the end of the movie?
						if(pxBinkHandle->FrameNum == pxBinkHandle->Frames)
						{
							break;
						}

						// Keep playing the movie.
						BinkNextFrame(pxBinkHandle);
					}
					// Always allow skip in debug, because it gets irritating waiting.
#ifndef _DEBUG
					if(bSkipAllowed)
#endif
					{
						// Check the input status
						if(Asura_Input::IsDebouncedKeyPressed(0, ASURA_WII_INPUT_A))
						{
							// User pressed the skip button.
							break;
						}
					}
					if(Asura_PlatformFileAccessUtils::CheckDiscStatus(pxBinkHandle))
					{
						// Restart bink
						BinkClose(pxBinkHandle);
						BinkSetSoundTrack(1, &uTrackNo);
						pxBinkHandle = BinkOpen(szFilename, BINKSNDTRACK);
						int iVol = Asura_Maths::FloatToInt(Asura_Sound_System::GetSoundGroupVolume(Asura_Sound_System::SoundGroup_SoundFX) * 32768.0f);
						if(iVol < 100) iVol = 100;//(setting volume to 0 appears to result in max volume)
						BinkSetVolume(pxBinkHandle, 0, iVol);
						// Register our locked texture pointers with Bink
						BinkRegisterFrameBuffers( pxBinkHandle, &texture_set.bink_buffers );
					}
				}// end while loop

				// KOR - this call was inside the loop - seems wrong to me.
				Free_Bink_textures( &texture_set );
			}
			Asura_OutputToDebugger(("Finished playing Bink.\n"));
			BinkClose(pxBinkHandle);
		}
		else
		{
			char* szError = BinkGetError();
			Asura_OutputToDebugger(("Bink error: %s while trying to load bink from %s\n", szError, szFilename));
		}
	}
	FreeResizeBuffer();
	Asura_Render::SetEnableScreenClear(bPrevClrScr);
	Asura_Sound_System::UnpauseAllSystems();
	Asura_Wii_HomeButtonMenu::Allow(true);

//	GXSetDrawSyncCallback(pOldDrawSyncCB);
	GXSetTevDirect(GX_TEVSTAGE0);
	GXSetTevDirect(GX_TEVSTAGE1);
	GXSetTevDirect(GX_TEVSTAGE2);
	GXSetTevDirect(GX_TEVSTAGE3);

	Asura_GX::InitVertexFormats();

	return 0;
}

void Asura_Wii_Bink::UpdateVolume()
{
	if(!m_pxBinkHandle) { return; }

	float fTotalVolume = m_fVolume * Asura_Sound_System::GetSoundGroupVolume(m_eSoundGroup); 
    int iVol = Asura_Maths::FloatToInt(fTotalVolume * 32768.0f);
	if(iVol < 100) iVol = 100;//(setting volume to 0 appears to result in max volume)
    BinkSetVolume(m_pxBinkHandle, 0, iVol);
}

float Asura_Wii_Bink::GetLength()
{
	return m_fLength;
}

float Asura_Wii_Bink::GetPosition()
{
	if (m_pxBinkHandle)
	{
		Asura_Assert(m_pxBinkHandle->Frames, "Bink has no frames");
		if(m_pxBinkHandle->Frames)
		{
			return m_fLength * static_cast<float>(m_pxBinkHandle->FrameNum) / m_pxBinkHandle->Frames;
		}
	}

	return 0.f;
}

bool Asura_Wii_Bink::IsFinished()
{
	return m_bFinished;
}

void Asura_Wii_Bink::CalculateLength()
{
	if(m_pxBinkHandle)
	{
		if(m_pxBinkHandle->FrameRate)
		{
			m_fLength = static_cast<float>(m_pxBinkHandle->Frames * m_pxBinkHandle->FrameRateDiv) / m_pxBinkHandle->FrameRate;
			return;
		}
	}
	else
	{
		Asura_Assert(0, "Bink handle or frame rate null.\n");
	}
}
#endif//def ASURA_USE_BINK
