//
// Asura_Wii_Streaming_ADPCM.cpp
//
// Created by KOR - 03 October 2008
//
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Sound/Asura_Wii_Streaming_ADPCM.h"

// Asura
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"
#ifndef ASURA_SUBMISSION_BUILD
#include "Rendering/Asura_Font_System.h"
#include "Rendering/Asura_Render.h"
#endif

// Asura Wii
#include "Core/Asura_Wii_MEM2.h"
#include "FileAccess/Asura_PlatformFileAccess.h"
#include "Sound/Asura_AX.h"

// Lib
#include <revolution/ax.h>
#include <revolution/mem.h>
#include <revolution/mix.h>
#include <string.h>

static MEMHeapHandle g_xMemHeapHandle = MEM_HEAP_INVALID_HANDLE;
void Asura_Wii_Streaming_ADPCM::Initialise()
{
	Asura_Assert(g_xMemHeapHandle == MEM_HEAP_INVALID_HANDLE, "Already initialised?");

	// Allocate a unit heap for all streaming buffers, in order to avoid fragmentation.
	const u_int uUnitHeapSize = STREAM_MAX_BUFFERS * STREAM_BUFFER_BYTES;
	void* pvUnitHeapBuffer = ASURA_WII_MEM2_ALLOC(uUnitHeapSize);
	g_xMemHeapHandle = MEMCreateUnitHeapEx(pvUnitHeapBuffer, uUnitHeapSize, STREAM_BUFFER_BYTES, 32, 0);
	Asura_Assert(g_xMemHeapHandle != MEM_HEAP_INVALID_HANDLE, "Failed to allocate unit heap");
}

bool Asura_Wii_Streaming_ADPCM::Create(const char* szFilename)
{
	// Convert the wav filename to a Wii-friendly version.
	static char szBuffer[256];
	Asura_Assert(strlen(szFilename) < 256, "ADPCM Filename too long");

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

	// Try stereo first
	if(Asura_FileAccess::FileExists(szBuffer))
	{
		return CreateFromResource(szBuffer, 0, true);
	}
	else
	{
		// See if there's mono version
		REPLACE_EXTENSION(szBuffer, "dsp", 256);	
		if(Asura_FileAccess::FileExists(szBuffer))
		{
			return CreateFromResource(szBuffer, 0);
		}
	}
	return false;
}

bool Asura_Wii_Streaming_ADPCM::CreateFromResource(const char* szResourceFilename, int iOffset, bool bStereo /* = false */)
{
	Asura_Assert(!(iOffset % 32), "ADPCM is not 32 byte aligned within file");
	// Convert filename to entry number.
	Asura_OutputToDebugger(("Loading ADPCM file from %s at offset 0x%08x\n", szResourceFilename, iOffset));
	s32 iEntryNum = DVDConvertPathToEntrynum(szResourceFilename);
	Asura_Assertf(iEntryNum != -1, ("Could not open %s for reading ADPCM", szResourceFilename));
	if(iEntryNum >= 0)
	{
		return CreateFromResource(iEntryNum, iOffset, -1, bStereo);
	}
	return false;
}

bool Asura_Wii_Streaming_ADPCM::CreateFromResource(int iDVDEntryNum, int iOffset, int iSize /* = -1 */, bool bStereo /* = false */)
{
	// Sanity checks.
	Asura_Assert(STREAM_IDLE == m_eStreamState, "Unexpected stream state");
	
	m_bStereo = bStereo;
	const u_int uNumChannels = GetNumChannels();

	// Open file
	if(!DVDFastOpen(iDVDEntryNum, &m_xFileInfo))
	{
		Asura_Assert(0, "Failed to open stream file");
		return false;
	}

	// Acquire voice
	for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
	{
		Asura_Assert(!m_apxAXVPB[uChannel], "Voice leak in streaming ADPCM");
		Asura_Assert(!m_apcBuffer[uChannel], "Memory leak in streaming ADPCM");
		m_apxAXVPB[uChannel] = AXAcquireVoice(AX_PRIORITY_NODROP, NULL, 0);
		if(!m_apxAXVPB[uChannel])
		{
			Asura_Assert(0, "Failed to acquire voice for streamed sound");
			return false;
		}
		// Allocate buffer
		m_apcBuffer[uChannel] = static_cast<char*>(MEMAllocFromUnitHeap(g_xMemHeapHandle));
	}


	if(iSize > 0)
	{
		m_uRemainingBytes = iSize;
	}
	else
	{
		// Get size from filesize.
		m_uRemainingBytes = DVDGetLength(&m_xFileInfo);
	}
	if(m_bStereo)
	{
		Asura_Assert(!(m_uRemainingBytes % 2), "Stereo file is an odd number of bytes");
		m_uRemainingBytes /= 2;
	}
	const u_int uRemainingBytesRounded = OSRoundUp32B(m_uRemainingBytes);
	const u_int uBytesToRead = Asura_Maths::Min(static_cast<u_int>(STREAM_CHUNK_BYTES), uRemainingBytesRounded);

	m_uCurrentFileOffset = iOffset;
	m_eStreamState = STREAM_LOADING_HEADER;

	// Reset flags
	m_bPaused = false;
	m_bFinished = false;
	m_uCurrentPlayChunk = 0;
	m_uCurrentLoadChunk = 0;
	m_uCurrentLoadChannel = 0;
	m_uBufferOffset = 0;
	m_uCurrentBuffer = 0;
	m_uEndChunk = m_uRemainingBytes / STREAM_CHUNK_BYTES;

	// Kick off the header load for the left channel
	Asura_Assert(sizeof(DSPADPCM) < uBytesToRead, "File is smaller than ADPCM header");
	DVDReadAsync(&m_xFileInfo, m_apcBuffer[0], uBytesToRead, m_uCurrentFileOffset, NULL);
	m_uCurrentFileOffset += uBytesToRead;
	if(m_uRemainingBytes > uBytesToRead)
	{
		m_uRemainingBytes -= uBytesToRead;
	}
	else
	{
		// We've read the whole thing in.
		for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
		{
			m_auEndAddress[uChannel] = OSCachedToPhysical(m_apcBuffer[uChannel]) + m_uRemainingBytes;
		}
		m_uRemainingBytes = 0;
	}

	return true;
}

void Asura_Wii_Streaming_ADPCM::Play(bool bLooping)
{
	Asura_Assert(m_eStreamState != STREAM_IDLE, "Unexpected state");
	m_bLooping = bLooping;
	m_bPaused = false;
	if(m_eStreamState == STREAM_LOADED_HEADER)
	{
		// We have already loaded the header
		BeginPlay();
	}
}

void Asura_Wii_Streaming_ADPCM::BeginPlay()
{
	if(m_bStereo)
	{
		InitialiseChannel(1);
	}
	InitialiseChannel(0);
}

void Asura_Wii_Streaming_ADPCM::InitialiseChannel(u_int uChannel)
{
	Asura_Assert(uChannel < GetNumChannels(), "Channel out of range");
	const u_int uPan = m_bStereo ? (uChannel * 127) : 64;

	const float fTotalVolume = m_fVolume;
	const float fDecibels = Asura_AX::LinearVolumeToDecibels(fTotalVolume);
	const int iDecibelTenths = static_cast<int>(fDecibels * 10.f);
	MIXInitChannel(
		m_apxAXVPB[uChannel],// pointer to voice
		0, //MIX_MODE_MUTE,	// initial aux A, B, mute modes
		iDecibelTenths,	// initial input atten / gain
		-960,			// initial aux A atten / gain
		-960,			// initial aux B atten / gain
		-960,			// initial aux C atten / gain
		uPan,			// initial pan 
		127,			// initial span
		0);				// initial fader atten / gain

	InitialiseVoice(uChannel);
}

void Asura_Wii_Streaming_ADPCM::InitialiseVoice(u_int uChannel)
{
	Asura_Assert(uChannel < GetNumChannels(), "Channel out of range");

	const u_int uNumChannels = GetNumChannels();

	// Construct the voice based on the header information
	const DSPADPCM* pxHeader = reinterpret_cast<const DSPADPCM*>(m_apcBuffer[uChannel] + m_uBufferOffset * STREAM_CHUNK_BYTES);
	u_int uLoopStartAddress = OSCachedToPhysical(m_apcBuffer[uChannel]) << 1;
	u_int uLoopEndAddress = uLoopStartAddress + (STREAM_BUFFER_BYTES << 1) - 1;
	u_int uCurrentAddress = (OSCachedToPhysical(pxHeader) << 1) + (sizeof(DSPADPCM) << 1) + pxHeader->sa;

	AXPBADDR   xAddr;
	AXPBADPCM  xAdpcm;

	uLoopStartAddress += 2; // Skip the frame header.

	xAddr.format           = AX_PB_FORMAT_ADPCM;
	xAddr.currentAddressHi = (u16)(uCurrentAddress >> 16);
	xAddr.currentAddressLo = (u16)(uCurrentAddress &  0xFFFF);

	// Check whether there's room for the whole thing.
	m_bEntirelyInMemory = (m_uRemainingBytes <= STREAM_CHUNK_BYTES * (STREAM_NUM_CHUNKS - 1) );

	if(m_bEntirelyInMemory)
	{
		if(m_uRemainingBytes)
		{
			// Work out where the end address is going to be.
			m_auEndAddress[uChannel] = OSCachedToPhysical(m_apcBuffer[uChannel]) + STREAM_CHUNK_BYTES + m_uRemainingBytes;
		}
		const u_int uEndAddress = (m_auEndAddress[uChannel] << 1) - 1;
		AXSetVoiceType(m_apxAXVPB[uChannel], AX_PB_TYPE_NORMAL);
		if(m_bLooping)
		{
			// Entire stream fits in buffer, so we can simply loop over the whole thing.
			xAddr.loopFlag         = AXPBADDR_LOOP_ON;
			xAddr.loopAddressHi    = (u16)(uCurrentAddress >> 16);
			xAddr.loopAddressLo    = (u16)(uCurrentAddress &  0xFFFF);
			xAddr.endAddressHi     = (u16)(uEndAddress >> 16);
			xAddr.endAddressLo     = (u16)(uEndAddress &  0xFFFF);

			// Set up the loop context
			AXPBADPCMLOOP xAXPBAdpcmLoop;
			xAXPBAdpcmLoop.loop_pred_scale	= pxHeader->ps;
			xAXPBAdpcmLoop.loop_yn1			= pxHeader->yn1;
			xAXPBAdpcmLoop.loop_yn2			= pxHeader->yn2;
			AXSetVoiceAdpcmLoop(m_apxAXVPB[uChannel], &xAXPBAdpcmLoop);
		}
		else
		{
			// One shot - set up non looping
			const u_int uZeroBuffer = Asura_AX::GetZeroBuffer();
			xAddr.loopFlag         = AXPBADDR_LOOP_OFF;
			xAddr.loopAddressHi    = (u16)(uZeroBuffer >> 16);
			xAddr.loopAddressLo    = (u16)(uZeroBuffer &  0xFFFF);
			xAddr.endAddressHi     = (u16)(uEndAddress >> 16);
			xAddr.endAddressLo     = (u16)(uEndAddress &  0xFFFF);
		}
	}
	else
	{
		// Set up streaming loop
		xAddr.loopFlag         = AXPBADDR_LOOP_ON;
		xAddr.loopAddressHi    = (u16)(uLoopStartAddress >> 16);
		xAddr.loopAddressLo    = (u16)(uLoopStartAddress &  0xFFFF);
		xAddr.endAddressHi     = (u16)(uLoopEndAddress >> 16);
		xAddr.endAddressLo     = (u16)(uLoopEndAddress &  0xFFFF);
		AXSetVoiceType    (m_apxAXVPB[uChannel], AX_PB_TYPE_STREAM); // No loop context.
		m_auEndAddress[uChannel] = 0;
	}
	m_bSetEndAddress = m_bEntirelyInMemory;

	Asura_Mem::Copy(xAdpcm.a, pxHeader->coef, 8 * 2 * sizeof(u16));
	xAdpcm.gain			= pxHeader->gain; 
	xAdpcm.pred_scale	= pxHeader->ps;
	xAdpcm.yn1			= pxHeader->yn1;
	xAdpcm.yn2			= pxHeader->yn2;

	AXSetVoiceAdpcm   (m_apxAXVPB[uChannel], &xAdpcm);
	AXSetVoiceAddr    (m_apxAXVPB[uChannel], &xAddr);
	AXSetVoiceSrcType (m_apxAXVPB[uChannel], AX_SRC_TYPE_4TAP_16K);
	AXSetVoiceSrcRatio(m_apxAXVPB[uChannel], static_cast<float>(pxHeader->sample_rate) * (1.f / 32000.0f));
	AXSetVoiceState   (m_apxAXVPB[uChannel], AX_PB_STATE_RUN);

	m_eStreamState = STREAM_PLAYING;
}

void Asura_Wii_Streaming_ADPCM::Update()
{
	switch(m_eStreamState)
	{
		case STREAM_IDLE:
		{
			return;
		}
		case STREAM_LOADING_HEADER:
		{
			Asura_Assert(m_uCurrentLoadChunk == 0, "Unexpected load chunk");
			if(IsDVDCommandFinished())
			{
				if(m_uCurrentLoadChannel < GetNumChannels() - 1)
				{
					// Kick off the load of the next channel
					++m_uCurrentLoadChannel;
					DVDReadAsync(&m_xFileInfo, m_apcBuffer[m_uCurrentLoadChannel], m_uCurrentFileOffset, m_uCurrentFileOffset, NULL);
					break;
				}
				if(m_bPaused)
				{
					// Wait until a play call before beginning
					m_eStreamState = STREAM_LOADED_HEADER;
				}
				else
				{
					// Play has already been requested.
					BeginPlay();
				}
			}
			break;
		}
		case STREAM_LOADED_HEADER:
		{
			// We are just waiting for a play command.
			// Fill up the buffer while we're waiting.
			FillAvailableChunks();
			break;
		}
		case STREAM_PLAYING:
		{
			const u_int uNumChannels = GetNumChannels();
			const u_int uPlayPos = GetCurrentPlayPos(0);
			if(m_bSetEndAddress)
			{
				const u_int uStartAddress = OSCachedToPhysical(m_apcBuffer[0]);
				// Check whether we've been kicked into zero buffer.
				if(uPlayPos < uStartAddress)
				{
					if(m_bLooping)
					{
						Asura_Assert(!m_bEntirelyInMemory, "Should not have reached zero buffer if looping and entirely in buffer");
						// Reset to the start
						m_uCurrentPlayChunk = 0;
						m_uCurrentBuffer = m_uBufferOffset;

						if(m_bStereo)
						{
							InitialiseVoice(1);
						}
						InitialiseVoice(0);
					}
					else
					{
						for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
						{
							AXSetVoiceState(m_apxAXVPB[uChannel], AX_PB_STATE_STOP);
						}
						m_eStreamState = STREAM_IDLE;
						m_bFinished = true;
					}
					break;
				}
			}

			const u_int uCurrentBuffer = GetBufferFromPosition(uPlayPos, 0);
			if(uCurrentBuffer != m_uCurrentBuffer)
			{
				m_uCurrentBuffer = uCurrentBuffer;
				++m_uCurrentPlayChunk;
				if(uCurrentBuffer == STREAM_NUM_CHUNKS - 1 && !m_bEntirelyInMemory)
				{
					for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
					{
						// Make sure the loop predictor and scale is set correctly.
						AXPBADPCMLOOP xLoop;

						xLoop.loop_pred_scale    = (u16)(*((u8*)(m_apcBuffer[uChannel])));
						xLoop.loop_yn1           = 0;
						xLoop.loop_yn2           = 0;

						AXSetVoiceAdpcmLoop(m_apxAXVPB[uChannel], &xLoop);
					}
				}
			}
			FillAvailableChunks();
			if(!m_bSetEndAddress && m_uCurrentPlayChunk == m_uEndChunk)
			{
				for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
				{
					AXSetVoiceEndAddr(m_apxAXVPB[uChannel], m_auEndAddress[uChannel] << 1);
					AXSetVoiceLoop(m_apxAXVPB[uChannel], AXPBADDR_LOOP_OFF);
					AXSetVoiceLoopAddr(m_apxAXVPB[uChannel], Asura_AX::GetZeroBuffer());
				}
				m_bSetEndAddress = true;
			}
			break;
		}
	}
}

void Asura_Wii_Streaming_ADPCM::FillAvailableChunks()
{
	// Don't queue up multiple reads.
	if(!IsDVDCommandFinished())
	{
		return;
	}
	const u_int uNumChannels = GetNumChannels();
	if(m_uRemainingBytes)
	{
		// Try to fill all the way up to the previous chunk in the ring buffer
		const u_int uBlocksAhead = m_uCurrentLoadChunk - m_uCurrentPlayChunk;
		if(uBlocksAhead < (STREAM_NUM_CHUNKS - 1) || m_uCurrentLoadChannel < uNumChannels - 1)
		{
			if(m_uCurrentLoadChannel == uNumChannels - 1)
			{
				++m_uCurrentLoadChunk;
			}
			LoadNextChunk((m_uCurrentLoadChunk + m_uBufferOffset) % STREAM_NUM_CHUNKS);
		}
	}
	else
	{
		// No remaining data - check whether we're looping.
		if(m_bLooping && !m_bEntirelyInMemory)
		{
			if(m_uCurrentPlayChunk == m_uEndChunk - 1)
			{
				// We should have the buffer after the end chunk free
				// to load the start back in again.
				m_uRemainingBytes = m_uCurrentFileOffset;
				m_uCurrentFileOffset = 0;
				m_uCurrentLoadChunk = 0;
				// Recalculate buffer offset.
				m_uBufferOffset = (m_uEndChunk + m_uBufferOffset + 1) % STREAM_NUM_CHUNKS;
				LoadNextChunk(m_uBufferOffset);
			}
		}
	}
}

u_int Asura_Wii_Streaming_ADPCM::GetCurrentPlayPos(u_int uChannel) const
{
	if(!m_apxAXVPB[uChannel])
	{
		return 0;
	}
	// Shift to convert nibble address to byte.
	const u_int uCurrentAddress = ((m_apxAXVPB[uChannel]->pb.addr.currentAddressHi << 16) | (m_apxAXVPB[uChannel]->pb.addr.currentAddressLo)) >> 1;
	return uCurrentAddress;
}

u_int Asura_Wii_Streaming_ADPCM::GetBufferFromPosition(u_int uPosition, u_int uChannel) const
{
	const u_int uStartAddress = OSCachedToPhysical(m_apcBuffer[uChannel]);
	const u_int uOffset = (uPosition - uStartAddress);
	const u_int uCurrentBuffer = uOffset / STREAM_CHUNK_BYTES;
	Asura_Assert(uCurrentBuffer < STREAM_NUM_CHUNKS, "Position outside buffer");
	return uCurrentBuffer;
}

u_int Asura_Wii_Streaming_ADPCM::GetCurrentBuffer() const
{
	// Should be the same for both - just check left channel regardless
	return GetBufferFromPosition(GetCurrentPlayPos(0), 0);
}

DEBUGVAR bool dbg_bPrintChunkLoads = false;

void Asura_Wii_Streaming_ADPCM::LoadNextChunk(u_int uBufferToLoad)
{
	Asura_Assert(m_uRemainingBytes, "Shouldn't be attempting to load any further data");

#ifdef ASURA_ASSERT
	const u_int uFileSize = DVDGetLength(&m_xFileInfo);
	Asura_Assert(uFileSize > m_uCurrentFileOffset, "Offset out of range");
#endif
	const u_int uNumChannels = GetNumChannels();

	// Get the next channel to load
	++m_uCurrentLoadChannel;
	m_uCurrentLoadChannel = m_uCurrentLoadChannel % uNumChannels;

	if(dbg_bPrintChunkLoads)
	{
		OSReport("Loading chunk %u, channel %u into buffer %u\n", m_uCurrentLoadChunk, m_uCurrentLoadChannel, uBufferToLoad);
	}

	const u_int uBufferOffset = uBufferToLoad * STREAM_CHUNK_BYTES;
	const u_int uBytesToRead = Asura_Maths::Min(static_cast<u_int>(STREAM_CHUNK_BYTES), m_uRemainingBytes);
	const u_int uFileOffset = m_uCurrentFileOffset * GetNumChannels() + m_uCurrentLoadChannel * uBytesToRead;
	const u_int uBytesToReadRounded = OSRoundUp32B(uBytesToRead);
	DVDReadAsync(&m_xFileInfo, m_apcBuffer[m_uCurrentLoadChannel] + uBufferOffset, uBytesToReadRounded, uFileOffset, NULL);
	if(m_uCurrentLoadChannel == uNumChannels - 1)
	{
		// Only move the counters along once all channels are done
		m_uCurrentFileOffset += uBytesToRead;
		if(m_uRemainingBytes > uBytesToRead)
		{
			m_uRemainingBytes -= uBytesToRead;
		}
		else
		{
			// We've read the whole thing in.
			for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
			{
				m_auEndAddress[uChannel] = OSCachedToPhysical(m_apcBuffer[uChannel]) + uBufferOffset + m_uRemainingBytes;
			}
			m_uRemainingBytes = 0;
		}
	}
}

void Asura_Wii_Streaming_ADPCM::Stop()
{
	// TODO - return to start.
	const u_int uNumChannels = GetNumChannels();
	for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
	{
		AXSetVoiceState(m_apxAXVPB[uChannel], AX_PB_STATE_STOP);
	}
}

void Asura_Wii_Streaming_ADPCM::Release()
{
	while(!IsDVDCommandFinished())
	{
		// Temporary fix for crashes when transitioning between tracks;
		// wait until we are done with our file before closing it.
		// We ought to be queueing the commands.
	}
	const u_int uNumChannels = GetNumChannels();
	for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
	{
		MIXReleaseChannel(m_apxAXVPB[uChannel]);
		AXFreeVoice(m_apxAXVPB[uChannel]);
		m_apxAXVPB[uChannel] = NULL;
		MEMFreeToUnitHeap(g_xMemHeapHandle, m_apcBuffer[uChannel]);
		m_apcBuffer[uChannel] = NULL;
	}
	m_eStreamState = STREAM_IDLE;
}

void Asura_Wii_Streaming_ADPCM::Pause()
{
	if(STREAM_PLAYING == m_eStreamState)
	{
		const u_int uNumChannels = GetNumChannels();
		for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
		{
			AXSetVoiceState(m_apxAXVPB[uChannel], AX_PB_STATE_STOP);
		}
	}
}

void Asura_Wii_Streaming_ADPCM::Resume()
{
	if(STREAM_PLAYING == m_eStreamState)
	{
		const u_int uNumChannels = GetNumChannels();
		for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
		{
			AXSetVoiceState(m_apxAXVPB[uChannel], AX_PB_STATE_RUN);
		}
	}
}

void Asura_Wii_Streaming_ADPCM::SetVolume(float fVolume)
{
	m_fVolume = fVolume;
	UpdateVolume();
}

void Asura_Wii_Streaming_ADPCM::UpdateVolume()
{
	const float fTotalVolume = m_fVolume;
	const float fDecibels = Asura_AX::LinearVolumeToDecibels(fTotalVolume);
	const int iDecibelTenths = static_cast<int>(fDecibels * 10.f);
	const u_int uNumChannels = GetNumChannels();
	for(u_int uChannel = 0; uChannel < uNumChannels; ++uChannel)
	{
		if(m_apxAXVPB[uChannel])
		{
			// MIXSetInput takes tenths of a decibel.
			MIXSetInput(m_apxAXVPB[uChannel], iDecibelTenths);
			MIXUnMute(m_apxAXVPB[uChannel]);
		}
	}
}

float Asura_Wii_Streaming_ADPCM::GetLength() const
{
	return 0.f;
}

float Asura_Wii_Streaming_ADPCM::GetPosition() const
{
	return 0.f;
}

bool Asura_Wii_Streaming_ADPCM::IsFinished() const
{
	return m_bFinished;
}

bool Asura_Wii_Streaming_ADPCM::IsDVDCommandFinished() const
{
	const int iResult = DVDGetFileInfoStatus(&m_xFileInfo);	
	return (iResult == DVD_STATE_END);
}

#ifndef ASURA_SUBMISSION_BUILD
DEBUGVAR Asura_Vector_2 dbg_xTopLeft(80.f, 80.f);
DEBUGVAR Asura_Vector_2 dbg_xChunkSize(100.f, 10.f);
DEBUGVAR float dbg_fBorderSize = 1.f;
void Asura_Wii_Streaming_ADPCM::DebugRender() const
{
	if(m_eStreamState == STREAM_IDLE)
	{
		return;
	}
	// Common render state.
	Asura_Overlay_Quad xQuad;
	xQuad.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;
	xQuad.TextureIndex = ASURA_TEXTURE_NONE;
	xQuad.SetRectUV(0.f, 0.f, 1.f, 1.f);

	for(u_int uChannel = 0; uChannel < GetNumChannels(); ++uChannel)
	{
		const float fYOffset = uChannel * (dbg_xChunkSize.y * 2.f);
		const u_int uPlayPos = GetCurrentPlayPos(uChannel);
		Asura_Vector_2 xPosition(dbg_xTopLeft);
		for(u_int uChunk = 0; uChunk < STREAM_NUM_CHUNKS; ++uChunk)
		{
			// TODO - this is incorrect when looping
			const u_int uCurrentLoadingChunk = (m_uCurrentLoadChunk + m_uBufferOffset) % 3;
			const bool bLoadingChunk = (uChannel == m_uCurrentLoadChannel) && (uChunk == uCurrentLoadingChunk) && !IsDVDCommandFinished();
			xQuad.Colour = bLoadingChunk ? 0xffff0000 : 0xff00ff00;
			xQuad.SetRectXY(xPosition.x + dbg_fBorderSize,
				xPosition.y + dbg_fBorderSize + fYOffset,
				dbg_xChunkSize.x - dbg_fBorderSize * 2.f,
				dbg_xChunkSize.y - dbg_fBorderSize * 2.f);
			xQuad.Render();

			xPosition.x += dbg_xChunkSize.x;
		}
		if(m_eStreamState == STREAM_PLAYING)
		{
			// Now render play position
			const u_int uStartAddress = OSCachedToPhysical(m_apcBuffer[uChannel]);
			const u_int uOffset = (uPlayPos - uStartAddress);
			const float fPlayPosition = dbg_xChunkSize.x * static_cast<float>(uOffset) / STREAM_CHUNK_BYTES;
			const Asura_Vector_2 xPlayLine0(dbg_xTopLeft.x + fPlayPosition, dbg_xTopLeft.y + fYOffset);
			const Asura_Vector_2 xPlayLine1(dbg_xTopLeft.x + fPlayPosition, dbg_xTopLeft.y + dbg_xChunkSize.y + fYOffset);
			Asura_Render::Render2DLine(xPlayLine0, xPlayLine1, 0xffffffff);
		}
		if(m_auEndAddress[uChannel])
		{
			// Render end position
			const u_int uStartAddress = OSCachedToPhysical(m_apcBuffer[uChannel]);
			const u_int uOffset = (m_auEndAddress[uChannel] - uStartAddress);
			const float fEndPosition = dbg_xChunkSize.x * static_cast<float>(uOffset) / STREAM_CHUNK_BYTES;
			const Asura_Vector_2 xEndLine0(dbg_xTopLeft.x + fEndPosition, dbg_xTopLeft.y + fYOffset);
			const Asura_Vector_2 xEndLine1(dbg_xTopLeft.x + fEndPosition, dbg_xTopLeft.y + dbg_xChunkSize.y + fYOffset);
			Asura_Render::Render2DLine(xEndLine0, xEndLine1, 0xff0000ff);
		
		}
	}
	Asura_Vector_2 xTextPos = dbg_xTopLeft;
	xTextPos.y -= Asura_Font_System::GetFontHeight();
	Asura_Font_System::Print(xTextPos.x, xTextPos.y, 0xffffffff, ASURA_TEXTFORMAT_FORMATSTRING, "Playing %u - Loading %u", m_uCurrentPlayChunk, m_uCurrentLoadChunk);
}
#endif
