#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <shlobj.h>
#include <direct.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>

#include "files.h"

static char *local_dir;
static char *global_dir;

/*
Sets the local and global directories used by the other functions.
Local = ~/.dir, where config and user-installed files are kept.
Global = installdir, where installed data is stored.
*/
int SetGameDirectories(const char *local, const char *global)
{
	local_dir = _strdup(local);
	global_dir = _strdup(global);

	if( GetFileAttributes( local_dir ) == INVALID_FILE_ATTRIBUTES ) {
		_mkdir( local_dir );
	}

	return 0;
}


#define DIR_SEPARATOR	"\\"

static char *FixFilename(const char *filename, const char *prefix, int force)
{
	char *f, *ptr;
	size_t flen;
	size_t plen;
	
	plen = strlen(prefix) + 1;
	flen = strlen(filename) + plen + 1;
	
	f = (char *)malloc(flen);
	strcpy(f, prefix);
	strcat(f, DIR_SEPARATOR);
	strcat(f, filename);
	
	/* only the filename part needs to be modified */
	ptr = &f[plen+1];
	
	while (*ptr) {
		if ((*ptr == '/') || (*ptr == '\\') || (*ptr == ':')) {
			*ptr = DIR_SEPARATOR[0];
		} else if (*ptr == '\r' || *ptr == '\n') {
			*ptr = 0;
			break;
		} else {
			if (force) {
				*ptr = tolower(*ptr);
			}
		}
		ptr++;
	}

	return f;
}

/*
Open a file of type type, with mode mode.

Mode can be:
#define	FILEMODE_READONLY	0x01
#define	FILEMODE_WRITEONLY	0x02
#define	FILEMODE_READWRITE	0x04
#define FILEMODE_APPEND		0x08
Type is (mode = ReadOnly):
#define	FILETYPE_PERM		0x08 // try the global dir only 
#define	FILETYPE_OPTIONAL	0x10 // try the global dir first, then try the local dir
#define	FILETYPE_CONFIG		0x20 // try the local dir only

Type is (mode = WriteOnly or ReadWrite):
FILETYPE_PERM: error
FILETYPE_OPTIONAL: error
FILETYPE_CONFIG: try the local dir only
*/
FILE *OpenGameFile(const char *filename, int mode, int type)
{
	char *rfilename;
	char *openmode;
	FILE *fp;
	
	if ((type != FILETYPE_CONFIG) && (mode != FILEMODE_READONLY)) 
		return NULL;
	
	switch(mode) {
		case FILEMODE_READONLY:
			openmode = "rb";
			break;
		case FILEMODE_WRITEONLY:
			openmode = "wb";
			break;
		case FILEMODE_READWRITE:
			openmode = "w+";
			break;
		case FILEMODE_APPEND:
			openmode = "ab";
			break;
		default:
			return NULL;
	}

	if (type != FILETYPE_CONFIG) {
		rfilename = FixFilename(filename, global_dir, 0);
		
		fp = fopen(rfilename, openmode);
		
		free(rfilename);
		
		if (fp != NULL) {
			return fp;
		}
		
		rfilename = FixFilename(filename, global_dir, 1);
		
		fp = fopen(rfilename, openmode);
		
		free(rfilename);
		
		if (fp != NULL) {
			return fp;
		}
	}
	
	if (type != FILETYPE_PERM) {
		rfilename = FixFilename(filename, local_dir, 0);
		
		fp = fopen(rfilename, openmode);
		
		free(rfilename);
		
		if (fp != NULL) {
			return fp;
		}
		
		rfilename = FixFilename(filename, local_dir, 1);
		
		fp = fopen(rfilename, openmode);
		
		free(rfilename);
		
		return fp;
	}
	
	return NULL;
}

int CloseGameFile(FILE *pfd)
{
	return fclose(pfd);
}

/*
Get the filesystem attributes of a file

#define	FILEATTR_DIRECTORY	0x0100
#define FILEATTR_READABLE	0x0200
#define FILEATTR_WRITABLE	0x0400

Error or can't access it: return value of 0 (What is the game going to do about it anyway?)
*/
static int GetFA(const char *filename)
{
	struct stat buf;
	int attr;

	attr = 0;
	if (stat(filename, &buf) == 0) {
		if (buf.st_mode & _S_IFDIR) {
			attr |= FILEATTR_DIRECTORY;
		}
			
		if (buf.st_mode & _S_IREAD) {
			attr |= FILEATTR_READABLE;
		}
			
		if (buf.st_mode & _S_IWRITE) {
			attr |= FILEATTR_WRITABLE;
		}
	}
	
	return attr;
}

static time_t GetTS(const char *filename)
{
	struct stat buf;

	if (stat(filename, &buf) == 0) {
		return buf.st_mtime;
	}
	
	return 0;
}

int GetGameFileAttributes(const char *filename, int type)
{
	struct stat buf;
	char *rfilename;
	int attr;
	
	attr = 0;	
	if (type != FILETYPE_CONFIG) {
		rfilename = FixFilename(filename, global_dir, 0);
		
		if (stat(rfilename, &buf) == 0) {
			if (buf.st_mode & _S_IFDIR) {
				attr |= FILEATTR_DIRECTORY;
			}
				
			if (buf.st_mode & _S_IREAD) {
				attr |= FILEATTR_READABLE;
			}
				
			if (buf.st_mode & _S_IWRITE) {
				attr |= FILEATTR_WRITABLE;
			}
			
			free(rfilename);
		
			return attr;
		}
		
		free(rfilename);
		
		rfilename = FixFilename(filename, global_dir, 1);
		
		if (stat(rfilename, &buf) == 0) {
			if (buf.st_mode & _S_IFDIR) {
				attr |= FILEATTR_DIRECTORY;
			}
				
			if (buf.st_mode & _S_IREAD) {
				attr |= FILEATTR_READABLE;
			}
				
			if (buf.st_mode & _S_IWRITE) {
				attr |= FILEATTR_WRITABLE;
			}
			
			free(rfilename);
		
			return attr;
		}
		
		free(rfilename);
	}
	
	if (type != FILETYPE_PERM) {
		rfilename = FixFilename(filename, local_dir, 0);
		
		if (stat(rfilename, &buf) == 0) {
			if (buf.st_mode & _S_IFDIR) {
				attr |= FILEATTR_DIRECTORY;
			}
				
			if (buf.st_mode & _S_IREAD) {
				attr |= FILEATTR_READABLE;
			}
				
			if (buf.st_mode & _S_IWRITE) {
				attr |= FILEATTR_WRITABLE;
			}

			free(rfilename);
		
			return attr;
		}
		
		free(rfilename);
		
		rfilename = FixFilename(filename, local_dir, 1);
		
		if (stat(rfilename, &buf) == 0) {
			if (buf.st_mode & _S_IFDIR) {
				attr |= FILEATTR_DIRECTORY;
			}
				
			if (buf.st_mode & _S_IREAD) {
				attr |= FILEATTR_READABLE;
			}
				
			if (buf.st_mode & _S_IWRITE) {
				attr |= FILEATTR_WRITABLE;
			}
			
			free(rfilename);
		
			return attr;
		}
		
		free(rfilename);
		
	}
	
	return 0;
}

/*
Delete a file: local dir only
*/
int DeleteGameFile(const char *filename)
{
	char *rfilename;
	int ret;
	
	rfilename = FixFilename(filename, local_dir, 0);
	ret = _unlink(rfilename);
	free(rfilename);
	
	if (ret == -1) {
		rfilename = FixFilename(filename, local_dir, 1);
		ret = _unlink(rfilename);
		free(rfilename);
	}
	
	return ret;
}

/*
Create a directory: local dir only

TODO: maybe also mkdir parent directories, if they do not exist?
*/
int CreateGameDirectory(const char *dirname)
{
	char *rfilename;
	int ret;

	rfilename = FixFilename(dirname, local_dir, 0);
	ret = _mkdir(rfilename);
	free(rfilename);
	
	if (ret == -1) {
		rfilename = FixFilename(dirname, local_dir, 1);
		ret = _mkdir(rfilename);
		free(rfilename);
	}
	
	return ret;
}


/* This struct is private. */
typedef struct GameDirectory
{
	intptr_t localdir;		/* directory opened with _findfirst */
	intptr_t globaldir;
	
	struct _finddata_t tmplocalfinddata;
	struct _finddata_t localfinddata;
	struct _finddata_t tmpglobalfinddata;
	struct _finddata_t globalfinddata;

	char *localdirname;
	char *globaldirname;

	GameDirectoryFile tmp;	/* Temp space */
} GameDirectory;

/*
"Open" a directory dirname, with type type
Returns a pointer to a directory datatype

Pattern is the pattern to match
*/
void *OpenGameDirectory(const char *dirname, const char *pattern, int type)
{
	char* localdirname;
	char* globaldirname;
	char* filespec;

	intptr_t localdir;
	intptr_t globaldir;
	GameDirectory *gd;
	
	gd = (GameDirectory *)malloc(sizeof(GameDirectory));
	memset( gd, 0, sizeof(GameDirectory) );

	globaldir = -1;
	globaldirname = NULL;
	if (type != FILETYPE_CONFIG) {
		globaldirname = FixFilename(dirname, global_dir, 0);

		filespec = (char*) malloc(strlen(globaldirname)+1+strlen(pattern)+1);
		strcpy( filespec, globaldirname );
		strcat( filespec, DIR_SEPARATOR );
		strcat( filespec, pattern );

		globaldir = _findfirst(filespec, &gd->tmpglobalfinddata);
		free(filespec);

		if (globaldir == -1L) {
			free(globaldirname);
			
			globaldirname = FixFilename(dirname, global_dir, 1);

			filespec = (char*) malloc(strlen(globaldirname)+1+strlen(pattern)+1);
			strcpy( filespec, globaldirname );
			strcat( filespec, DIR_SEPARATOR );
			strcat( filespec, pattern );

			globaldir = _findfirst(filespec, &gd->tmpglobalfinddata);
			free(filespec);

			if (globaldir == -1L) {
				free(globaldirname);
				globaldirname = NULL;
			}
		}		
	}
	
	localdir = -1;
	localdirname = NULL;
	if (type != FILETYPE_PERM) {
		localdirname = FixFilename(dirname, local_dir, 0);

		filespec = (char*) malloc(strlen(localdirname)+1+strlen(pattern)+1);
		strcpy( filespec, localdirname );
		strcat( filespec, DIR_SEPARATOR );
		strcat( filespec, pattern );

		localdir = _findfirst(filespec, &gd->tmplocalfinddata);
		free(filespec);

		if (localdir == -1L) {
			free(localdirname);
			
			localdirname = FixFilename(dirname, local_dir, 1);

			filespec = (char*) malloc(strlen(localdirname)+1+strlen(pattern)+1);
			strcpy( filespec, localdirname );
			strcat( filespec, DIR_SEPARATOR );
			strcat( filespec, pattern );

			localdir = _findfirst(filespec, &gd->tmplocalfinddata);
			free( filespec );

			if (localdir == -1L) {
				free(localdirname);
				localdirname = NULL;
			}
		}
	}
	
	if (localdir == -1L && globaldir == -1L) {
		free( gd );
		return NULL;
	}

	gd->localdir = localdir;
	gd->globaldir = globaldir;

	gd->localdirname = localdirname;
	gd->globaldirname = globaldirname;

	return gd;
}

/*
This struct is public.

typedef struct GameDirectoryFile
{
	char *filename;
	int attr;
} GameDirectoryFile;
*/

/*
Returns the next match of pattern with the contents of dir

f is the current file
*/
GameDirectoryFile *ScanGameDirectory(void *dir)
{
	char *ptr;
	GameDirectory *directory;
	
	directory = (GameDirectory *)dir;
	
	if (directory->globaldir != -1L) {
		directory->globalfinddata = directory->tmpglobalfinddata;

		ptr = FixFilename(directory->globalfinddata.name, directory->globaldirname, 0);
		directory->tmp.attr = GetFA(ptr);
		directory->tmp.timestamp = GetTS(ptr);
		free(ptr);

		directory->tmp.filename = &directory->globalfinddata.name[0];

		if( _findnext( directory->globaldir, &directory->tmpglobalfinddata ) == -1 ) {
				_findclose(directory->globaldir);
				free(directory->globaldirname);
		
				directory->globaldir = -1L;
				directory->globaldirname = NULL;
		}

		return &directory->tmp;
	}
	
	if (directory->localdir != -1L) {
		directory->localfinddata = directory->tmplocalfinddata;

		ptr = FixFilename(directory->localfinddata.name, directory->localdirname, 0);
		directory->tmp.attr = GetFA(ptr);
		directory->tmp.timestamp = GetTS(ptr);
		free(ptr);

		directory->tmp.filename = &directory->localfinddata.name[0];

		if( _findnext( directory->localdir, &directory->tmplocalfinddata ) == -1 ) {
				_findclose(directory->localdir);
				free(directory->localdirname);
		
				directory->localdir = -1L;
				directory->localdirname = NULL;
		}

		return &directory->tmp;
	}
	
	return NULL;
}

/*
Close directory
*/
int CloseGameDirectory(void *dir)
{
	GameDirectory *directory = (GameDirectory *)dir;
	
	if (directory != NULL) {

		if (directory->localdirname != NULL) {
			free(directory->localdirname);
		}
		if (directory->globaldirname != NULL) {
			free(directory->globaldirname);
		}
		if (directory->localdir != -1L) {
			_findclose(directory->localdir);
		}
		if (directory->globaldir != -1L) {
			_findclose(directory->globaldir);
		}
			
		return 0;
	}
	return -1;
}

/*
  Game-specific helper function.
 */
static int try_game_directory(const char *dir, const char *file)
{
	char tmppath[MAX_PATH];
	DWORD retr;

	strncpy(tmppath, dir, MAX_PATH-32);
	tmppath[MAX_PATH-32] = 0;
	strcat(tmppath, file);
	
	retr = GetFileAttributes(tmppath);

	if( retr == INVALID_FILE_ATTRIBUTES ) {
		return 0;
	}

	/*
	  TODO - expand this check to check for read access
     */
	return 1;
}

/*
  Game-specific helper function.
 */
static int check_game_directory(const char *dir)
{
	if (!dir || !*dir) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\avp_huds")) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\avp_huds\\alien.rif")) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\avp_rifs")) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\avp_rifs\\temple.rif")) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\fastfile")) {
		return 0;
	}
	
	if (!try_game_directory(dir, "\\fastfile\\ffinfo.txt")) {
		return 0;
	}
	
	return 1;
}


static char* GetLocalDirectory(void)
{
	char folderPath[2 * MAX_PATH + 10];
	char* localdir;

	const char* homedrive;
	const char* homepath;
	char* homedir;

	homedir = NULL;

	/*
	  TODO - should check that the directory is actually usable.
     */

	/*
	   1. Check registry (not currently implemented)
	 */

	/*
	   2. CSIDL_LOCAL_APPDATA with SHGetFolderPath
	 */
	if( homedir == NULL ) {
		if( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_LOCAL_APPDATA,
			NULL, SHGFP_TYPE_CURRENT, &folderPath[0] ) ) ) {

			homedir = _strdup( folderPath );
		}
	}

	/*
	   3. CSIDL_APPDATA with SHGetFolderPath
     */
	if( homedir == NULL ) {
		if( SUCCEEDED( SHGetFolderPath( NULL, CSIDL_APPDATA,
			NULL, SHGFP_TYPE_CURRENT, &folderPath[0] ) ) ) {

			homedir = _strdup( folderPath );
		}
	}

	/*
	   4. HOMEDRIVE+HOMEPATH
     */

	if( homedir == NULL ) {
		homedrive = getenv("HOMEDRIVE");
		homepath  = getenv("HOMEPATH");

		if( homedrive == NULL ) {
			homedrive = "";
		}

		if( homepath != NULL ) {

			homedir = (unsigned char*)malloc(strlen(homedrive)+strlen(homepath)+1);
			
			strcpy(homedir, homedrive);
			strcat(homedir, homepath);
		}
	}

	/*
	   5. HOME
     
     */
	if( homedir == NULL ) {
		homepath = getenv("HOME");

		if( homepath != NULL ) {
			homedir = _strdup(homepath);
		}
	}

	/* 
	  6. CWD
     */
	if( homedir == NULL ) {
		homedir = _strdup(".");
	}

	localdir = (unsigned char*)malloc(strlen(homedir) + 10);
	strcpy(localdir, homedir);
	strcat(localdir, "\\AvPLinux"); // temp name, maybe

	free(homedir);

	return localdir;
}

static const char* GetGlobalDirectory(const char* argv0)
{
	char* gamedir;
	char* tmp;

	/*
	1. $AVP_DATA overrides all
	2. Registry Setting
	3. executable path from argv[0]
	4. current directory
	*/
	
	/* 1. $AVP_DATA */
	gamedir = getenv("AVP_DATA");
	
	/* $AVP_DATA overrides all, so no check */
	
	/* 2. Registry Setting */
	/* TODO */

	if (gamedir == NULL) {
		/* 3. executable path from argv[0] */
		tmp = _strdup(argv0);
		
		if (tmp == NULL) {
			/* ... */
			fprintf(stderr, "GetGlobalDirectory failure\n");
			exit(EXIT_FAILURE);
		}

		gamedir = strrchr(tmp, DIR_SEPARATOR[0]);

		if (gamedir != NULL) {
			*gamedir = 0;
			gamedir = tmp;
		
			if (!check_game_directory(gamedir)) {
				gamedir = NULL;
			}
		}
	}

	/* 4. current directory */
	return _strdup(".");
}

/*
  Game-specific initialization
 */
void InitGameDirectories(char *argv0)
{
	extern char *SecondTex_Directory;
	extern char *SecondSoundDir;

	const char* localdir;
	const char* globaldir;

	SecondTex_Directory = "graphics\\";
	SecondSoundDir = "sound\\";

	localdir  = GetLocalDirectory();
	globaldir = GetGlobalDirectory(argv0);

	assert(localdir != NULL);
	assert(globaldir != NULL);
	
	/* last chance sanity check */
	if (!check_game_directory(globaldir)) {
		fprintf(stderr, "Unable to find the AvP gamedata.\n");
		fprintf(stderr, "The directory last examined was: %s\n", globaldir);
		fprintf(stderr, "Has the game been installed and\n");
		fprintf(stderr, "are all game files lowercase?\n");
		exit(EXIT_FAILURE);
	}

	SetGameDirectories(localdir, globaldir);
}
