//
//file/file.c
//
//
//-UserX

#include "base/mem.h"
#include "file/file.h"
#include "base/str.h"
#include "base/logger.h"
#include "misc/compat.h"

#ifdef _UNIX_
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#endif

/**
@name file
*/
//@{

FileHandle blankfilehandle = BLANKFILEHANDLE;

FileHandle *fhstdin = NULL;
FileHandle *fhstdout = NULL;
FileHandle *fhstderr = NULL;

StringArrayHandle *filepaths = NULL;
int defaultpathtotal = 0;

int filesysactive = 0;

/**
Initializes the file handler.
*/
void fileInit(void) {
	fhstdin = memCopy(&blankfilehandle, sizeof(FileHandle), "FileHandle", "stdin");
	fhstdout = memCopy(&blankfilehandle, sizeof(FileHandle), "FileHandle", "stdout");
	fhstderr = memCopy(&blankfilehandle, sizeof(FileHandle), "FileHandle", "stderr");
	fhstdin->filename = stringCopy("stdin");
	fhstdout->filename = stringCopy("stdout");
	fhstderr->filename = stringCopy("stderr");
	fhstdin->file = stdin;
	fhstdout->file = stdout;
	fhstderr->file = stderr;
	filepaths = saMake();
	fileClearPath(NULL);
	filesysactive = 1;
}

/**
Internal: Does the actual opening of a file.
@param filename The file to open.
@param mode The mode to open the file in, this is identical to fopen()'s.
@return The pointer to a FILE.
*/
FILE *fileOpenFile(char *filename, char *mode) {
	char *s;
	FILE *fh;
#ifdef _UNIX_
	struct passwd *pw;
#endif
	if(filename == NULL || mode == NULL){
		return NULL;
	}
	if(filename[0] == '~') {
		if(filename[1] == '/') {
#ifdef _UNIX_
			pw = getpwuid(geteuid());
			if(pw == NULL || pw->pw_dir == NULL) {
#endif
				s = getenv("HOME");
				if(s == NULL) {
					s = stringCopyMany("/home/iip", filename + 1, NULL);
				} else {
					s = stringCopyMany(s, filename + 1, NULL);
				}
#ifdef _UNIX_
			} else {
				s = stringCopyMany(pw->pw_dir, filename + 1, NULL);
			}
#endif
		} else {
			s = stringCopyMany("/home/", filename + 1, NULL);
		}
		fh = fopen(s, mode);
		stringFree(s);
		return fh;
	} else {
		return fopen(filename, mode);
	}

}

/**
Opens a file.
@param filename The file to open.
@param mode The mode to open the file in, this is identical to fopen()'s.
@return The pointer to a FileHandle.
*/
FileHandle *fileOpen(char *filename, char *mode) {
	FileHandle *fh;
	int i;
	
	if(isStringBlank(filename)) {
		return NULL;
	}
	if(mode == NULL) {
		return NULL;
	}

	fh = memCopy(&blankfilehandle, sizeof(FileHandle), "FileHandle", NULL);
	//todo:cycle through all paths/permutations
	if(filename[0] == '@') {
		filename++;
		for(i = filepaths->size - 1; i >= 0 && fh->file == NULL; i--) {
			if(stringCompare(filepaths->data[i], "-") != 0) {
				fh->filename = stringCopyMany(filepaths->data[i], filename, NULL);
				fh->file = fileOpenFile(fh->filename, mode);
				if(fh->file != NULL) {
					break;
				}
				stringFree(fh->filename);
				fh->filename = NULL;
			}
		}
	}
	if(fh->file == NULL) {
		fh->filename = stringCopy(filename);
		fh->file = fileOpenFile(fh->filename, mode);
	}
	if(fh->file == NULL) {
		//call fileClose() to clean up allocated structures
		fileClose(fh);
		return NULL;
	}
#if BUFSIZE >= 1024
	fh->buffer = memAlloc(BUFSIZ, "FileBuffer", NULL);
	setvbuf(fh->file, fh->buffer, _IOFBF, BUFSIZE);
#else
	fh->buffer = memAlloc(1024, "FileBuffer", NULL);
	setvbuf(fh->file, fh->buffer, _IOFBF, 1024);
#endif
	return fh;
}

/**
Closes a file and releases a FileHandle.
@param fh The FileHandle of an open file.
*/
void fileClose(FileHandle *fh) {
	if(fh == NULL) {
		return;
	}

	if(fh->file != NULL) {
//todo: handle cases where fclose() does return an error
		fclose(fh->file);
	}


	memFree(fh->buffer);
	stringFree(fh->filename);
	memFree(fh);
}

/**
Reads x bytes of data from a file.
@param fh Pointer to a FileHandle.
@param buffer Pointer to a buffer to receive data.
@param bytes Number of bytes to be read.
@return The number of bytes actually read, or -1 if there was an error.
*/
int fileRead(FileHandle *fh, void *buffer, size_t bytes) {
	if(fh == NULL) {
		return -1;
	}
	if(fh->file == NULL) {
		return -1;
	}
	if(buffer == NULL) {
		return -1;
	}
	return fread(buffer, 1, bytes, fh->file);
}

/**
Reads a character from a file.
@param fh Pointer to a FileHandle.
@return The value of the character read, or EOF if there was an error.
*/
int fileReadChar(FileHandle *fh) {
	if(fh == NULL) {
		return EOF;
	}
	if(fh->file == NULL) {
		return EOF;
	}
	return fgetc(fh->file);
}

/**
Writes x bytes of data to a file.
@param fh Pointer to a FileHandle.
@param buffer Pointer to a buffer of data to be written.
@param bytes Number of bytes to be written.
@return The number of bytes actually written, or -1 if there was an error.
*/
int fileWrite(FileHandle *fh, void *buffer, size_t bytes) {
	if(fh == NULL) {
		return -1;
	}
	if(fh->file == NULL) {
		return -1;
	}
	if(buffer == NULL) {
		return -1;
	}
	return fwrite(buffer, 1, bytes, fh->file);
}

/**
Writes a character to a file.
@param fh Pointer to a FileHandle.
@return -1 if there was an error.
*/
int fileWriteChar(FileHandle *fh, int c) {
	if(fh == NULL) {
		return -1;
	}
	if(fh->file == NULL) {
		return -1;
	}
	return fputc(c, fh->file);
}

/**
Writes a string to a file, releasing the string afterwards.
@param fh Pointer to a FileHandle.
@param buffer The string to be written, the string is released after being written.
@return number of bytes written or -1 if there was an error.
*/
int fileWriteString(FileHandle *fh, char *buffer) {
	int i;
	if(buffer == NULL) {
		return -1;
	}
	i = fileWrite(fh, buffer, stringLength(buffer));
	stringFree(buffer);
	return i;
}

/**
Writes a character to a file.
@param fh Pointer to a FileHandle.
@param buffer The string to be written.
@return number of bytes written or -1 if there was an error.
*/
int fileWriteStringKeep(FileHandle *fh, char *buffer) {
	if(buffer == NULL) {
		return -1;
	}
	return fileWrite(fh, buffer, stringLength(buffer));
}

/**
Flush the buffers for this file handle. Any outstand
disk writes will be written.
@param fh Pointer to the FileHandle to flush.
@return Zero if successful, non-zero if not.
*/
int fileFlush(FileHandle *fh) {
	if(fh == NULL) {
		return -1;
	}
	return fflush(fh->file);
}

/**
Check if a file is EOF.
@param fh Pointer to a FileHandle.
@return Non-zero if at EOF, zero if not.
*/
int fileEOF(FileHandle *fh) {
	if(fh == NULL) {
		return EOF;
	}
	if(fh->file == NULL) {
		return EOF;
	}
	return feof(fh->file);
}

/**
Check if a file has had an error.
@param fh Pointer to a FileHandle.
@return Non-zero if there is an error, zero if not.
*/
int fileError(FileHandle *fh) {
	if(fh == NULL) {
		return -1;
	}
	if(fh->file == NULL) {
		return -1;
	}
	return ferror(fh->file);
}

/**
Clears the last error flag.
@param fh Pointer to a FileHandle.
*/
void fileClearError(FileHandle *fh) {
	if(fh == NULL) {
		return;
	}
	if(fh->file == NULL) {
		return;
	}
	clearerr(fh->file);
}

/**
Adds a file path (used by the '--filepath' option).
*/
void fileAddPath(void *extra, char *str) {
	if(stringCompare(str, "-") == 0) {
		saDeleteMany(filepaths, 0, defaultpathtotal);
		defaultpathtotal = 0;
		//append "-" anyway so config files get saved properly
	}
	saAppendCopy(filepaths, str);
}

/**
Gets the full list of filepaths.
*/
void fileGetPath(void *extra, StringArrayHandle **sa) {
	if(sa == NULL) {
		return;
	}
	*sa = saCopy(filepaths);
	saDeleteMany(*sa, 0, defaultpathtotal);
}

/**
Clears the list of filepaths and fills it with the defaults.
*/
void fileClearPath(void *extra) {
	int i;
	char **paths;
	char *s;

	saDeleteMany(filepaths, 0, filepaths->size);
	defaultpathtotal = 0;

	if(extra == NULL) {
		return;
	}
	//paths = *((char ***)extra);
	paths = (char **)extra;
	if(paths == NULL) {
		return;
	}
	for(i = 0; paths[i] != NULL; i++) {
		//Append a path seperator character if missing
		if(paths[i][stringLength(paths[i]) - 1] == PATHSEPERATORCHAR) {
			s = stringCopy(paths[i]);
		} else {
			s = stringCopyMany(paths[i], PATHSEPERATOR, NULL);
		}
		saAppend(filepaths, s);
		defaultpathtotal++;
	}
}

//@}

