//Copyright (C) 2007 SiegeLord
//See ReadMe.txt and License.txt for copying information

#include "../include/engine/fileio.h"
#include "../include/engine/error.h"
#include "../include/engine/zipfile.h"

PACKFILE* g_pDataZip;
PACKFILE* g_pModZip;

string g_sDataFolder;
string g_sExtension;//used for folder parsing stuff

void InitFileIO(const char* datafolder, const char* modzip)
{
	g_sDataFolder = datafolder;
	LoadDataZip();
	LoadModZip(modzip);
}

void DeInitFileIO()
{
	g_sDataFolder.clear();
	UnloadDataZip();
	UnloadModZip();
}

void LoadDataZip()
{
	string path = g_sDataFolder + string(".zdt");
	g_pDataZip = pack_fopen_zip_dir(g_sDataFolder.c_str());
}

void UnloadDataZip()
{
	if(g_pDataZip)
	{
		pack_fclose(g_pDataZip);
		g_pDataZip = 0;
	}
}

void LoadModZip(const char* filename)
{
	UnloadModZip();
	g_pModZip = pack_fopen_zip_dir(filename);
}

void UnloadModZip()
{
	if(g_pModZip)
	{
		pack_fclose(g_pModZip);
		g_pModZip = 0;
	}
}

PACKFILE* LoadFile(const char* filename)
{
	PACKFILE* ret = 0;

	//first try the mod zip, if it exists
	if(g_pModZip != 0)
	{
		ret = pack_fopen_zip(g_pModZip, filename);

		if(ret)
			return ret;
	}

	//then try the file system
	string path;
	path = g_sDataFolder;
	path += "/";
	path += filename;

	ret = pack_fopen(path.c_str(), "r");

	if(!ret)
	{
		if(!g_pDataZip)
			Throw("Could not load file!\n", path.c_str());
		path = filename;

		ret = pack_fopen_zip(g_pDataZip, filename);

		if(!ret)
			Throw("Could not load file!\n", path.c_str());
	}

	return ret;
}

void CloseFile(PACKFILE* file)
{
	_ASSERT(file);
	pack_fclose(file);
}

int ReadFile(PACKFILE* file, void** buffer)
{
#define _BUF_SIZE (1024 * 1024)

	int size = 0;
	char* buf = 0;

	while(true)
	{
		char* buffer = new char[_BUF_SIZE];//create a read buffer
		int read = pack_fread(buffer, _BUF_SIZE, file);//try to read _BUF_SIZE worth of bytes into it

		char* buf2 = new char[size + read];//our new data buffer
		memcpy(buf2, buf, size);//copy the old buffer into the new buffer
		memcpy(buf2 + size, buffer, read);//copy the new read data at the end of the new buffer

		size += read;

		delete buf;//delete old buffer
		buf = buf2;

		if(read < _BUF_SIZE)//eof reached, we are done
			break;
	}

	char* buf2 = new char[size + 1];

	memcpy(buf2, buf, size);//make yet another buffer

	buf2[size] = 0;//make it end with a 0
	delete buf;
	buf = buf2;

	*buffer = buf;
	return size;
}

int FolderCallback(const char * restrict filename, int attrib, void * restrict param)
{
	SFolder* folder = (SFolder*)param;

	string datafile = g_sDataFolder;
	datafile = datafile + string("/") + folder->full_name;

	int n = datafile.size();

	string file(filename);
	string token = file.substr(n, file.size() - n);

	if(token == "" || token == "." || token == "..")
		return 0;

	SFolder* child = new SFolder;

	child->full_name = folder->full_name + token + string("/");
	child->root = folder;
	child->name = token;

	folder->children.push_back(child);

	return 0;
};

int FileCallback(const char *filename, int attrib, void *param)
{
	SFolder* folder = (SFolder*)param;

	string datafile = g_sDataFolder;
	datafile = datafile + string("/") + folder->full_name;

	int n = datafile.size();

	string file(filename);
	string token = file.substr(n, file.size() - n);

	if(token == "" || token == "." || token == "..")
		return 0;

	if(token.find(g_sExtension) == token.npos)
		return 0;

	folder->files.push_back(token);

	return 0;
};

//should be called after the normal version
//path, as always, does not contain the data folder
void ParseFolderZip(SFolder* restrict folder, const char* restrict path)
{
	string root_path(path);//path that we shall look for at the beginning of the file names

	char buf[512];
	PACKFILE* zip = g_pModZip;
	if(zip == 0)
		zip = g_pDataZip;
MARK:;//I LOVE GOTO
	const int size = zip_get_num_dir_entries(zip);
	FOR1(size)
	{
		zip_get_name(zip, ii, buf);
		string filename(buf);
		int a = filename.find(root_path);
		if(a != 0)
			continue;
		a = filename.find(g_sExtension);
		if(a == (int)filename.npos)
			continue;
		//now strip of each folder name, and find/make them
		SFolder* cur_folder = folder;
		filename = filename.substr(root_path.size(), filename.size() - root_path.size());
		a = 0;

		int b;
		string path;
		string token;
		while(true)
		{
			b = filename.find_first_of('/', a);

			if(b == (int)filename.npos)//it's a file
			{
				//see if it already exists in the folder
				bool found = false;
				const int size2 =  cur_folder->files.size();
				FOR2(size2)
				{
					if(cur_folder->files[jj] == filename)
					{
						found = true;
						break;
					}
				}
				if(!found)//add it
				{
					token = filename.substr(a);
					cur_folder->files.push_back(token);
				}
				break;
			}
			else//still in some folder, isolate the folder name and search for it
				//if can't find it, make a new one
			{
				token = filename.substr(a, b - a);
				bool found = false;
				const int size2 = cur_folder->children.size();
				FOR2(size2)
				{
					if(token == cur_folder->children[jj]->name)//found the folder, make it the cur_folder
					{
						cur_folder = cur_folder->children[jj];
						found = true;
						break;
					}
				}
				if(!found)//make a new one
				{
					SFolder* fold = new SFolder;
					fold->name = token;
					fold->root = cur_folder;
					fold->full_name = cur_folder->full_name + token + string("/");
					cur_folder->children.push_back(fold);
					cur_folder = fold;
				}
			}
			a = b + 1;//skip over the slash
		}
	}
	if(zip == g_pModZip)
	{
		zip = g_pDataZip;
		goto MARK;
	}
}

void ParseFolder(SFolder* folder, const char* path, const char* extension)
{
	_ASSERT(folder);

	g_sExtension = extension;

	string dir = g_sDataFolder + "/" + path + "*";
	for_each_file_ex(dir.c_str(),FA_DIREC,0,FolderCallback,folder);//first get folder names from the filesys

	for_each_file_ex(dir.c_str(),0,0,FileCallback,folder); //now get files in this folder
															//from the filesys

	//now parse all lower folders
	const int size = folder->children.size();
	FOR1(size)
	{
		ParseFolder(folder->children[ii], path, extension);
	}

	ParseFolderZip(folder, path);
}

void DumpFilesIntoArray(SFolder* folder, vector<string>* array)
{
	_ASSERT(folder);
	_ASSERT(array);

	const int size = folder->files.size();
	FOR1(size)
	{
		string filename = folder->full_name + folder->files[ii];
		array->push_back(filename);
	}
	const int size2 = folder->children.size();
	FOR1(size2)
	{
		DumpFilesIntoArray(folder->children[ii], array);
	}
}

void ForEachFile(SFolder* folder, void (*callback)(const char *filename, const char* name, void *param), void *param)
{
	_ASSERT(folder);
	_ASSERT(callback);

	const int size = folder->files.size();
	FOR1(size)
	{
		string filename = folder->full_name + folder->files[ii];
		(*callback)(filename.c_str(), folder->files[ii].c_str(), param);
	}
	const int size2 = folder->children.size();
	FOR1(size2)
	{
		ForEachFile(folder->children[ii], callback, param);
	}
}
