#include "tier0/platform.h"
#include "tier1/commandline.h"
#include "filesystem.h"
#include "unistd.h"
#include <cstdio>

#define BASEDIR "rtt"
#define GAMEDIR "funnygame"


struct PackHeader_t
{
	char id[8];
	unsigned long long offset;
	unsigned long long size;
};
struct PackDirectory_t
{
	char name[56];
	unsigned long long offset;
	unsigned long long size;
};
struct Pack_t
{
	FILE* handle;
	PackHeader_t header;
	CUtlVector<PackDirectory_t> files;
};
struct FileDirectory_t
{
	CUtlString path;
	Pack_t pack;
};


CUtlString fs_basedir;
CUtlString fs_gamedir;
CUtlSelfReferencingVector<FileDirectory_t> fs_directories;

class CFileSystem: public IFileSystem
{
public:
	static void AddDirectory( const char *psz );
	static void AddFile( const char *psz );
};


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void IFileSystem::InitFilesystem()
{
#ifdef STATIC_BUILD
	fs_basedir = ICommandLine::ParamValue("-basedir");
	if ( fs_basedir == 0 )
		fs_basedir=BASEDIR;
	if ( fs_basedir.GetString()[0] == '-' )
		fs_basedir=BASEDIR;
	fs_gamedir = ICommandLine::ParamValue("-gamedir");
	if ( fs_gamedir == 0 )
		fs_gamedir=GAMEDIR;
	if ( fs_gamedir.GetString()[0] == '-' )
		fs_gamedir=GAMEDIR;

	AddGameDirectory(fs_gamedir);
#endif
	AddGameDirectory(".");
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CFileSystem::AddFile( const char *psz )
{
	CUtlString extension = Plat_GetExtension(psz);
	if (extension=="pak")
	{
		IFileSystem::LoadPackFile(psz);
	};
}

//-----------------------------------------------------------------------------
// Adds directory which can contain pack files
//-----------------------------------------------------------------------------
void IFileSystem::AddGameDirectory( const char *psz )
{
	FileDirectory_t dir = {};
	dir.path = psz;
	fs_directories.AppendTail(dir);

	Plat_ListDirRecursive(psz, CFileSystem::AddFile, 0);
	for (auto &dir: fs_directories)
	{
		V_printf("%s\n",(char*)dir.path);
	};

};

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool IFileSystem::LoadPackFile( const char *szFilename )
{
	Pack_t pack = {};
	PackHeader_t header = {};
	unsigned long long nNumFiles = 0;
	PackDirectory_t *pDirs = NULL;

	FILE* f = V_fopen(szFilename, "rb");
	if (!f)
		Plat_FatalErrorFunc("Failed to open %s",szFilename);
	V_fread(&header,1,sizeof(header),f);
	// check for rttpacku
	if (
	header.id[0]!='r' || header.id[1] != 't' || header.id[2] != 't' || header.id[3]!='p' ||
	header.id[4]!='a' || header.id[5] != 'c' || header.id[6] != 'k' || header.id[7]!='u'
	)
	{
		Plat_FatalErrorFunc("%s is not a pack file",szFilename);
	}
	nNumFiles = header.size/sizeof(PackDirectory_t);

	pDirs = (PackDirectory_t*)V_malloc(header.size);
	V_fseek(f, header.offset, SEEK_SET);
	V_fread(pDirs, sizeof(PackDirectory_t), nNumFiles, f);
	pack.header = header;
	pack.handle = f;
	pack.files = CUtlVector<PackDirectory_t>(nNumFiles);
	V_memcpy(pack.files.GetData(),pDirs, header.size);
	V_free(pDirs);

	nNumFiles = header.size/sizeof(PackDirectory_t);
	
	FileDirectory_t fd = {};
	fd.path = szFilename;
	fd.pack = pack;
	fs_directories.AppendTail(fd);

	return true;
}

//-----------------------------------------------------------------------------
// Creates path
//-----------------------------------------------------------------------------
void IFileSystem::CreatePath( const char *szPath )
{

}

//-----------------------------------------------------------------------------
// Opens file
// If it is located in a pack then it can only be read
//-----------------------------------------------------------------------------
FileHandle_t IFileSystem::Open( const char *szFilename, EFileOptions options )
{
	if (options == IFILE_READ)
	{
		FILE *file = V_fopen(szFilename, "rb");
		
		/* found in fs */
		if ( file != NULL )
		{
			FileHandle_t filehandle = new FileHandle_s;	
			filehandle->file = file;
			filehandle->nPtr = 0;
			filehandle->options = IFILE_READ;
			
			/* get size */
			V_fseek(file, 0, SEEK_END);
			filehandle->nSize = V_ftell(file);
			V_fseek(file, 0, SEEK_SET);

			return filehandle;
		}

		/* not found in fs, try to search in packs */
		for ( auto &pak: fs_directories )
		{
			for ( auto &file: pak.pack.files )
			{
				if ( !strncmp(file.name, szFilename, 56) )
				{
					FileHandle_t filehandle = new FileHandle_s;	
					filehandle->file = 0;
					filehandle->parent = pak.pack.handle;
					filehandle->nSize = file.size;
					filehandle->nOffset = file.offset;
					filehandle->nPtr = 0;
					filehandle->options = IFILE_READ;
					return filehandle;
				}
			}
		};
	}
	return 0;
}

//-----------------------------------------------------------------------------
// Closes file
//-----------------------------------------------------------------------------
void IFileSystem::Close( FileHandle_t file )
{
	/* close only fs files */
	if (file->file)
	{
		V_fclose(file->file);
	}
	delete file;

}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
size_t IFileSystem::Size( FileHandle_t file )
{
	return file->nSize;
}

//-----------------------------------------------------------------------------
// Reads nSize bytes of file
//-----------------------------------------------------------------------------
size_t IFileSystem::Read( FileHandle_t file, void *pOutput, size_t nSize)
{
	if (file->file)
	{
		size_t readsize = V_fread(pOutput, 1, nSize, file->file);
		file->nPtr+=readsize;
		return readsize;
	}

	size_t readsize = V_fseek(file->parent, file->nOffset, file->nPtr);
	V_fread(pOutput, 1, nSize, file->parent);

	return readsize;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
size_t IFileSystem::ReadLine( FileHandle_t file, void *pOutput, size_t nSize)
{

}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
size_t IFileSystem::Write( FileHandle_t file, void *pInput, size_t nSize)
{

}
