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

#include "Core/Asura.h"

// This file's header.
#include "FileAccess/Asura_PlatformFileAccess.h"

// asura
#include "Core/Asura_String.h"
#include "FileAccess/Asura_FileAccess.h"
#include "FileAccess/Asura_PlatformFileAccessUtils.h"
#include "Sound/Asura_Sound.h"

#include <string.h>
#include <revolution/dvd.h>
#include <revolution/os.h>

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////

// Must be a multiple of 32 bytes
#define ASURA_WII_FILE_BUFFER_SIZE (32 * 1024)

#ifndef INVALID_FILE_ATTRIBUTES
#define  INVALID_FILE_ATTRIBUTES 0xffffffff
#endif

#define MAX_FILENAME_LENGTH 256

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////

// Kevin Lea - 07/09/2001 16:22:31 - implementation note
//
// valid fileHandles are in the range 0 to (ASURA_MAX_NO_OF_OPEN_FILES-1) inclusive
// 

// Buffer containing next <buffer size> bytes to be read
char* g_apcFileBuffer[ASURA_MAX_NO_OF_OPEN_FILES];
// How far we are in the buffer
unsigned int g_auPositionInBuffer[ASURA_MAX_NO_OF_OPEN_FILES];
// How far we are in the file (marks END address of the buffer)
unsigned int g_auBufferPositionInFile[ASURA_MAX_NO_OF_OPEN_FILES];
// Remaining bytes in the file to be read
unsigned int g_auBytesToEnd[ASURA_MAX_NO_OF_OPEN_FILES];
// GC file info
DVDFileInfo g_axFileInfo[ASURA_MAX_NO_OF_OPEN_FILES];

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////

volatile static bool s_bDVDCmdResultValid = false;
volatile static s32  s_iDVDCmdResult      = 0;

static void DVDCallbackFunction( s32 result, DVDFileInfo* fileInfo )
{
    s_bDVDCmdResultValid = true;
    s_iDVDCmdResult      = result;
}

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////

void Asura_FileAccess::Platform_Initialise( void )
{
	DVDSetAutoFatalMessaging(true);
	// Allocate buffers
	for(int i = 0; i < ASURA_MAX_NO_OF_OPEN_FILES; i++)
	{
		g_apcFileBuffer[i] = (char *)Asura_AllocateAlign(ASURA_WII_FILE_BUFFER_SIZE, ASURA_ALIGN_32_BYTE);
	}
}

void Asura_FileAccess::Platform_Shutdown( void )
{
	for(int i = 0; i < ASURA_MAX_NO_OF_OPEN_FILES; i++)
	{
		Asura_DeallocateAlign(g_apcFileBuffer[i]);
	}
}

bool Asura_FileAccess::Platform_SkipUncompressedData( Asura_Handle iFileHandle, u_int uNumBytes )
{
	unsigned int iBytesRemaining = ASURA_WII_FILE_BUFFER_SIZE - g_auPositionInBuffer[iFileHandle];
	if(uNumBytes < iBytesRemaining)
	{
		// Staying within this block
		g_auPositionInBuffer[iFileHandle] += uNumBytes;
		return true;
	}
	// Skip to end of this buffer block
	uNumBytes -= iBytesRemaining;
	if(uNumBytes >= ASURA_WII_FILE_BUFFER_SIZE)
	{
		// We have blocks to skip
		unsigned int uBlocksToSkip = uNumBytes / ASURA_WII_FILE_BUFFER_SIZE;

        s_bDVDCmdResultValid = false;
        DVDSeekAsync( &g_axFileInfo[ iFileHandle ],
			uBlocksToSkip * ASURA_WII_FILE_BUFFER_SIZE,
			DVDCallbackFunction );
        while (!s_bDVDCmdResultValid)
		{
            Asura_PlatformFileAccessUtils::CheckDiscStatus();
		}

        if ( s_iDVDCmdResult >= 0 )
        {
		    g_auBytesToEnd[iFileHandle] -= uBlocksToSkip * ASURA_WII_FILE_BUFFER_SIZE;
		    g_auBufferPositionInFile[iFileHandle] += uBlocksToSkip * ASURA_WII_FILE_BUFFER_SIZE;
		    uNumBytes -= uBlocksToSkip * ASURA_WII_FILE_BUFFER_SIZE;
        }
		else
		{
			return false;
		}
	}
	// We're going to end up in the next block. Just read in the buffer
	if(g_auBytesToEnd[iFileHandle] < ASURA_WII_FILE_BUFFER_SIZE)
	{
		// Read remaining bytes to end of file
        s_bDVDCmdResultValid = false;
        DVDReadAsync( &g_axFileInfo[ iFileHandle ],
            reinterpret_cast< void* >( g_apcFileBuffer[ iFileHandle ] ),
            static_cast< s32 >( g_auBytesToEnd[ iFileHandle ] ),
            g_auBufferPositionInFile[iFileHandle],
            DVDCallbackFunction );
        while (!s_bDVDCmdResultValid)
		{
            Asura_PlatformFileAccessUtils::CheckDiscStatus();
		}

        if ( s_iDVDCmdResult >= 0 )
        {
		    g_auBufferPositionInFile[iFileHandle] += g_auBytesToEnd[iFileHandle];
        }
		else
		{
			return false;
		}
	}
	else
	{
		// Read in next <buffer size> bytes
        s_bDVDCmdResultValid = false;
        DVDReadAsync( &g_axFileInfo[ iFileHandle ],
            reinterpret_cast< void* >( g_apcFileBuffer[ iFileHandle ] ),
            static_cast< s32 >( ASURA_WII_FILE_BUFFER_SIZE ),
            g_auBufferPositionInFile[ iFileHandle ],
            DVDCallbackFunction );
        while (!s_bDVDCmdResultValid)
		{
            Asura_PlatformFileAccessUtils::CheckDiscStatus();
		}

        if ( s_iDVDCmdResult >= 0 )
        {
		    g_auBufferPositionInFile[iFileHandle] += ASURA_WII_FILE_BUFFER_SIZE;
        }
		else
		{
			return false;
		}
	}
	g_auBytesToEnd[iFileHandle] -= ASURA_WII_FILE_BUFFER_SIZE;
	g_auPositionInBuffer[iFileHandle] = uNumBytes;
	return true;
}

bool Asura_FileAccess::Platform_Open(Asura_Handle fileHandle, const char* filename)
{
	// Independent code uses backslashes; NGC uses forward slashes. Replace 'em
	// Damn - filename is const, so have to create a new string
	Asura_Assert(strlen(filename)+1 < MAX_FILENAME_LENGTH, "Filename too long.");
	char szNewFileName[MAX_FILENAME_LENGTH];
	strcpy(szNewFileName, filename);
	REPLACE_SLASHES(szNewFileName);

    // We MUST open the file before accessing the file
    if (DVDOpen(szNewFileName, &g_axFileInfo[fileHandle]))
	{
		OSReport("Opening %s with handle %i\n", filename, fileHandle);
		g_auBytesToEnd[fileHandle] = OSRoundUp32B( DVDGetLength(&g_axFileInfo[fileHandle]) );
		// Uses small buffer (size set at top of this file)
		if( g_auBytesToEnd[fileHandle] < ASURA_WII_FILE_BUFFER_SIZE)
		{
			// Read in entire file
            s_bDVDCmdResultValid = false;
            DVDReadAsync( &g_axFileInfo[ fileHandle ],
                reinterpret_cast< void* >( g_apcFileBuffer[ fileHandle ] ),
                static_cast< s32 >( g_auBytesToEnd[ fileHandle ] ),
                0,
                DVDCallbackFunction );
            while (!s_bDVDCmdResultValid)
			{
                Asura_PlatformFileAccessUtils::CheckDiscStatus();
			}

            if ( s_iDVDCmdResult >= 0 )
            {
			    g_auBufferPositionInFile[fileHandle] = g_auBytesToEnd[ fileHandle ];
			    g_auBytesToEnd[fileHandle] = 0;
            }
			else
			{
				return false;
			}
		}
		else
		{
			// Read in the first <buffer size> bytes
            s_bDVDCmdResultValid = false;
            DVDReadAsync( &g_axFileInfo[ fileHandle ],
                reinterpret_cast< void* >( g_apcFileBuffer[ fileHandle ] ),
                static_cast< s32 >( ASURA_WII_FILE_BUFFER_SIZE ),
                0,
                DVDCallbackFunction );
            while (!s_bDVDCmdResultValid)
			{
                Asura_PlatformFileAccessUtils::CheckDiscStatus();
			}

            if ( s_iDVDCmdResult >= 0 )
            {
			    g_auBufferPositionInFile[fileHandle] = ASURA_WII_FILE_BUFFER_SIZE;
			    g_auBytesToEnd[fileHandle] -= ASURA_WII_FILE_BUFFER_SIZE;
            }
			else
			{
				return false;
			}
		}
		g_auPositionInBuffer[fileHandle] = 0;
		return true;
	}
    else
	{
		return false;
    }
}

bool Asura_FileAccess::Platform_Close(Asura_Handle fileHandle)
{
	OSReport("Closing filehandle %i\n", fileHandle);
	//delete [] FileBuffer[fileHandle];
	DVDClose(&g_axFileInfo[fileHandle]);

	return true;
}

bool Asura_FileAccess::Platform_ReadData(Asura_Handle iFileHandle, void* pvDestination, unsigned int uNumBytes)
{
	// KOR - 27/06/2003 16:56:19 - Optimised version in progress - doesn't work at present,
	// due to DVDRead requiring 32 byte alignment for destination
/*	unsigned int iBytesRemaining = ASURA_WII_FILE_BUFFER_SIZE - g_auPositionInBuffer[iFileHandle];
	char* pcSrc = g_apcFileBuffer[iFileHandle] + g_auPositionInBuffer[iFileHandle];
	char* pcDest = (char*)pvDestination;
	if(uNumBytes < iBytesRemaining)
	{
		// Staying within this block
		memcpy(pcDest, pcSrc, uNumBytes);
		g_auPositionInBuffer[iFileHandle] += uNumBytes;
		return true;
	}
	// Copy to end of this buffer block
	uNumBytes -= iBytesRemaining;
	memcpy(pcDest, pcSrc, iBytesRemaining);
	pcDest += iBytesRemaining;
	if(uNumBytes >= ASURA_WII_FILE_BUFFER_SIZE)
	{
		// We have at least one entire block to read
		unsigned int uBytesToRead = ASURA_WII_FILE_BUFFER_SIZE * (unsigned int)(uNumBytes / ASURA_WII_FILE_BUFFER_SIZE);
		if(DVDRead(&g_axFileInfo[iFileHandle], (void*)pcDest, (s32)uBytesToRead, g_auBufferPositionInFile[iFileHandle]) < 0)
		{
			Asura_Assert(0, "Error occurred when reading file");
		}
		pcDest += uBytesToRead;
		g_auBytesToEnd[iFileHandle] -= uBytesToRead;
		g_auBufferPositionInFile[iFileHandle] += uBytesToRead;
		uNumBytes -= uBytesToRead;
	}
	// We're going to end up in the next block. Now read in the buffer
	if(g_auBytesToEnd[iFileHandle] < ASURA_WII_FILE_BUFFER_SIZE)
	{
		// Read remaining bytes to end of file
		if (DVDRead(&g_axFileInfo[iFileHandle], (void*)g_apcFileBuffer[iFileHandle], (s32)g_auBytesToEnd[iFileHandle], g_auBufferPositionInFile[iFileHandle]) < 0)
		{
			Asura_Assert(0, "Error occurred when reading file");
		}
		g_auBufferPositionInFile[iFileHandle] += g_auBytesToEnd[iFileHandle];
	}
	else
	{
		// Read in next <buffer size> bytes
		if (DVDRead(&g_axFileInfo[iFileHandle], (void*)g_apcFileBuffer[iFileHandle], (s32)ASURA_WII_FILE_BUFFER_SIZE, g_auBufferPositionInFile[iFileHandle]) < 0)
		{
			Asura_Assert(0, "Error occurred when reading file");
		}
		g_auBufferPositionInFile[iFileHandle] += ASURA_WII_FILE_BUFFER_SIZE;
	}
	pcSrc = g_apcFileBuffer[iFileHandle];
	// Finally, copy last piece of data in if we need it.
	if(uNumBytes)
	{
		memcpy(pcDest, pcSrc, uNumBytes);
	}
	g_auBytesToEnd[iFileHandle] -= ASURA_WII_FILE_BUFFER_SIZE;
	g_auPositionInBuffer[iFileHandle] = uNumBytes;
	return true;*/
	const u_int uReadBytes = uNumBytes;
	unsigned int iBytesRemaining = ASURA_WII_FILE_BUFFER_SIZE - g_auPositionInBuffer[iFileHandle];
	char* src = g_apcFileBuffer[iFileHandle] + g_auPositionInBuffer[iFileHandle];
	char* dest = (char*)pvDestination;
	while(uNumBytes >= iBytesRemaining)
	{
		// Copy entire buffer into destination
		memcpy(dest, src, iBytesRemaining);
		dest += iBytesRemaining;
		uNumBytes -= iBytesRemaining;
		if(g_auBytesToEnd[iFileHandle] < ASURA_WII_FILE_BUFFER_SIZE)
		{
			// Read remaining bytes to end of file
            s_bDVDCmdResultValid = false;
            DVDReadAsync( &g_axFileInfo[ iFileHandle ],
                reinterpret_cast< void* >( g_apcFileBuffer[ iFileHandle ] ),
                static_cast< s32 >( g_auBytesToEnd[ iFileHandle ] ),
                g_auBufferPositionInFile[ iFileHandle ],
                DVDCallbackFunction );
            while (!s_bDVDCmdResultValid)
			{
                Asura_PlatformFileAccessUtils::CheckDiscStatus();
			}

            if ( s_iDVDCmdResult >= 0 )
            {
			    g_auBufferPositionInFile[iFileHandle] += g_auBytesToEnd[iFileHandle];
            }
			else
			{
				return false;
			}
		}
		else
		{
			// Read in next <buffer size> bytes
            s_bDVDCmdResultValid = false;
            DVDReadAsync( &g_axFileInfo[ iFileHandle ],
                reinterpret_cast< void* >( g_apcFileBuffer[ iFileHandle ] ),
                static_cast< s32 >( ASURA_WII_FILE_BUFFER_SIZE ),
                g_auBufferPositionInFile[ iFileHandle ],
                DVDCallbackFunction );
            while (!s_bDVDCmdResultValid)
			{
                Asura_PlatformFileAccessUtils::CheckDiscStatus();
			}

            if ( s_iDVDCmdResult >= 0 )
            {
			    g_auBufferPositionInFile[iFileHandle] += ASURA_WII_FILE_BUFFER_SIZE;
            }
			else
			{
				return false;
			}
		}
		g_auBytesToEnd[iFileHandle] -= ASURA_WII_FILE_BUFFER_SIZE;
		g_auPositionInBuffer[iFileHandle] = 0;
		iBytesRemaining = ASURA_WII_FILE_BUFFER_SIZE;
		src = g_apcFileBuffer[iFileHandle];
	}
	if(uNumBytes)
	{
		// Copy remaining bytes.
		memcpy(dest, src, uNumBytes);
		g_auPositionInBuffer[iFileHandle] += uNumBytes;
	}

	// KOR - 14/09/2006 - noticed that data was incorrect when loading a small test level.
	// This call sorts it out; however, it may significantly impact loading times, so I've
	// disabled it until we can test it out.
	DCFlushRange(pvDestination, uReadBytes);
	return true;
}

bool Asura_FileAccess::Platform_FileExists( const char* szFilename )
{
	Asura_Assert(strlen(szFilename)+1 < MAX_FILENAME_LENGTH, "Filename too long.");
	char szNewFileName[MAX_FILENAME_LENGTH];
	strcpy(szNewFileName, szFilename);
	REPLACE_SLASHES(szNewFileName);

	return (DVDConvertPathToEntrynum(szNewFileName) != -1);
}

u_int Asura_FileAccess::Platform_GetFileSize( const char* szFilename )
{
	DVDFileInfo xFileInfo;
	if ( false == DVDOpen( szFilename, &xFileInfo ) )
	{
		return 0;
	}

	u_int uFileSize	= DVDGetLength( &xFileInfo );

	DVDClose( &xFileInfo );

	return uFileSize;
}

u_int Asura_FileAccess::Platform_GetFileSize( Asura_Handle iFileHandle )
{
	return DVDGetLength( &g_axFileInfo[iFileHandle] );
}

bool Asura_FileAccess::Platform_LoadAsynchronous(Asura_Handle iHandle,const char* szFilename)
{
	Asura_Assert( 0,"Asynchronous loading not supported");
	return false;
}

enum ASURA_ASYNCLOADING_STATUS Asura_FileAccess::Platform_GetAsynchronousLoadingStatus(Asura_Handle iFileHandle)
{
	Asura_Assert( 0,"Asynchronous loading not supported");
	return ASURA_ASYNCLOADING_STATUS_ERROR;
}

//////////////////////////////////////////////////////////////////////////////
//
// FUNCTION     Asura_FileAccess::Platform_OpenFromMemory().
// PARAMETERS   iFileHandle - effectively, index into open file array
//              pMem        - pointer to buffer containing file contents
//              uSize       - size of buffer
// RETURNS      false, as we don't support this method on this platform.
// DESCRIPTION  sets up file reading from memory that already contains a file.
// AUTHOR       Mike Healey - 17.08.2005.
//
//////////////////////////////////////////////////////////////////////////////
bool Asura_FileAccess::Platform_OpenFromMemory( Asura_Handle iFileHandle, const void* pMem, u_int uSize )
{
	return false;
}

void Asura_FileAccess::Platform_FindFiles(const char* szDirectory, const char* szExtension, void (*pfnCallback)(const char* szFilename), bool bRecursive /* = true */)
{
	Asura_Char szCurrentPath[256];
	bool bWhileOk = true;
	DVDDir xDirectory;
	if(!DVDOpenDir(szDirectory, &xDirectory))
	{
		return;
	}
	while( bWhileOk)
	{
		DVDDirEntry xDirectoryEntry;
		bWhileOk = DVDReadDir(&xDirectory, &xDirectoryEntry);
		if(bWhileOk)
		{
			Asura_PrintfToBfr(szCurrentPath, "%s/%s", 256, true, szDirectory, xDirectoryEntry.name);		
			if(xDirectoryEntry.isDir)
			{
				if(bRecursive)
				{
					// Directory - recurse into
					FindFiles(szCurrentPath, szExtension, pfnCallback);
				}
			}
			else
			{
				// File - check against mask
				if(xDirectoryEntry.name)
				{
					if(Asura_StringUtil::EndsWith(szCurrentPath, szExtension, true))
					{
						pfnCallback(szCurrentPath);
					}
				}
			}
		}
	}
	DVDCloseDir(&xDirectory);

}