//
// Asura_Wii_NAND.cpp
//
// Created by KOR - 14 June 2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "LoadAndSave/Asura_Wii_NAND.h"

// Asura
#include "Core/Asura_Maths.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"

// Library
#include <revolution/nand.h>
#include <revolution/tpl.h>
#include <string.h>

// Macro for calling a NAND function and checking its result.
// Not suitable for use when you actually need to branch based on result.
#define ASURA_WII_NAND_CALL(__NANDFunc) \
	{ \
		const int iResult = __NANDFunc; \
		if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback) \
		{ \
			s_pfnNANDCorruptCallback(); \
		} \
		Asura_Assertf(iResult == NAND_RESULT_OK, (#__NANDFunc " failed; return value: %i", iResult)); \
	}

// Statics
volatile ASURA_WII_NAND_STATE	Asura_Wii_NAND::s_eState = ASURA_WII_NAND_IDLE;
Asura_Wii_NAND_WriteFileData	Asura_Wii_NAND::s_xWriteFileData;
Asura_Wii_NAND_ReadFileData		Asura_Wii_NAND::s_xReadFileData;
// Buffers
#ifdef ASURA_WII_NAND_USE_SAFE_CALLS 
char							Asura_Wii_NAND::s_acNandSafeOpenCopyBuffer[uASURA_WII_NAND_SAFEOPEN_BUFFER_SIZE] __attribute__ ((aligned (32)));
#endif
char							Asura_Wii_NAND::s_acNandIOBuffer[uASURA_WII_NAND_IO_BUFFER_SIZE] __attribute__ ((aligned (32)));
// Directory storage
char							Asura_Wii_NAND::s_acDirectoryReadBuffer[OSRoundUp32B(uASURA_WII_NAND_MAX_FILES_PER_DIR * (NAND_MAX_NAME + 1))] = { 0 };
const char*						Asura_Wii_NAND::s_aszFilenames[uASURA_WII_NAND_MAX_FILES_PER_DIR] = { NULL };
u32								Asura_Wii_NAND::s_uNumFilenames = 0;
NANDCorruptCallback*			Asura_Wii_NAND::s_pfnNANDCorruptCallback = NULL;

static NANDCommandBlock		g_xCommandBlock;
static NANDBanner			g_xBanner __attribute__ ((aligned (32)));

void Asura_Wii_NAND::Initialise()
{
#if 0
	Asura_Assert(BytesToNandBlocks(0) == 0, "");
	Asura_Assert(BytesToNandBlocks(1) == 1, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE - 1) == 1, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE + 0) == 1, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE + 1) == 2, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE * 8 - 1) == 8, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE * 8 + 0) == 8, "");
	Asura_Assert(BytesToNandBlocks(NAND_FSBLOCK_SIZE * 8 + 1) == 9, "");
	RunTests();
#endif
}

void Asura_Wii_NAND::Shutdown()
{
	// Empty for now. 
}

void Asura_Wii_NAND::HandleReadError(s32 iResult, NANDCommandBlock* pxCloseCommandBlock /* = NULL */)
{
	// Check for fatal corruption.
	if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
	{
		s_pfnNANDCorruptCallback();
	}

	// Close file if necessary
	if(pxCloseCommandBlock)
	{
		NANDCloseAsync(&s_xReadFileData.m_xFileInfo, ReadCallback_CloseError, pxCloseCommandBlock);
	}
	else
	{
		SetState(ASURA_WII_NAND_ERROR);
	}
}

void Asura_Wii_NAND::ReadCallback_CloseError(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	SetState(ASURA_WII_NAND_ERROR);
}



bool Asura_Wii_NAND::ReadFile(const char* szFilename, void* pvBuffer, u_int uBufferSize, bool bAsync /* = false */, u_int uOffset /* = 0 */)
{
	Asura_Assert(!IsBusy(), "Operation already in progress");

	if(IsBusy()) { return false; }

	if(bAsync)
	{
		Asura_Assert(!uOffset, "Offset not implemented in async read");

		s_xReadFileData.m_pcBuffer = reinterpret_cast<char*>(pvBuffer);
		strncpy(s_xReadFileData.m_szFilename, szFilename, NAND_MAX_NAME + 1);
		s_xReadFileData.m_uBufferSize = uBufferSize;

		SetState(ASURA_WII_NAND_READ);
		const int iOpenResult = NANDOpenAsync(szFilename, &s_xReadFileData.m_xFileInfo, NAND_ACCESS_READ, ReadCallback_GetLength, &g_xCommandBlock);
		if(iOpenResult == NAND_RESULT_OK)
		{
			return true;
		}
		else
		{			
			Asura_Assertf(0, ("Failed to open NAND file: result %i", iOpenResult));
			HandleReadError(iOpenResult, false);
			return false;
		}
		// Shouldn't reach here.
	}

	NANDFileInfo xNandInfo;
	const int iOpenResult = NANDOpen(szFilename, &xNandInfo, NAND_ACCESS_READ);
	if(iOpenResult != NAND_RESULT_OK)
	{
		if(iOpenResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
		}
		return false;
	}

	u32 uFileLength = 0;
	const int iLengthResult = NANDGetLength(&xNandInfo, &uFileLength);
	if(iLengthResult != NAND_RESULT_OK)
	{
		return false;
	}

	Asura_Assert(uOffset < uFileLength, "Read offset is greater than file length");
	if(uOffset)
	{
		const u_int uSeekResult = NANDSeek(&xNandInfo, uOffset, NAND_SEEK_SET);
		if(uSeekResult != uOffset)
		{
			if(uSeekResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
			{
				s_pfnNANDCorruptCallback();
			}
			Asura_Assert(0, "Seek in NAND read failed");
			return false;
		}
	}

	const u32 uFileSizeRemaining = uFileLength - uOffset;
	const u32 uTotalLengthToRead = Asura_Maths::Min(uFileSizeRemaining, (u32)uBufferSize);

	// NAND reads require 32 byte alignment and size multiple.
	const bool bCanReadDirect = ((reinterpret_cast<u_int>(pvBuffer) % 32) == 0) && ((uBufferSize % 32) == 0);

	bool bSuccess = false;
	if(bCanReadDirect && uTotalLengthToRead == uFileSizeRemaining)
	{
		Asura_OutputToDebugger(("Asura_Wii_NAND::ReadFile - fast read to aligned memory\n"));
		// If the buffer supplied is 32 byte aligned, we can read straight in
		const int iReadResult = NANDRead(&xNandInfo, pvBuffer, OSRoundUp32B(uFileSizeRemaining));
		Asura_Assert(iReadResult == uFileSizeRemaining, "Failed to read NAND file");
		if(iReadResult == uFileSizeRemaining)
		{
			bSuccess = true;
		}
		else if(iReadResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
		}
	}
	else
	{
		Asura_OutputToDebugger(("Asura_Wii_NAND::ReadFile - slow buffered read to non-aligned memory\n"));
		// Otherwise we need to copy it via an intermediate aligned buffer.
		u_int uPosition = 0;
		u_int uBytesRemaining = uTotalLengthToRead;
		while(uBytesRemaining)
		{
			const u_int uBytesToRead = Asura_Maths::Min(uASURA_WII_NAND_IO_BUFFER_SIZE, uBytesRemaining);
			const int iReadResult = NANDRead(&xNandInfo, s_acNandIOBuffer, OSRoundUp32B(uBytesToRead));
			if(iReadResult != uBytesToRead)
			{
				if(iReadResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
				{
					s_pfnNANDCorruptCallback();
				}
				Asura_Assert(0, "Failed to read NAND file");
				break;
			}
			Asura_Mem::Copy(static_cast<char*>(pvBuffer) + uPosition, s_acNandIOBuffer, uBytesToRead);
			uPosition += uBytesToRead;
			uBytesRemaining -= uBytesToRead;
		}
		Asura_Assert(uPosition == uTotalLengthToRead, "Unexpected file position after NAND read");
		bSuccess = (uBytesRemaining == 0);
	}

	ASURA_WII_NAND_CALL(NANDClose(&xNandInfo));

	return bSuccess;
}

void Asura_Wii_NAND::ReadCallback_GetLength(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	Asura_Assertf(iResult == NAND_RESULT_OK, ("Open for read failed: result %i", iResult));
	if(iResult != NAND_RESULT_OK)
	{
		HandleReadError(iResult, pxCommandBlock);
		return;
	}

	ASURA_WII_NAND_CALL(NANDGetLengthAsync(&s_xReadFileData.m_xFileInfo, &s_xReadFileData.m_uBytesRemaining, ReadCallback_Read, pxCommandBlock));
}

void Asura_Wii_NAND::ReadCallback_Read(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	// Result should be result of getlength or bytes read
	Asura_Assertf(iResult >= 0, ("GetLength or read failed: result %i", iResult));
	if(iResult < 0)
	{
		HandleReadError(iResult, pxCommandBlock);
		return;
	}

	const u_int uFileLength = s_xReadFileData.m_uBytesRemaining;
	const u_int uBufferSize = s_xReadFileData.m_uBufferSize;
	if(iResult >= 0 && uFileLength <= uBufferSize)
	{
		char* pcData = s_xReadFileData.m_pcBuffer;
		if(!(reinterpret_cast<u_int>(pcData) % 32))
		{
			// Read straight in.
			ASURA_WII_NAND_CALL(NANDReadAsync(&s_xReadFileData.m_xFileInfo, pcData, OSRoundUp32B(uFileLength), ReadCallback_Close, pxCommandBlock));
		}
		else
		{
			// Otherwise we need to copy it via an intermediate aligned buffer.
			const u_int uBytesToRead = OSRoundUp32B(Asura_Maths::Min(uASURA_WII_NAND_IO_BUFFER_SIZE, uFileLength));
			const bool bContinue = (uBytesToRead < uFileLength);
			ASURA_WII_NAND_CALL(NANDReadAsync(&s_xReadFileData.m_xFileInfo, s_acNandIOBuffer, uBytesToRead, bContinue ? ReadCallback_ReadBuffered : ReadCallback_CloseBuffered, pxCommandBlock));
		}		
	}
	else
	{
		Asura_Assertf(0, ("Buffer passed into ReadFileAsync is too small (file: %u, buffer %u)", uFileLength, uBufferSize));
		ASURA_WII_NAND_CALL(NANDCloseAsync(&s_xReadFileData.m_xFileInfo, ReadCallback_Finish, pxCommandBlock));
	}
}

void Asura_Wii_NAND::ReadCallback_ReadBuffered(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	// Result should be bytes read
	Asura_Assertf(iResult > 0, ("Read failed: result %i", iResult));
	if(iResult <= 0)
	{
		HandleReadError(iResult, pxCommandBlock);
		return;
	}

	// Copy out of intermediate buffer
	Asura_Mem::Copy(s_xReadFileData.m_pcBuffer, s_acNandIOBuffer, iResult);
	s_xReadFileData.m_pcBuffer += iResult;
	s_xReadFileData.m_uBytesRemaining -= iResult;

	const u_int uBytesRemaining = s_xReadFileData.m_uBytesRemaining;
	const u_int uBytesToRead = OSRoundUp32B(Asura_Maths::Min(uASURA_WII_NAND_IO_BUFFER_SIZE, uBytesRemaining));
	const bool bContinue = (uBytesToRead < s_xReadFileData.m_uBytesRemaining);
	ASURA_WII_NAND_CALL(NANDReadAsync(&s_xReadFileData.m_xFileInfo, s_acNandIOBuffer, uBytesToRead, bContinue ? ReadCallback_ReadBuffered : ReadCallback_CloseBuffered, pxCommandBlock));
}

void Asura_Wii_NAND::ReadCallback_Close(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	// Result should be bytes read.
	Asura_Assertf((iResult == s_xReadFileData.m_uBytesRemaining), ("Failed to read NAND file: result %i, bytes remaining %u", iResult, s_xReadFileData.m_uBytesRemaining));
	if(iResult != s_xReadFileData.m_uBytesRemaining)
	{
		HandleReadError(iResult, pxCommandBlock);
		return;
	}

	ASURA_WII_NAND_CALL(NANDCloseAsync(&s_xReadFileData.m_xFileInfo, ReadCallback_Finish, pxCommandBlock));
}

void Asura_Wii_NAND::ReadCallback_CloseBuffered(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	// Result should be bytes read.
	Asura_Assertf((iResult == s_xReadFileData.m_uBytesRemaining), ("Failed to read NAND file: result %i, bytes remaining %u", iResult, s_xReadFileData.m_uBytesRemaining));
	if(iResult != s_xReadFileData.m_uBytesRemaining)
	{
		HandleReadError(iResult, pxCommandBlock);
		return;
	}

	// Copy out of intermediate buffer
	Asura_Mem::Copy(s_xReadFileData.m_pcBuffer, s_acNandIOBuffer, iResult);

	ASURA_WII_NAND_CALL(NANDCloseAsync(&s_xReadFileData.m_xFileInfo, ReadCallback_Finish, pxCommandBlock));
}

void Asura_Wii_NAND::ReadCallback_Finish(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsReading(), "Unexpected state");
	Asura_Assertf((iResult == NAND_RESULT_OK), ("Failed to close NAND file after read: result %i", iResult));
	if(iResult != NAND_RESULT_OK)
	{
		HandleReadError(iResult);
		return;
	}

	// Finished
	SetState(ASURA_WII_NAND_IDLE);
}

bool Asura_Wii_NAND::WriteFile(const char* szFilename, const void* pvData, u_int uSize, bool bOverwrite /* = true */, bool bAsync /* = false */, u_int uOffset /* = 0 */)
{
	Asura_Assert(!IsBusy(), "Operation already in progress");
	Asura_Assertf(strlen(szFilename) <= NAND_MAX_NAME, ("Filename (%s) is too long", szFilename));

	if(IsBusy()) { return false; }

	//Asura_OutputToDebugger(("Writing %s to NAND\n", szFilename));
	
	if(bAsync)
	{
		Asura_Assert(!uOffset, "Offset not implemented for asynchronous writes");

		s_xWriteFileData.m_bOverwrite = bOverwrite;
		s_xWriteFileData.m_pcData = reinterpret_cast<const char*>(pvData);
		strncpy(s_xWriteFileData.m_szFilename, szFilename, NAND_MAX_NAME + 1);
		s_xWriteFileData.m_uDataSize = uSize;

		SetState(ASURA_WII_NAND_WRITE);
		// Kick off file creation
		const int iCreateResult = NANDCreateAsync(szFilename, (NAND_PERM_OWNER_READ | NAND_PERM_OWNER_WRITE), 0, WriteCallback_Open, &g_xCommandBlock);
		if(iCreateResult == NAND_RESULT_OK)
		{
			return true;
		}
		else if(iCreateResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
			return false;
		}
		else
		{
			SetState(ASURA_WII_NAND_IDLE);
			Asura_Assertf(0, ("Failed to create NAND file: result %i", iCreateResult));
			return false;
		}
		// Shouldn't reach here.
	}


	// Check whether it already exists.
	const int iCreateResult = NANDCreate(szFilename, (NAND_PERM_OWNER_READ | NAND_PERM_OWNER_WRITE), 0);
	if(iCreateResult == NAND_RESULT_EXISTS)
	{
		if(!bOverwrite)
		{
			Asura_Assertf(0, ("Cannot write NAND file %s as it already exists", szFilename));
			return false;
		}

	}
	else if(iCreateResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
	{
		s_pfnNANDCorruptCallback();
		return false;
	}
	else if(iCreateResult != NAND_RESULT_OK)
	{
		Asura_Assert(0, "Failed to create NAND file");
		return false;
	}

	NANDFileInfo xFileInfo;
	// Open it.
#ifdef ASURA_WII_NAND_USE_SAFE_CALLS 
	const int iOpenResult = NANDSimpleSafeOpen(szFilename, &xFileInfo, NAND_ACCESS_WRITE, s_acNandSafeOpenCopyBuffer, uASURA_WII_NAND_SAFEOPEN_BUFFER_SIZE);
#else
	const int iOpenResult = NANDOpen(szFilename, &xFileInfo, NAND_ACCESS_WRITE);
#endif
	if(iOpenResult != NAND_RESULT_OK)
	{
		if(iOpenResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
		}
		Asura_Assert(0, "Failed to open NAND file");
		return false;
	}

	if(uOffset)
	{
		const int iSeekResult = NANDSeek(&xFileInfo, uOffset, NAND_SEEK_SET);
		if(iSeekResult < 0)
		{
			if(iSeekResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
			{
				s_pfnNANDCorruptCallback();
			}
			Asura_Assert(0, "Failed to seek in NAND file");
			return false;
		}
	}

	bool bSuccess = false;
	if(!(reinterpret_cast<u_int>(pvData) % 32))
	{
		// If the buffer supplied is 32 byte aligned, we can write straight in
		const int iWriteResult = NANDWrite(&xFileInfo, pvData, uSize);
		Asura_Assert(iWriteResult == uSize, "Failed to write NAND file");
		if(iWriteResult == uSize)
		{
			bSuccess = true;
		}
		else if(iWriteResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
			return false;
		}
	}
	else
	{
		// Otherwise we need to copy it via an intermediate aligned buffer.
		u_int uPosition = 0;
		u_int uBytesRemaining = uSize;
		while(uBytesRemaining)
		{
			const u_int uBytesToWrite = Asura_Maths::Min(uASURA_WII_NAND_IO_BUFFER_SIZE, uBytesRemaining);
			Asura_Mem::Copy(s_acNandIOBuffer, static_cast<const char*>(pvData) + uPosition, uBytesToWrite);
			const int iWriteResult = NANDWrite(&xFileInfo, s_acNandIOBuffer, uBytesToWrite);
			if(iWriteResult != uBytesToWrite)
			{
				if(iWriteResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
				{
					s_pfnNANDCorruptCallback();
				}
				Asura_Assert(0, "Failed to write NAND file");
				break;
			}
			uPosition += uBytesToWrite;
			uBytesRemaining -= uBytesToWrite;
		}
		Asura_Assert(uPosition == uSize, "Unexpected file position after NAND write");
		bSuccess = (uBytesRemaining == 0);
	}

	// Close it.
#ifdef ASURA_WII_NAND_USE_SAFE_CALLS 
	ASURA_WII_NAND_CALL(NANDSimpleSafeClose(&xFileInfo));
#else
	ASURA_WII_NAND_CALL(NANDClose(&xFileInfo));
#endif
	return bSuccess;
}

void Asura_Wii_NAND::WriteCallback_Open(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsWriting(), "Unexpected state");
	if(iResult == NAND_RESULT_EXISTS)
	{
		// extract over-write from user data.
		if(!s_xWriteFileData.m_bOverwrite)
		{
			Asura_Assertf(0, ("Cannot write NAND file %s as it already exists", s_xWriteFileData.m_szFilename));
			return;
		}
	}
	else if(iResult != NAND_RESULT_OK)
	{
		if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
		{
			s_pfnNANDCorruptCallback();
		}

		Asura_Assertf(0, ("Failed to create NAND file: result %i", iResult));
		return;
	}

	// Go ahead and open it.
#ifdef ASURA_WII_NAND_USE_SAFE_CALLS 
	ASURA_WII_NAND_CALL(NANDSimpleSafeOpenAsync(
		s_xWriteFileData.m_szFilename,
		&s_xWriteFileData.m_xFileInfo,
		(NAND_ACCESS_WRITE | NAND_ACCESS_READ),
		s_acNandSafeOpenCopyBuffer,
		uASURA_WII_NAND_SAFEOPEN_BUFFER_SIZE,
		WriteCallback_Write,
		pxCommandBlock));
#else
	ASURA_WII_NAND_CALL(NANDOpenAsync(
		s_xWriteFileData.m_szFilename,
		&s_xWriteFileData.m_xFileInfo,
		(NAND_ACCESS_WRITE | NAND_ACCESS_READ),
		WriteCallback_Write,
		pxCommandBlock));
#endif
}

void Asura_Wii_NAND::WriteCallback_Write(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsWriting(), "Unexpected state");
	Asura_Assertf((iResult >= 0), ("Failed to open or write NAND file: result %i", iResult));

	if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
	{
		s_pfnNANDCorruptCallback();
		return;
	}

	const char* pcData = s_xWriteFileData.m_pcData;
	if(!(reinterpret_cast<u_int>(pcData) % 32))
	{
		ASURA_WII_NAND_CALL(NANDWriteAsync(&s_xWriteFileData.m_xFileInfo, pcData, s_xWriteFileData.m_uDataSize, WriteCallback_Close, pxCommandBlock));
	}
	else
	{
		// Otherwise we need to copy it via an intermediate aligned buffer.
		const u_int uBytesToWrite = Asura_Maths::Min(uASURA_WII_NAND_IO_BUFFER_SIZE, s_xWriteFileData.m_uDataSize);
		Asura_Mem::Copy(s_acNandIOBuffer, pcData, uBytesToWrite);
		s_xWriteFileData.m_pcData += uBytesToWrite;
		s_xWriteFileData.m_uDataSize -= uBytesToWrite;
		const bool bContinue = (s_xWriteFileData.m_uDataSize != 0);
		ASURA_WII_NAND_CALL(NANDWriteAsync(&s_xWriteFileData.m_xFileInfo, s_acNandIOBuffer, uBytesToWrite, bContinue ? WriteCallback_Write : WriteCallback_Close, pxCommandBlock));
	}
}

void Asura_Wii_NAND::WriteCallback_Close(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsWriting(), "Unexpected state");
	// Result should be bytes written.
	Asura_Assertf((iResult > 0), ("Failed to write NAND file: result %i", iResult));

	if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
	{
		s_pfnNANDCorruptCallback();
		return;
	}

#ifdef ASURA_WII_NAND_USE_SAFE_CALLS 
	ASURA_WII_NAND_CALL(NANDSimpleSafeCloseAsync(&s_xWriteFileData.m_xFileInfo, WriteCallback_Finish, pxCommandBlock));
#else
	ASURA_WII_NAND_CALL(NANDCloseAsync(&s_xWriteFileData.m_xFileInfo, WriteCallback_Finish, pxCommandBlock));
#endif
}

void Asura_Wii_NAND::WriteCallback_Finish(s32 iResult, NANDCommandBlock* pxCommandBlock)
{
	Asura_Assert(IsWriting(), "Unexpected state");
	Asura_Assertf((iResult == NAND_RESULT_OK), ("Failed to close NAND file after write: result %i", iResult));

	if(iResult == NAND_RESULT_CORRUPT && s_pfnNANDCorruptCallback)
	{
		s_pfnNANDCorruptCallback();
		return;
	}

	// Finished
	SetState(ASURA_WII_NAND_IDLE);
}

bool Asura_Wii_NAND::CreateBanner(const Asura_UnicodeChar* wszTitle, const Asura_UnicodeChar* wszDesc, const char* szBannerTexture, const char* szIconTexture)
{
	// Zero the structure out to ensure we don't have any trash kicking around.
	Asura_Mem::Set(&g_xBanner, 0, sizeof(g_xBanner));

	// We have to recast the unicode strings since NANDInitBanner explicitly takes u_shorts, not wchar_ts
	NANDInitBanner( &g_xBanner, NAND_BANNER_FLAG_ANIM_BOUNCE, reinterpret_cast<const u_short*>(wszTitle), reinterpret_cast<const u_short*>(wszDesc));

	// banner
	if(!LoadTexture(szBannerTexture, g_xBanner.bannerTexture, NAND_BANNER_TEXTURE_SIZE)) { return false; }
	// icon (only supporting one icon for the moment).
	if(!LoadTexture(szIconTexture, g_xBanner.iconTexture[0], NAND_BANNER_ICON_SIZE)) { return false; }

	NANDSetIconSpeed(&g_xBanner, 0, NAND_BANNER_ICON_ANIM_SPEED_END);

	return true;
}

bool Asura_Wii_NAND::WriteBanner(const Asura_UnicodeChar* wszTitle, const Asura_UnicodeChar* wszDesc, const char* szBannerTexture, const char* szIconTexture, bool bAsync /* = false */)
{
	Asura_Assert(!IsBusy(), "Operation already in progress");

	ChangeToHomeDir();

	if(!CreateBanner(wszTitle, wszDesc, szBannerTexture, szIconTexture)) { return false; }

	return WriteFile(szBANNER_FILENAME, &g_xBanner, NAND_BANNER_SIZE(1), true, bAsync);
}

bool Asura_Wii_NAND::DeleteBanner()
{
	return DeleteFile(szBANNER_FILENAME);
}

int Asura_Wii_NAND::CheckAvailableSpaceBytes(u_int uRequiredBytes)
{
	u_int uRequiredBlocks = BytesToNandBlocks(uRequiredBytes);
	u_int uRequiredNodes = 1;
	return CheckAvailableSpaceBlocks(uRequiredBlocks, uRequiredNodes);
}

int Asura_Wii_NAND::CheckAvailableSpaceBlocks(u_int uRequiredBlocks, u_int uNumFiles)
{
	u_int uRequiredNodes = uNumFiles;
	if(!DoesBannerExist())
	{
		// Need a node + blocks for the banner.
		++uRequiredNodes;
		uRequiredBlocks += BytesToNandBlocks(NAND_BANNER_SIZE(1));
	}

	u32 uCheckResult = 0;
	ASURA_WII_NAND_CALL(NANDCheck(uRequiredBlocks, uRequiredNodes, &uCheckResult));
	return uCheckResult;
}


bool Asura_Wii_NAND::DoesBannerExist()
{
	ChangeToHomeDir();
	return (GetFileSize(szBANNER_FILENAME) >= 0);
}

void Asura_Wii_NAND::ChangeToHomeDir()
{
	char szHomeDir[NAND_MAX_PATH]="";

	ASURA_WII_NAND_CALL(NANDGetHomeDir(szHomeDir));
	ASURA_WII_NAND_CALL(NANDChangeDir(szHomeDir));
}

int Asura_Wii_NAND::GetFileSize(const char* szFilename)
{
    NANDFileInfo xNandInfo;
	const int iOpenResult = NANDOpen(szFilename, &xNandInfo, NAND_ACCESS_READ);
    if(iOpenResult != NAND_RESULT_OK)
    {
        return -1;
    }

	u32 uLength = 0;
    const int iLengthResult = NANDGetLength(&xNandInfo, &uLength);

	ASURA_WII_NAND_CALL(NANDClose(&xNandInfo));

	return (iLengthResult == NAND_RESULT_OK) ? uLength : -1;
}

bool Asura_Wii_NAND::LoadTexture(const char* szFilename, void* pvBuffer, u_int uSize)
{
	const Asura_Handle iFileHandle = Asura_FileAccess::Open(szFilename);
	if(iFileHandle == ASURA_INVALID_HANDLE)
	{
		Asura_Assert(0, "Failed to load texture for NAND banner");
		return false;
	}
	const u_int uFileSize = OSRoundUp32B(Asura_FileAccess::GetFileSize(iFileHandle));
	const TPLPalettePtr pxTexturePalette = static_cast<TPLPalettePtr>(Asura_AllocateAlign(uFileSize, ASURA_ALIGN_32_BYTE));

	Asura_FileAccess::ReadData(iFileHandle, pxTexturePalette, uFileSize);
	Asura_FileAccess::Close(iFileHandle);

	TPLBind(pxTexturePalette);

	const TPLDescriptorPtr pxTextureDesc = TPLGet(pxTexturePalette, 0);
	Asura_Mem::Copy(pvBuffer, pxTextureDesc->textureHeader->data, uSize);

	Asura_DeallocateAlign(pxTexturePalette);

	return true;
}

u_int Asura_Wii_NAND::ReadHomeDir()
{
	char szHomeDir[NAND_MAX_PATH]="";

	const int iGetHomeResult = NANDGetHomeDir(szHomeDir);
	if(iGetHomeResult != NAND_RESULT_OK)
	{
		return 0;
	}

	const int iGetNumResult = NANDReadDir(szHomeDir, NULL, &s_uNumFilenames);
	if(iGetNumResult != NAND_RESULT_OK)
	{
		return 0;
	}
	
	if(s_uNumFilenames > uASURA_WII_NAND_MAX_FILES_PER_DIR)
	{
		Asura_Assertf(0, ("Too many files in directory (got %u, max %u)", s_uNumFilenames, uASURA_WII_NAND_MAX_FILES_PER_DIR));
		return 0;
	}

	const int iReadResult = NANDReadDir(szHomeDir, s_acDirectoryReadBuffer, &s_uNumFilenames);
	if(iReadResult != NAND_RESULT_OK)
	{
		return 0;
	}
	const char* szCurrentFile = s_acDirectoryReadBuffer;
	for(u_int uFile = 0; uFile < s_uNumFilenames; ++uFile)
	{
		s_aszFilenames[uFile] = szCurrentFile;
		szCurrentFile += strlen(szCurrentFile) + 1;
	}
	return s_uNumFilenames;
}

const char* Asura_Wii_NAND::GetFilenameInHomeDir(u_int uFile)
{
	Asura_Assert(uFile < s_uNumFilenames, "File out of range");
	return (uFile < s_uNumFilenames) ? s_aszFilenames[uFile] : NULL;
}

bool Asura_Wii_NAND::DeleteFile(const char* szFilename)
{
	if(IsBusy())
	{
		Asura_Assert(0, "Attempted to delete a file while NAND is busy");
		return false;
	}
	const int iDeleteResult = NANDDelete(szFilename);
	return (iDeleteResult == NAND_RESULT_OK);
}

#ifdef _DEBUG
static void WaitForAsyncOperation()
{
	int i = 0;
	while(Asura_Wii_NAND::IsBusy()) 
	{
		OSYieldThread();
		++i;
		if(!(i % 10000))
		{
			OSReport(".");
		}
	}
	OSReport("\n");
}

void Asura_Wii_NAND::RunTests()
{
	// Test making banner
	WriteBanner(L"The Simpsons Game", L"Savegames", "/graphics/nand_banner.tpl", "/graphics/nand_icon.tpl");

	// Test file IO
	{
		// Small string
		const char szTestFilename[] = "test.txt";
		const char szTestStringOut[] = "Overwritten.";
		char szTestStringIn[16];
		CheckAvailableSpaceBytes(sizeof(szTestStringOut));
		const bool bWriteSuccess = WriteFile(szTestFilename, szTestStringOut, sizeof(szTestStringOut), true, true);
		Asura_Assert(bWriteSuccess, "");
		OSReport("Writing\n");
		WaitForAsyncOperation();

		const bool bReadBigSuccess = ReadFile(szTestFilename, szTestStringIn, sizeof(szTestStringIn), true);
		OSReport("Reading\n");
		WaitForAsyncOperation();
		Asura_Assert(bReadBigSuccess, "");
		Asura_Assert(!strcmp(szTestStringIn, szTestStringOut), "");
	}

	{
		// Aligned
		const char szTestFilename[] = "test.bin";
		const u_int uBufferSize = 123456;
		CheckAvailableSpaceBytes(uBufferSize);

		char* pcBufferOut = static_cast<char*>(Asura_AllocateAlign(uBufferSize, ASURA_ALIGN_32_BYTE));
		char* pcBufferIn = static_cast<char*>(Asura_AllocateAlign(uBufferSize, ASURA_ALIGN_32_BYTE));
		for(u_int u = 0; u < uBufferSize; ++u)
		{
			pcBufferOut[u] = Asura_Random::GetInt(-127, 127);
		}
		const bool bWriteSuccess = WriteFile(szTestFilename, pcBufferOut, uBufferSize, true, true);
		Asura_Assert(bWriteSuccess, "");
		OSReport("Writing\n");
		WaitForAsyncOperation();

		const bool bReadSuccess = ReadFile(szTestFilename, pcBufferIn, uBufferSize, true);
		OSReport("Reading\n");
		WaitForAsyncOperation();
		Asura_Assert(bReadSuccess, "");
		for(u_int u = 0; u < uBufferSize; ++u)
		{
			Asura_Assert(pcBufferOut[u] == pcBufferIn[u], "");
		}
		Asura_DeallocateAlign(pcBufferOut);
		Asura_DeallocateAlign(pcBufferIn);
	}
	
	{
		const char szTestFilename[] = "noalign.bin";
		const u_int uBufferSize = 123456;
		char* pcBufferOut = static_cast<char*>(Asura_AllocateAlign(uBufferSize, ASURA_ALIGN_32_BYTE));
		char* pcBufferIn = static_cast<char*>(Asura_AllocateAlign(uBufferSize, ASURA_ALIGN_32_BYTE));

		// Unaligned.
		for(u_int u = 1; u < uBufferSize; ++u)
		{
			pcBufferOut[u] = Asura_Random::GetInt(-127, 127);
		}
		const bool bWriteSuccess = WriteFile(szTestFilename, pcBufferOut + 1, uBufferSize - 1, true, true);
		Asura_Assert(bWriteSuccess, "");
		OSReport("Writing\n");
		WaitForAsyncOperation();

		const bool bReadSuccess = ReadFile(szTestFilename, pcBufferIn + 1, uBufferSize - 1, true);
		OSReport("Reading\n");
		WaitForAsyncOperation();
		Asura_Assert(bReadSuccess, "");
		for(u_int u = 1; u < uBufferSize; ++u)
		{
			Asura_Assert(pcBufferOut[u] == pcBufferIn[u], "");
		}
		Asura_DeallocateAlign(pcBufferOut);
		Asura_DeallocateAlign(pcBufferIn);
	}
	const u_int uNumFiles = ReadHomeDir();
	OSReport("Files in home dir\n---\n");
	for(u_int uFile = 0; uFile < uNumFiles; ++uFile)
	{
		OSReport("%s\n", GetFilenameInHomeDir(uFile));
	}
	OSReport("---\n");
}
#endif
