/******************************************************************************
*	File		-	binary_file_buffer.cpp
*	Author		-	Joey Pollack
*	Date		-	2020/01/24 (y/m/d)
*	Mod Date	-	2020/01/24 (y/m/d)
*	Description	-	Reads an entire binary file into memory and allows for
*					extracting the data in chunks. Much faster than reading
*					large files chunk by chunk.
******************************************************************************/

#include "binary_file_buffer.h"
#include <fstream>
#include <string.h>

namespace lunarium
{
	BinaryFileBuffer::BinaryFileBuffer()
		:	mbIsLoaded(false), mpBuffer(nullptr), mFileSize(0), mBufferPos(0), mFileName("")
	{
	}

	BinaryFileBuffer::~BinaryFileBuffer()
	{
		Unload();
	}

	bool BinaryFileBuffer::NewFile(int reserve_size)
	{
		if (mbIsLoaded)
		{
			Unload();
		}

		mpBuffer = new unsigned char[reserve_size];
		mFileSize = reserve_size;
		mbIsLoaded = true;

		return true;
	}

	bool BinaryFileBuffer::WriteFile(const char* filename, bool include_extra_bytes)
	{
		std::ofstream ofs(filename, std::ios_base::binary | std::ios_base::trunc);
		if (!ofs.is_open())
		{
			return false;
		}

		// Only writing up to the buffer pos because the rest will be garbage data
		int write_size = mBufferPos;
		if (include_extra_bytes)
		{
			// Unless the user says otherwise
			write_size = mFileSize;
		}

		ofs.write((const char*)mpBuffer, write_size);
		ofs.close();
		ofs.clear();
		mFileName = filename;
		return true;
	}

	bool BinaryFileBuffer::LoadFile(const char * filename)
	{
		if (mbIsLoaded)
		{
			return false;
		}

		std::ifstream ifs(filename, std::ios_base::binary);
		if (!ifs.is_open())
		{
			return false;
		}

		int start = ifs.tellg();
		ifs.seekg(0, std::ios_base::end);
		int end = ifs.tellg();
		ifs.seekg(0, std::ios_base::beg);
		
		mFileSize = end - start;
		mpBuffer = new unsigned char[mFileSize];

		ifs.read((char*)mpBuffer, mFileSize);

		ifs.close();
		ifs.clear();

		mFileName = filename;
		mbIsLoaded = true;
		return true;
	}

	void BinaryFileBuffer::Unload()
	{
		if (!mbIsLoaded)
			return;

		delete[] mpBuffer;
		mpBuffer = nullptr;

		mFileSize = 0;
		mFileName = "";
		mBufferPos = 0;
		mbIsLoaded = false;
	}

	bool BinaryFileBuffer::IsLoaded() const
	{
		return mbIsLoaded;
	}

	const std::string& BinaryFileBuffer::LoadedFileName() const
	{
		return mFileName;
	}

	int BinaryFileBuffer::GetFileSize() const
	{
		return mFileSize;
	}
	
	const unsigned char* BinaryFileBuffer::GetBuffer() const
	{
		return mpBuffer;
	}

	/////////////////////////////////////////////////////////////////////
	//				READ METHODS
	/////////////////////////////////////////////////////////////////////
	bool BinaryFileBuffer::Read(char * buffer, int numBytes)
	{
		if (!mbIsLoaded || mBufferPos >= mFileSize)
			return false;

		memcpy(buffer, &mpBuffer[mBufferPos], numBytes);
		mBufferPos += numBytes;

		return true;
	}

	int BinaryFileBuffer::ReadInt()
	{
		int value = 0;
		Read((char*)&value, sizeof(int));
		return value;
	}

	short BinaryFileBuffer::ReadShort()
	{
		short value = 0;
		Read((char*)&value, sizeof(short));
		return value;		
	}

	char BinaryFileBuffer::ReadByte()
	{
		char value = 0;
		Read((char*)&value, sizeof(char));
		return value;		
	}

	unsigned int BinaryFileBuffer::ReadUInt()
	{
		unsigned int value = 0;
		Read((char*)&value, sizeof(unsigned int));
		return value;		
	}

	unsigned short BinaryFileBuffer::ReadUShort()
	{
		unsigned short value = 0;
		Read((char*)&value, sizeof(unsigned short));
		return value;		
	}

	unsigned char BinaryFileBuffer::ReadUByte()
	{
		unsigned char value = 0;
		Read((char*)&value, sizeof(unsigned char));
		return value;		
	}

	/////////////////////////////////////////////////////////////////////
	//				WRITE METHODS
	/////////////////////////////////////////////////////////////////////
	bool BinaryFileBuffer::Write(char* buffer, int num_bytes)
	{
		if (!mbIsLoaded)
		{
			return false;
		}

		if (mBufferPos + num_bytes >= mFileSize)
		{
			// Reserve more space
			int new_size = mFileSize * 2 + num_bytes;	// adding num_bytes to make sure the write will fit
			unsigned char* new_buffer = new unsigned char[new_size];
			memcpy(new_buffer, mpBuffer, mFileSize);
			delete[] mpBuffer;
			mpBuffer = new_buffer;
			mFileSize = new_size;
		}

		memcpy(&mpBuffer[mBufferPos], buffer, num_bytes);
		mBufferPos += num_bytes;
		return true;
	}

	void BinaryFileBuffer::WriteInt(int value)
	{
		Write((char*)&value, sizeof(int));
	}

	void BinaryFileBuffer::WriteShort(short value)
	{
		Write((char*)&value, sizeof(short));		
	}

	void BinaryFileBuffer::WriteByte(char value)
	{
		Write((char*)&value, sizeof(char));		
	}

	void BinaryFileBuffer::WriteUInt(unsigned int value)
	{
		Write((char*)&value, sizeof(unsigned int));	
	}

	void BinaryFileBuffer::WriteUShort(unsigned short value)
	{
		Write((char*)&value, sizeof(unsigned short));			
	}

	void BinaryFileBuffer::WriteUByte(unsigned char value)
	{
		Write((char*)&value, sizeof(unsigned char));	
	}

	void BinaryFileBuffer::SeekTo(int pos)
	{
		mBufferPos = pos;

		if (mBufferPos < 0)
			mBufferPos = 0;

		if (mBufferPos >= mFileSize)
			mBufferPos = mFileSize - 1;
	}

}