///////////////////////////////////////////////////////////////////////////////
/// @file deFileMemory.cpp
///
/// @brief memory file interface
///
/// @author Lightning
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Nov 2001
/// @author Lightning
/// @remarks Creation
///
/// @date May 2003
/// @author Lightning
/// @remarks Renamed and modified to be a file and not a file system
///
///////////////////////////////////////////////////////////////////////////////

#include "deFile_priv.hpp"

deFileMemory::deFileMemory()
{
	//set the defaults
	priv_Buffer = NULL;
	priv_OpenFlags = 0;
	priv_AllocSize = 0;
	priv_NewAllocSize = 0;
	priv_Position = 0;
	priv_KnownSize = 0;
	priv_BaseFile = NULL;
	priv_BaseFS = NULL;
	priv_OwnBuffer = deFALSE;
}

deFileMemory::~deFileMemory()
{
	//make sure any open file is close
	this->Close();
}

void* deFileMemory::GetInterface(IdeFile::interface_t i)
{
	if (i == IdeFile::iface_IdeFile)
		return (IdeFileSystem*)(this);
	if (i == IdeFile::iface_IdeFileMemory)
		return (IdeFileMemory*)(this);
	return NULL;
}

deBoolean deFileMemory::Open()
{
	return Open(NULL, (long)0);	//no size for default open
}

deBoolean deFileMemory::Open(long Length)
{
	return Open(NULL, Length);
}

deBoolean deFileMemory::Open(void *Buffer, long Length)
{
	long	WriteLength;
	
	//verify the length is valid
	if(Length < 0)
		return deFALSE;

	//if another file is open, fail
	if(priv_Buffer)
		return deFALSE;

	//setup the known size
	priv_KnownSize = Length;		//current size known to the caller

	//if a buffer is supplied, use it, otherwise, allocate one
	if(Buffer)
	{
		//user supplied buffer, set the buffer to read-only
		priv_OpenFlags = deFile_Open_Read;
		priv_OwnBuffer = deFALSE;

		//we dont care about any plugins for memoryfs due to
		//the memoryfs having no way to tell the user how the buffer
		//is when ran thru the plugins

		//set the buffer
		priv_Buffer = (BYTE *)Buffer;

		//setup the allocation size
		priv_AllocSize = Length;

		//setup the new allocation size
		priv_NewAllocSize = 0;
	}
	else
	{
		//setup the actual sizes
		priv_AllocSize = 0;				//current allocated size
		priv_NewAllocSize = 65536;		//size to go up by (set to 64KB chunks)

		//figure out the chunk sizes needed for the length
		//divide by the size of a new block, add 1 to result
		WriteLength = (Length / priv_NewAllocSize) + 1;

		//multiply by size of a block to get new needed size
		WriteLength = WriteLength * priv_NewAllocSize;

		//reallocate the memory to the new size
		priv_Buffer = (BYTE *)realloc(priv_Buffer, WriteLength);

		//adjust the current size of the pool
		priv_AllocSize = WriteLength;

		priv_Buffer = (BYTE *)malloc(WriteLength);
		if(!priv_Buffer)
			return deFALSE;

		//make sure it is read/write
		priv_OpenFlags = deFile_Open_Read | deFile_Open_Write;
		priv_OwnBuffer = deTRUE;
	}

	//set the starting position
	priv_Position = 0;

	return deTRUE;
}

deBoolean deFileMemory::Open(IdeFileSystem *BaseFS, char *Filename, long OpenFlags)
{
	IdeFile *	File;
	deFSReal *	FSReal;

	//if no basefs, create a realfs
	if(!BaseFS)
	{
		FSReal = new deFSReal;
		if(!FSReal)
			return deFALSE;

		//store the basefs so we cn close it
		priv_BaseFS = FSReal;
		BaseFS = FSReal;
	}

	//open the file up
	File = BaseFS->Open(Filename, OpenFlags);
	if(!File)
		return deFALSE;

	//read the file in
	if(!Open(File))
	{
		File->Release();
		return deFALSE;
	}

	return deTRUE;
}

deBoolean deFileMemory::Open(IdeFile *BaseFile)
{
	//open up a file, read it into memory
	//create a buffer for the file
	priv_AllocSize = BaseFile->GetSize();
	priv_KnownSize = priv_AllocSize;
	priv_Buffer = (BYTE *)malloc(priv_AllocSize);
	if(!priv_Buffer)
		return deFALSE;

	//read the data into the buffer
	BaseFile->Read(priv_Buffer, priv_AllocSize);

	//store off what the basefs and file is that we are using
	priv_OpenFlags = BaseFile->GetOpenFlags();
	priv_OwnBuffer = deTRUE;
	priv_BaseFile = BaseFile;

	//if the file is writable, set the size to grab memory at
	priv_NewAllocSize = 0;
	if(priv_OpenFlags & deFile_Open_Write)
		priv_NewAllocSize = 65536;

	//all done
	return deTRUE;
}

int deFileMemory::Release()
{
	delete this;
	return 0;
}

deBoolean deFileMemory::Close()
{
	//make sure the buffer is valid
	if(!priv_Buffer)
		return deFALSE;

	//if a file exists, then wipe it out
	if(priv_BaseFile)
	{
		//before we wipe the file out, see if we need to write
		//the memory out
		if(priv_OpenFlags & deFile_Open_Write)
		{
			//yes, seek to the beginning and write the data
			priv_BaseFile->SetPosition(0, deFile_Seek_Start);
			priv_BaseFile->Write(priv_Buffer, priv_AllocSize);
		}

		//release the file
		priv_BaseFile->Release();
		priv_BaseFile = NULL;

		//if we had a base file, see if we have a basefs to let go of
		if(priv_BaseFS)
		{
			priv_BaseFS->Release();
			priv_BaseFS = NULL;
		}
	}

	//if we own the buffer, free it
	if(priv_OwnBuffer)
		free(priv_Buffer);
	
	//set all the variables
	priv_Buffer = NULL;
	priv_OwnBuffer = deFALSE;
	priv_OpenFlags = 0;
	priv_AllocSize = 0;
	priv_NewAllocSize = 0;
	priv_Position = 0;
	priv_KnownSize = 0;

	return deTRUE;
}

long deFileMemory::Read(void *Buffer, long Length)
{
	//make sure the buffer is valid
	if(!priv_Buffer)
		return deFALSE;

	//make sure we can read it
	if(!(priv_OpenFlags & deFile_Open_Read))
		return deFALSE;

	//make sure it is not out of bounds
	if((priv_Position + Length) > priv_KnownSize)
	{
		//out of bounds, update the length so we read to the end
		Length = (priv_Position + Length) - priv_KnownSize;
	}

	//if 0 length, return
	if(Length <= 0)
		return 0;

	//read the data
	memcpy(Buffer, &priv_Buffer[priv_Position], Length);

	//adjust the pointer
	priv_Position = priv_Position + Length;

	//return the length read (incase there was an issue)
	return Length;
}

long deFileMemory::Write(void *Buffer, long Length)
{
	//make sure the buffer is valid
	if(!priv_Buffer)
		return deFALSE;

	//make sure it can be written to
	if(!(priv_OpenFlags & deFile_Open_Write))
		return deFALSE;

	//verify that the Position + length is within the memory area
	if((priv_Position + Length) > priv_KnownSize)
	{
		//it is outside the current allocation, expand the allocation
		if(!SetSize(priv_Position + Length))
			return -1;
	}

	//write the data
	memcpy(&priv_Buffer[priv_Position], Buffer, Length);

	//adjust the pointer
	priv_Position = priv_Position + Length;

	//return the length written (incase there was an issue)
	return Length;
}

long deFileMemory::GetPosition()
{
	//get the current position in the file

	//make sure the buffer is valid
	if(!priv_Buffer)
		return -1;

	//return the current position
	return priv_Position;
}

long deFileMemory::SetPosition(long NewPosition, Seek SeekType)
{
	//set the current file position

	//make sure the pool is valid
	if(!priv_Buffer)
		return -1;

	//get the method to move and move
	switch(SeekType)
	{
		case deFile_Seek_Start:
			priv_Position = NewPosition;
			break;
				
		case deFile_Seek_Current:
			priv_Position = priv_Position + NewPosition;
			break;

		case deFile_Seek_End:
			//make sure it isn't invalid
			if((priv_KnownSize - NewPosition) < 0)
				return -1;

			priv_Position = priv_KnownSize - NewPosition;
			break;

		default:
			return -1;
	}

	return priv_Position;
}

long deFileMemory::GetSize()
{
	//get the size of the current file

	//if no pool, fail
	if(!priv_Buffer)
		return deFALSE;

	//get the size
	return priv_KnownSize;
}

deBoolean deFileMemory::SetSize(long NewSize)
{
	long WriteLength;

	//set the size of the memory pool

	//make sure the pool is valid
	if(priv_Buffer == NULL)
		return deFALSE;

	//make sure it can be changed
	if(!(priv_OpenFlags & deFile_Open_Write))
		return deFALSE;

	//figure out how much we have to expand by (based on the chunk size)

	//set the known size
	priv_KnownSize = NewSize;

	//if the known size is larger than the alloc size, resize the alloc
	if(priv_KnownSize > priv_AllocSize)
	{
		//divide by the size of a new block, add 1 to result
		WriteLength = (NewSize / priv_NewAllocSize) + 1;

		//multiply by size of a block to get new needed size
		WriteLength = WriteLength * priv_NewAllocSize;

		//reallocate the memory to the new size
		priv_Buffer = (BYTE *)realloc(priv_Buffer, WriteLength);

		//adjust the current size of the pool
		priv_AllocSize = WriteLength;
	}

	//if the position is outside the pool then move it back
	if(priv_Position > NewSize)
		priv_Position = NewSize;

	return deTRUE;
}

s64 deFileMemory::GetTime()
{
	return 0;
}

deBoolean deFileMemory::SetTime(s64 /*Time*/)
{
	return 0;
}

deBoolean deFileMemory::GetProperties(deFileProperties * /*Properties*/)
{
	return 0;
}

long deFileMemory::GetOpenFlags()
{
	return priv_OpenFlags;
}
