/*
 * Copyright (c) 2009, The MilkyTracker Team.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

//////////////////////////////////////////////////////////////////////////
// System dependent file loading routines								//
// to make future porting easier										//
//////////////////////////////////////////////////////////////////////////
#include "XMFile.h"

XMFileBase::XMFileBase() :
	baseOffset(0)
{
}

XMFileBase::~XMFileBase()
{
}

void XMFileBase::seekWithBaseOffset(mp_dword pos)
{
	seek(pos+baseOffset);
}

mp_uint32 XMFileBase::sizeWithBaseOffset()
{
	return size()-baseOffset;
}

mp_uint32 XMFileBase::posWithBaseOffset()
{
	return pos()-baseOffset;
}

//////////////////////////////////////////////////////////////////////////
// Reading/writing of little endian stuff								//
//////////////////////////////////////////////////////////////////////////
mp_ubyte XMFileBase::readByte()
{
	mp_ubyte c;
	mp_sint32 bytesRead = read(&c,1,1);
	//ASSERT(bytesRead == 1);
	
	if (!(bytesRead == 1))
		c = 0;
	
	return (mp_ubyte)c;
}

mp_uword XMFileBase::readWord()
{
	mp_ubyte c[2];
	mp_sint32 bytesRead = read(&c,1,2);
	//ASSERT(bytesRead == 2);

	if (!(bytesRead == 2))
		c[0] = c[1] = 0;

	return (mp_uword)((mp_uword)c[0]+((mp_uword)c[1]<<8));
}

mp_dword XMFileBase::readDword()
{
	mp_ubyte c[4];
	mp_sint32 bytesRead = read(&c,1,4);
	//ASSERT(bytesRead == 4);

	if (!(bytesRead == 4))
		c[0] = c[1] = c[2] = c[3] = 0;

	return (mp_dword)((mp_uint32)c[0]+
					  ((mp_uint32)c[1]<<8)+
					  ((mp_uint32)c[2]<<16)+
					  ((mp_uint32)c[3]<<24));
}

void XMFileBase::readWords(mp_uword* buffer,mp_sint32 count)
{
	for (mp_sint32 i = 0; i < count; i++)
		*buffer++ = readWord();
}

void XMFileBase::readDwords(mp_dword* buffer,mp_sint32 count)
{
	for (mp_sint32 i = 0; i < count; i++)
		*buffer++ = readDword();
}

void XMFileBase::writeByte(mp_ubyte b)
{
	mp_sint32 bytesWritten = write(&b, 1, 1);
	ASSERT(bytesWritten == 1);
}

void XMFileBase::writeWord(mp_uword w)
{
	mp_ubyte c[2];
	c[0] = (mp_ubyte)w;
	c[1] = (mp_ubyte)(w>>8);
	mp_sint32 bytesWritten = write(&c, 1, 2);
	ASSERT(bytesWritten == 2);	
}

void XMFileBase::writeDword(mp_dword dw)
{
	mp_ubyte c[4];
	c[0] = (mp_ubyte)dw;
	c[1] = (mp_ubyte)(dw>>8);
	c[2] = (mp_ubyte)(dw>>16);
	c[3] = (mp_ubyte)(dw>>24);
	mp_sint32 bytesWritten = write(&c, 1, 4);
	ASSERT(bytesWritten == 4);	
}

void XMFileBase::writeWords(const mp_uword* buffer,mp_sint32 count)
{
	for (mp_sint32 i = 0; i < count; i++)
	{
		writeWord(*buffer);
		buffer++;
	}
}

void XMFileBase::writeDwords(const mp_dword* buffer,mp_sint32 count)
{
	for (mp_sint32 i = 0; i < count; i++)
	{
		writeDword(*buffer);
		buffer++;
	}
}

void XMFileBase::writeString(const char* string)
{
	write(string, 1, strlen(string));
}

#define BUFFERSIZE 16384

//////////////////////////////////////////////////////////////////////////
// WIN32 implentation													//
//////////////////////////////////////////////////////////////////////////
#ifdef WIN32

#include <tchar.h>

XMFile::XMFile(const SYSCHAR*	fileName, bool writeAccess /* = false*/) :
	XMFileBase(),
	fileName(fileName),
	cacheBuffer(NULL)
{
	this->writeAccess = writeAccess;

	bytesRead = 0;
	
	handle = CreateFile(fileName,
					    writeAccess ? GENERIC_WRITE : GENERIC_READ,
						writeAccess ? FILE_SHARE_WRITE : FILE_SHARE_READ,
						NULL,
						writeAccess ? CREATE_ALWAYS : OPEN_EXISTING, 
						FILE_ATTRIBUTE_NORMAL, 
						NULL);

	//ASSERT(handle != INVALID_HANDLE_VALUE);

	if (writeAccess)
	{
		cacheBuffer = new mp_ubyte[BUFFERSIZE+16];
		currentCacheBufferPtr = cacheBuffer;
	}
}

bool XMFile::isOpen()
{
	return handle != INVALID_HANDLE_VALUE;
}

XMFile::~XMFile()
{
	if (writeAccess && handle != INVALID_HANDLE_VALUE)
		flush();

	if (cacheBuffer)
		delete[] cacheBuffer;

	CloseHandle(handle);
}

mp_sint32 XMFile::read(void* ptr,mp_sint32 size,mp_sint32 count)
{
	unsigned long NumberOfBytesRead;
	bool bResult = (bool)ReadFile(handle,ptr,size*count,&NumberOfBytesRead,NULL);
	bytesRead+=(mp_uint32)NumberOfBytesRead;
	if (!bResult) return -1;
	return (mp_sint32)NumberOfBytesRead;
}

void XMFile::flush()
{
	unsigned long NumberOfBytesWritten;
	bool bResult = (bool)WriteFile(handle,cacheBuffer,
								   currentCacheBufferPtr-cacheBuffer,
								   &NumberOfBytesWritten,
								   NULL);
	bytesRead+=(mp_uint32)NumberOfBytesWritten;
	currentCacheBufferPtr = cacheBuffer;
}

mp_sint32 XMFile::write(const void* ptr,mp_sint32 size,mp_sint32 count)
{
#ifdef DEBUG
	unsigned long NumberOfBytesWritten;
	bool bResult = (bool)WriteFile(handle,ptr,size*count,&NumberOfBytesWritten,NULL);
	bytesRead+=(mp_uint32)NumberOfBytesWritten;
	if (!bResult) return -1;
	return (mp_sint32)NumberOfBytesWritten;
	FlushFileBuffers(handle);
#else
	// Buffer to be written is bigger than our internal cache
	// => Write through
	if (size*count > BUFFERSIZE)
	{
		// Flush first
		flush();
		unsigned long NumberOfBytesWritten;
		bool bResult = (bool)WriteFile(handle,ptr,size*count,&NumberOfBytesWritten,NULL);
		bytesRead+=(mp_uint32)NumberOfBytesWritten;
		if (!bResult) return -1;
		return (mp_sint32)NumberOfBytesWritten;
	}

	// Buffer to be written still fits into our cache buffer
	if (size*count + currentCacheBufferPtr <= cacheBuffer+BUFFERSIZE)
	{
		// Copy into cache
		memcpy(currentCacheBufferPtr, ptr, size*count);
		// Advance current cache ptr
		currentCacheBufferPtr += size*count;
	}
	else
	{
		// Buffer doesn't fit, flush buffer first
		flush();
		// Copy into cache
		memcpy(currentCacheBufferPtr, ptr, size*count);
		// Advance 
		currentCacheBufferPtr += size*count;
	}
	
	if (currentCacheBufferPtr == cacheBuffer + BUFFERSIZE)
		flush();

	bytesRead+=size*count;
	return size*count;
#endif
}

void XMFile::seek(mp_uint32 pos, SeekOffsetTypes seekOffsetType/* = SeekOffsetTypeStart*/)
{
	if (writeAccess)
	{
		flush();
	}

	DWORD moveMethod = FILE_BEGIN;

	if (seekOffsetType == XMFile::SeekOffsetTypeCurrent)
		moveMethod = FILE_CURRENT;
	else if (seekOffsetType == XMFile::SeekOffsetTypeEnd)
		moveMethod = FILE_END;
	
	SetFilePointer(handle, pos, NULL, moveMethod);
}

mp_uint32 XMFile::pos()
{
	return SetFilePointer(handle, 0, NULL, FILE_CURRENT);
}

mp_uint32 XMFile::size()
{
	mp_uint32 size = 0;
	mp_uint32 curPos = pos();
	SetFilePointer(handle, 0, NULL, FILE_END);
	size = pos();
	seek(curPos);
	return size;
}

bool XMFile::remove(const SYSCHAR* file)
{
	return DeleteFile(file);
}

bool XMFile::exists(const SYSCHAR* file)
{
	HANDLE handle = CreateFile(file,
					    GENERIC_READ,
						FILE_SHARE_READ,
						NULL,
						OPEN_EXISTING, 
						FILE_ATTRIBUTE_NORMAL, 
						NULL);

	bool res = handle != INVALID_HANDLE_VALUE;
	if (res)
		CloseHandle(handle);
	return res;
}

const char* XMFile::getFileNameASCII()
{
	const SYSCHAR* ptr = fileName+_tcslen(fileName);
	
	while (*ptr != '\\' && ptr > fileName)
		ptr--;
		
	if (*ptr == '\\') ptr++;
	
	fileNameASCII = new char[_tcslen(ptr)+1];
	
	for (mp_uint32 i = 0; i <= _tcslen(ptr); i++)
		fileNameASCII[i] = (char)ptr[i];

	//strcpy(fileNameASCII, ptr);
	
	return fileNameASCII;
}

//////////////////////////////////////////////////////////////////////////
// C compatible implentation											//
//////////////////////////////////////////////////////////////////////////
#else

#include <unistd.h>

XMFile::XMFile(const SYSCHAR*	fileName, bool writeAccess /* = false*/) :
	XMFileBase(),
	fileName(fileName),
	fileNameASCII(NULL),
	cacheBuffer(NULL)
{
	this->writeAccess = writeAccess;

	bytesRead = 0;	
	handle = fopen(fileName,writeAccess?"wb":"rb");

	//ASSERT(handle != NULL);
	
	if (writeAccess)
	{
		cacheBuffer = new mp_ubyte[BUFFERSIZE];
		currentCacheBufferPtr = cacheBuffer;
	}
}

XMFile::~XMFile()
{
	if (writeAccess && handle != NULL)
		flush();
	
	if (handle != NULL)
		fclose(handle);
	
	if (fileNameASCII)
		delete[] fileNameASCII;
		
	if (cacheBuffer)
		delete[] cacheBuffer;
}

bool XMFile::isOpen()
{
	return handle != NULL;
}

mp_sint32 XMFile::read(void* ptr, mp_sint32 size, mp_sint32 count)
{
	unsigned long NumberOfBytesRead = fread(ptr,size,count,handle)*size;
	bytesRead += NumberOfBytesRead;
	return (mp_sint32)NumberOfBytesRead;
}

void XMFile::flush()
{
	fwrite(cacheBuffer, 1, currentCacheBufferPtr-cacheBuffer, handle);
	currentCacheBufferPtr = cacheBuffer;
}

mp_sint32 XMFile::write(const void* ptr, mp_sint32 size, mp_sint32 count)
{
	// Buffer to be written is bigger than our internal cache
	// => Write through
	if (size*count > BUFFERSIZE)
	{
		// Flush first
		flush();
		unsigned long NumberOfBytesWritten = fwrite(ptr,size,count,handle)*size;
		bytesRead += NumberOfBytesWritten;
		return (mp_sint32)NumberOfBytesWritten;
	}

	// Buffer to be written still fits into our cache buffer
	if (size*count + currentCacheBufferPtr <= cacheBuffer+BUFFERSIZE)
	{
		// Copy into cache
		memcpy(currentCacheBufferPtr, ptr, size*count);
		// Advance current cache ptr
		currentCacheBufferPtr += size*count;
	}
	else
	{
		// Buffer doesn't fit, flush buffer first
		flush();
		// Copy into cache
		memcpy(currentCacheBufferPtr, ptr, size*count);
		// Advance 
		currentCacheBufferPtr += size*count;
	}
	
	if (currentCacheBufferPtr == cacheBuffer + BUFFERSIZE)
		flush();

	bytesRead+=size*count;
	return size*count;
}

void XMFile::seek(mp_uint32 pos, SeekOffsetTypes seekOffsetType/* = SeekOffsetTypeStart*/)
{
	if (writeAccess)
	{
		flush();
		fflush(handle);
	}
		
	int moveMethod = SEEK_SET;

	if (seekOffsetType == XMFile::SeekOffsetTypeCurrent)
		moveMethod = SEEK_CUR;
	else if (seekOffsetType == XMFile::SeekOffsetTypeEnd)
		moveMethod = SEEK_END;

	fseek(handle,pos,moveMethod);
}

mp_uint32 XMFile::pos()
{
	return ftell(handle);
}

mp_uint32 XMFile::size()
{
	mp_uint32 size = 0;
	mp_uint32 curPos = pos();
	fseek(handle,0,SEEK_END);
	size = pos();
	seek(curPos);
	return size;
}

bool XMFile::exists(const SYSCHAR* file)
{
	FHANDLE handle = fopen(file,"rb");
	bool res = handle != NULL;
	if (res)
		fclose(handle);
	return res;
}

bool XMFile::remove(const SYSCHAR* file)
{
	return unlink(file) == 0;
}

const char* XMFile::getFileNameASCII()
{
	const SYSCHAR* ptr = fileName+strlen(fileName);
	
	while (*ptr != '/' && ptr > fileName)
		ptr--;
		
	if (*ptr == '/') ptr++;
	
	fileNameASCII = new char[strlen(ptr)+1];
	
	strcpy(fileNameASCII, ptr);
	
	return fileNameASCII;
}

#endif
