/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/agt/agility.h"

namespace Glk {
namespace AGT {

#ifdef force16
#undef int
#define int short
#endif


/*----------------------------------------------------------------------*/
/*  Filetype Data                                                       */
/*----------------------------------------------------------------------*/
const char *extname[] = {
	"",
	DA1, DA2, DA3, DA4, DA5, DA6, DSS,
	pHNT, pOPT, pTTL,
	pSAV, pSCR, pLOG,
	pAGX, pINS, pVOC, pCFG,
	pAGT, pDAT, pMSG, pCMD, pSTD, AGTpSTD
};


#ifdef PATH_SEP
static const char *path_sep = PATH_SEP;
#else
static const char *path_sep = nullptr;
#endif

/* This returns the options to use when opening the given file type */
/* rw is true if we are writing, false if we are reading. */
const char *filetype_info(filetype ft, rbool rw) {
	if (ft < fTTL) return "rb";
	if (ft == fAGX) return rw ? "wb" : "rb";
	if (ft == fSAV) return (rw ? "wb" : "rb");
	if (ft == fTTL || ft == fINS || ft == fVOC) return  "rb";
#ifdef OPEN_AS_TEXT
	if (ft >= fCFG) return (open_as_binary ? "rb" : "r");
#else
	if (ft >= fCFG) return "rb";
#endif
	if (ft == fSCR) {
		if (rw)
			return (BATCH_MODE || make_test) ? "w" : "a";
		else return "r";
	}
	if (ft == fLOG) return rw ? "w" : "r";
	fatal("INTERNAL ERROR: Invalid filetype.");
	return nullptr;
}


/* Returns true if ft is a possible extension in general context ft_base */
static rbool compat_ext(filetype ft, filetype ft_base) {
	if (ft_base == fNONE || ft_base == fDA1 || ft_base == fAGX) { /* Game file */
		return (ft >= fDA1 && ft <= fDSS)
		       || ft == fOPT || ft == fTTL
		       || (ft >= fAGX && ft <= fCFG);
	}

	if (ft_base == fSAV || ft_base == fSCR || ft_base == fLOG)
		return (ft == ft_base);

	if (ft_base == fAGT) { /* Source code */
		return (ft >= fAGT && ft <= fCMD)
		       || ft == fTTL || ft == fCFG;
	}

	fatal("INTERNAL ERROR: Invalid file class.");
	return 0;
}



/*----------------------------------------------------------------------*/
/*  Misc. utilities                                                     */
/*----------------------------------------------------------------------*/

char *assemble_filename(const char *path, const char *root,
						const char *ext) {
	int len1, len2, len3;
	char *name;

	len1 = len2 = len3 = 0;
	if (path != nullptr) len1 = strlen(path);
	if (root != nullptr) len2 = strlen(root);
	if (ext != nullptr) len3 = strlen(ext);
	name = (char *)rmalloc(len1 + len2 + len3 + 1);
	if (path != nullptr) memcpy(name, path, len1);
#ifdef PREFIX_EXT
	if (ext != NULL) memcpy(name + len1, ext, len3);
	if (root != NULL) memcpy(name + len1 + len3, root, len2);
#else
	if (root != nullptr) memcpy(name + len1, root, len2);
	if (ext != nullptr) memcpy(name + len1 + len2, ext, len3);
#endif
	name[len1 + len2 + len3] = 0;
	return name;
}

#ifdef PATH_SEP
/* This works for binary files; we don't care about non-binary
   files since this only used to search for game files. */
static rbool file_exist(const char *fname) {
	return Common::File::exists(fname);
}
#endif

/* This checks to see if c matches any of the characters in matchset */
static rbool smatch(char c, const char *matchset) {
	for (; *matchset != 0; matchset++)
		if (*matchset == c) return 1;
	return 0;
}



/*----------------------------------------------------------------------*/
/*  Taking Apart the Filename                                           */
/*----------------------------------------------------------------------*/

static int find_path_sep(const char *name) {
	int i;

	if (path_sep == nullptr)
		return -1;
	for (i = strlen(name) - 1; i >= 0; i--)
		if (smatch(name[i], path_sep)) break;
	return i;
}


/* Checks to see if the filename (which must be path-free)
   has an extension. Returns the length of the extensions
   and writes the extension type in pft */
static int search_for_ext(const char *name, filetype base_ft,
						  filetype *pft) {
	filetype t;
	int xlen, len;

	*pft = fNONE;
	len = strlen(name);
	if (len == 0) return 0;
	for (t = (filetype)(fNONE + 1); t <= fSTD; t = (filetype)((int)t + 1))
		if (compat_ext(t, base_ft)) {
			xlen = strlen(extname[t]);
			if (xlen == 0 || xlen > len) continue;
#ifdef PREFIX_EXT
			if (strncasecmp(name, extname[t], xlen) == 0)
#else
			if (fnamecmp(name + len - xlen, extname[t]) == 0)
#endif
			{
				*pft = t;
				return xlen;
			}
		}

	return 0;
}


/* Extract root filename or extension  from
   pathless name, given that the extension is of length extlen. */
/* If isext is true, extract the extension. If isext is false,
   then extrac the root. */
static char *extract_piece(const char *name, int extlen, rbool isext) {
	char *root;
	int len, xlen;
	rbool first;  /* If true, extract from beginning; if false, extract
		   from end */

	len = strlen(name) - extlen;
	xlen = extlen;
	if (isext) {
		int tmp;
		tmp = len;
		len = xlen;
		xlen = tmp;
	}
	if (len == 0) return nullptr;
	root = (char *)rmalloc((len + 1) * sizeof(char));
#ifdef PREFIX_EXT
	first = isext ? 1 : 0;
#else
	first = isext ? 0 : 1;
#endif
	if (first) {
		memcpy(root, name, len);
		root[len] = 0;
	} else {
		memcpy(root, name + xlen, len);
		root[len] = 0;
	}
	return root;
}


/* This returns true if "path" is absolute, false otherwise.
   This is _very_ platform dependent. */
static rbool absolute_path(char *path) {
#ifdef pathtest
	return pathtest(path);
#else
	return 1;
#endif
}

/*----------------------------------------------------------------------*/
/*  Basic routines for dealing with file contexts                       */
/*----------------------------------------------------------------------*/

#define FC(x) ((file_context_rec*)(x))

/* formal_name is used to get filenames for diagnostic messages, etc. */
char *formal_name(fc_type fc, filetype ft) {
	if (FC(fc)->special) return FC(fc)->gamename;
	if (ft == fNONE)
		return rstrdup(FC(fc)->shortname);
	if (ft == fAGT_STD)
		return rstrdup(AGTpSTD);
	return assemble_filename("", FC(fc)->shortname, extname[ft]);
}

#ifdef PATH_SEP
static rbool test_file(const char *path, const char *root, const char *ext) {
	char *name;
	rbool tmp;

	name = assemble_filename(path, root, ext);
	tmp = file_exist(name);
	rfree(name);
	return tmp;
}

/* This does a path search for the game files. */
static void fix_path(file_context_rec *fc) {
	char **ppath;


	if (gamepath == NULL) return;
	for (ppath = gamepath; *ppath != NULL; ppath++)
		if (test_file(*ppath, fc->shortname, fc->ext)
		        || test_file(*ppath, fc->shortname, pAGX)
		        || test_file(*ppath, fc->shortname, DA1)) {
			fc->path = rstrdup(*ppath);
			return;
		}
}
#endif


/* This creates a new file context based on gamename. */
/* ft indicates the rough use it will be put towards:
	 ft=fNONE indicates it's the first pass read, before PATH has been
		 read in, and so the fc shouldn't be filled out until
	 fix_file_context() is called.
	 ft=pDA1 indicates that name refers to the game files.
	 ft=pAGX indicates the name of the AGX file to be written to.
	 ft=pSAV,pLOG,pSCR all indicate that name corresponds to the
		 related type of file. */
fc_type init_file_context(const char *name, filetype ft) {
	file_context_rec *fc;
	int p, x;  /* Path and extension markers */

	fc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
	fc->special = 0;

	fc->gamename = rstrdup(name);

	p = find_path_sep(fc->gamename);
	if (p < 0)
		fc->path = nullptr;
	else {
		fc->path = (char *)rmalloc((p + 2) * sizeof(char));
		memcpy(fc->path, fc->gamename, p + 1);
		fc->path[p + 1] = '\0';
	}
	x = search_for_ext(fc->gamename + p + 1, ft, &fc->ft);
	fc->shortname = extract_piece(fc->gamename + p + 1, x, 0);
	fc->ext = extract_piece(fc->gamename + p + 1, x, 1);

#ifdef PATH_SEP
	if (fc->path == NULL && ft == fDA1)
		fix_path(fc);
#endif
	return fc;
}


void fix_file_context(fc_type fc, filetype ft) {
#ifdef PATH_SEP
	if (FC(fc)->path == NULL && ft == fDA1)
		fix_path(FC(fc));
#endif
}


/* This creates new file contexts from old. */
/* This is used to create save/log/script filenames from the game name,
   and to create include files in the same directory as the source file. */
fc_type convert_file_context(fc_type fc, filetype ft, const char *name) {
	file_context_rec *nfc;
	rbool local_ftype; /* Indicates file should be in working directory,
			not game directory. */

	local_ftype = (ft == fSAV || ft == fSCR || ft == fLOG);
	if (BATCH_MODE || make_test) local_ftype = 0;

	if (name == nullptr) {
		nfc = (file_context_rec *)rmalloc(sizeof(file_context_rec));
		nfc->gamename = nullptr;
		nfc->path = nullptr;
		nfc->shortname = rstrdup(fc->shortname);
		nfc->ext = nullptr;
		nfc->ft = fNONE;
		nfc->special = 0;
	} else {
		nfc = init_file_context(name, ft);
	}

	/* If path already defined, then combine paths. */
	if (!local_ftype && nfc->path != nullptr && !absolute_path(nfc->path)) {
		char *newpath;
		newpath = nfc->path;
		newpath = assemble_filename(fc->path, nfc->path, "");
		rfree(nfc->path);
		nfc->path = newpath;
	}

	/* scripts, save-games and logs should go in  the working directory,
	   not the game directory, so leave nfc->path equal to NULL for them. */
	if (!local_ftype && nfc->path == nullptr)
		nfc->path = rstrdup(fc->path); /* Put files in game directory */
	return nfc;
}

void release_file_context(fc_type *pfc) {
	file_context_rec *fc;
	fc = FC(*pfc);
	rfree(fc->gamename);
	rfree(fc->path);
	rfree(fc->shortname);
	rfree(fc->ext);
	rfree(fc);
}


/*----------------------------------------------------------------------*/
/*   Routines for Finding Files                                         */
/*----------------------------------------------------------------------*/

static genfile try_open_file(const char *path, const char *root,
							 const char *ext, const char *how,
							 rbool nofix) {
	char *name = assemble_filename(path, root, ext);
	genfile f = fopen(name, how);
	rfree(name);

	return f;
}


static genfile findread(file_context_rec *fc, filetype ft) {
	genfile f;

	f = nullptr;

	if (ft == fAGT_STD) {
		f = try_open_file(fc->path, AGTpSTD, "", filetype_info(ft, 0), 0);
		return f;
	}
	if (ft == fAGX || ft == fNONE) /* Try opening w/o added extension */
		f = try_open_file(fc->path, fc->shortname, fc->ext, filetype_info(ft, 0), 0);
	if (f == nullptr)
		f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 0);
	return f;
}


/*----------------------------------------------------------------------*/
/*  File IO Routines                                                    */
/*----------------------------------------------------------------------*/

genfile readopen(fc_type fc, filetype ft, const char **errstr) {
	genfile f;

	*errstr = nullptr;
	f = findread(fc, ft);
	if (f == nullptr) {
		*errstr = "Cannot open file";
	}
	return f;
}

rbool fileexist(fc_type fc, filetype ft) {
	genfile f;

	if (fc->special) return 0;
	f = try_open_file(fc->path, fc->shortname, extname[ft], filetype_info(ft, 0), 1);
	if (f != nullptr) { /* File already exists */
		readclose(f);
		return 1;
	}
	return 0;
}


genfile writeopen(fc_type fc, filetype ft,
				  file_id_type *pfileid, const char **errstr) {
	char *name;
	genfile f;

	*errstr = nullptr;
	name = nullptr;

	{
		name = assemble_filename(FC(fc)->path, FC(fc)->shortname, extname[ft]);
		f = fopen(name, filetype_info(ft, 1));
	}
	if (f == nullptr) {
		*errstr = "Cannot open file";
	}
	if (pfileid == nullptr)
		rfree(name);
	else
		*pfileid = name;
	return f;
}


rbool filevalid(genfile f, filetype ft) {
	return (f != nullptr);
}



void binseek(genfile f, long offset) {
	Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
	assert(rs);

	rs->seek(offset);
}


/* This returns the number of bytes read, or 0 if there was an error. */
long varread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
	long num;

	*errstr = nullptr;
	assert(f != nullptr);

	num = fread(buff, recsize, recnum, f);
	if (num != recnum)
		*errstr = "varread";
	num = num * recsize;

	return num;
}

rbool binread(genfile f, void *buff, long recsize, long recnum, const char **errstr) {
	long num;

	num = varread(f, buff, recsize, recnum, errstr);
	if (num < recsize * recnum && *errstr == nullptr)
		*errstr = rstrdup("Unexpected end of file.");
	return (*errstr == nullptr);
}


rbool binwrite(genfile f, void *buff, long recsize, long recnum, rbool ferr) {
	assert(f != nullptr);

	if (fwrite(buff, recsize, recnum, f) != (size_t)recnum) {
		if (ferr) fatal("binwrite");
		return 0;
	}
	return 1;
}

void readclose(genfile f) {
	assert(f != nullptr);

	fclose(f);
}

void writeclose(genfile f, file_id_type fileid) {
	assert(f != nullptr);
	rfree(fileid);

	fclose(f);
}

long binsize(genfile f)
/* Returns the size of a binary file */
{
	long pos, leng;

	assert(f != nullptr);

	pos = ftell(f);
	fseek(f, 0, SEEK_END);
	leng = ftell(f);
	fseek(f, pos, SEEK_SET);

	return leng;
}

rbool textrewind(genfile f) {
	Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(f);
	assert(rs);
	rs->seek(0);
	return 0;
}


genfile badfile(filetype ft) {
	return nullptr;
}

} // End of namespace AGT
} // End of namespace Glk
