#include "OgreADPArchive.h"

#include <OgreStringConverter.h>
#include <OgreLogManager.h>

#include <android/log.h>

#define  LOG_TAG    "ADPArchive"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

namespace Ogre{

	ADPArchive::ADPArchive(const String& name, const String& archType, ADPSource *source)
		:Archive(name, archType), mBuffer(0), mBufferSize(0), mSource(source)
	{
	}

	ADPArchive::~ADPArchive()
	{
		unload();
	}

	bool ADPArchive::isCaseSensitive() const
	{
		return true;
	}

	void ADPArchive::load()
	{
		Ogre::DataStreamPtr in;
		std::ifstream *stream = 0;
		if(mSource)
		{
			in = mSource->getPackage(mName);
			if(in.isNull())
			{
				OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND,
					"Cannot open file: " + mName,
					"ADPArchive::load");
			}
		}
		else
		{
			stream = new std::ifstream(mName.c_str(), std::ios_base::in | std::ios_base::binary);
			if(!stream->is_open())
			{
				delete stream;
				OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND,
					"Cannot open file: " + mName,
					"ADPArchive::load");
			}
			in = Ogre::DataStreamPtr(new FileStreamDataStream(stream));
		}
		
		// First are magic characters for the file type
		char c1 = 0, c2 = 0, c3 = 0;
		in->read(&c1, 1);
		in->read(&c2, 1);
		in->read(&c3, 1);

		if(c1 == 'a' && c2 == 'd' && c3 == 'p')
		{
			int version = 0;
			in->read(&version, sizeof(int));

			int numFiles = 0;
			in->read(&numFiles, sizeof(int));
			
			Ogre::LogManager::getSingleton().logMessage("Num files: " + Ogre::StringConverter::toString(numFiles));
		
			char *name = 0;
			int nameBufSize = 0;
			size_t totalSize = 0;
			for(int i = 0; i < numFiles; ++i)
			{
				ADPEntry entry;

				// Read in each entry
				int nameLen = 0;
				in->read(&nameLen, sizeof(int));
		
				// Reallocate buffer for larger names
				if(nameLen > nameBufSize)
				{
					if(name)
						OGRE_FREE(name, Ogre::MEMCATEGORY_GENERAL);
					name = (char*)OGRE_MALLOC(nameLen, Ogre::MEMCATEGORY_GENERAL);
					nameBufSize = nameLen;
				}

				in->read(name, nameLen);

				unsigned int offset = 0;
				in->read(&offset, sizeof(unsigned int));
				
				unsigned int size = 0;
				in->read(&size, sizeof(unsigned int));

				entry.name = String(name, nameLen);
				entry.offset = offset;
				entry.length = size;
				mEntries.push_back(entry);

				totalSize += size;
			}

			if(name)
				OGRE_FREE(name, Ogre::MEMCATEGORY_GENERAL);

			// Read in the rest of the buffer
			mBuffer = (Ogre::uint8*)OGRE_MALLOC(totalSize, Ogre::MEMCATEGORY_GENERAL);
			in->read(mBuffer, totalSize);
			mBufferSize = totalSize;
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND,
				"Unsupported ADP archive format: " + mName,
				"ADPArchive::load");
		}
		
		if(stream)
			delete stream;
	}

	void ADPArchive::unload()
	{
		mEntries.clear();

		if(mBuffer)
			OGRE_FREE(mBuffer, Ogre::MEMCATEGORY_GENERAL);
		mBuffer = 0;
		mBufferSize = 0;
	}

	DataStreamPtr ADPArchive::open(const Ogre::String &filename, bool readOnly) const
	{
		DataStreamPtr stream;
		for(Ogre::list<ADPEntry>::type::const_iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			if(i->name == filename)
			{				
				Ogre::uint8 *buffer = mBuffer + i->offset;
				stream = DataStreamPtr(new Ogre::MemoryDataStream(buffer, i->length, false, readOnly));
			}
		}
		return stream;
	}

	DataStreamPtr ADPArchive::create(const Ogre::String &filename) const
	{
		return DataStreamPtr();
	}

	void ADPArchive::remove(const String &filename) const
	{

	}

	StringVectorPtr ADPArchive::list(bool recursive, bool dirs)
	{
		StringVectorPtr files(new StringVector);

		for(Ogre::list<ADPEntry>::type::iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			files->push_back(i->name);
		}

		return files;
	}

	FileInfoListPtr ADPArchive::listFileInfo(bool recursive, bool dirs)
	{
		FileInfoListPtr files(new FileInfoList);

		for(Ogre::list<ADPEntry>::type::iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			FileInfo info;
			info.archive = this;
			info.basename = info.filename = info.path = i->name;
			info.compressedSize = info.uncompressedSize = i->length;
			files->push_back(info);
		}

		return files;
	}

	StringVectorPtr ADPArchive::find(const String& pattern, bool recursive, bool dirs)
	{
		StringVectorPtr files(new StringVector);

		for(Ogre::list<ADPEntry>::type::iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			if(StringUtil::match(i->name, pattern))
				files->push_back(i->name);
		}

		return files;
	}

	FileInfoListPtr ADPArchive::findFileInfo(const String& pattern, bool recursive, bool dirs)
	{
		FileInfoListPtr files(new FileInfoList);

		for(Ogre::list<ADPEntry>::type::iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			if(StringUtil::match(i->name, pattern))
			{				
				FileInfo info;
				info.archive = this;
				info.basename = info.filename = info.path = i->name;
				info.compressedSize = info.uncompressedSize = i->length;
				files->push_back(info);
			}
		}

		return files;
	}

	bool ADPArchive::exists(const String& filename)
	{
		for(Ogre::list<ADPEntry>::type::iterator i = mEntries.begin(); i != mEntries.end(); ++i)
		{
			if(i->name == filename)
				return true;
		}
		return false;
	}

	time_t ADPArchive::getModifiedTime(const Ogre::String &filename)
	{
		return 0;
	}

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

	const String &ADPArchiveFactory::getType() const
	{
		static String type = "ADP";
		return type;
	}

}