/***************************************************************************

    MAME source code to HTML converter

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in
          the documentation and/or other materials provided with the
          distribution.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

****************************************************************************

    Template file format:

    <html header>
    <!--PATH--> = insert path
    <!--CONTENT--> = insert content
    <html footer>

***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "osdcore.h"
#include "astring.h"
#include "corefile.h"


/***************************************************************************
    CONSTANTS & DEFINES
***************************************************************************/

#define PREPROC_CLASS		"preproc"
#define KEYWORD_CLASS		"keyword"
#define CUSTOM_CLASS		"custom"



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

enum file_type
{
	FILE_TYPE_INVALID,
	FILE_TYPE_C,
	FILE_TYPE_MAKE,
	FILE_TYPE_XML,
	FILE_TYPE_TEXT
};


struct ext_to_type
{
	const char *	extension;
	file_type		type;
};


struct token_entry
{
	const char *	token;
	const char *	color;
};


struct include_path
{
	include_path *	next;
	astring 		path;
};


struct list_entry
{
	list_entry *	next;
	astring 		name;
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

static include_path *incpaths;

static const ext_to_type extension_lookup[] =
{
	{ ".c", FILE_TYPE_C },
	{ ".h", FILE_TYPE_C },
	{ ".lst", FILE_TYPE_C },
	{ ".mak", FILE_TYPE_MAKE },
	{ "makefile", FILE_TYPE_MAKE },
	{ ".lay", FILE_TYPE_XML },
	{ ".xml", FILE_TYPE_XML },
	{ ".txt", FILE_TYPE_TEXT },
};


static const token_entry dummy_token_table[] =
{
	{ NULL, KEYWORD_CLASS }
};


static const token_entry c_token_table[] =
{
	{ "#define", PREPROC_CLASS },
	{ "#elif", PREPROC_CLASS },
	{ "#else", PREPROC_CLASS },
	{ "#endif", PREPROC_CLASS },
	{ "#error", PREPROC_CLASS },
	{ "#if", PREPROC_CLASS },
	{ "#ifdef", PREPROC_CLASS },
	{ "#ifndef", PREPROC_CLASS },
	{ "#include", PREPROC_CLASS },
	{ "#line", PREPROC_CLASS },
	{ "#pragma", PREPROC_CLASS },
	{ "#undef", PREPROC_CLASS },

	{ "auto", KEYWORD_CLASS },
	{ "bool", KEYWORD_CLASS },
	{ "break", KEYWORD_CLASS },
	{ "case", KEYWORD_CLASS },
	{ "catch", KEYWORD_CLASS },
	{ "char", KEYWORD_CLASS },
	{ "class", KEYWORD_CLASS },
	{ "const", KEYWORD_CLASS },
	{ "const_cast", KEYWORD_CLASS },
	{ "continue", KEYWORD_CLASS },
	{ "default", KEYWORD_CLASS },
	{ "delete", KEYWORD_CLASS },
	{ "do", KEYWORD_CLASS },
	{ "double", KEYWORD_CLASS },
	{ "dynamic_cast", KEYWORD_CLASS },
	{ "else", KEYWORD_CLASS },
	{ "enum", KEYWORD_CLASS },
	{ "explicit", KEYWORD_CLASS },
	{ "export", KEYWORD_CLASS },
	{ "extern", KEYWORD_CLASS },
	{ "false", KEYWORD_CLASS },
	{ "float", KEYWORD_CLASS },
	{ "for", KEYWORD_CLASS },
	{ "friend", KEYWORD_CLASS },
	{ "goto", KEYWORD_CLASS },
	{ "if", KEYWORD_CLASS },
	{ "inline", KEYWORD_CLASS },
	{ "int", KEYWORD_CLASS },
	{ "long", KEYWORD_CLASS },
	{ "mutable", KEYWORD_CLASS },
	{ "namespace", KEYWORD_CLASS },
	{ "new", KEYWORD_CLASS },
	{ "operator", KEYWORD_CLASS },
	{ "private", KEYWORD_CLASS },
	{ "protected", KEYWORD_CLASS },
	{ "public", KEYWORD_CLASS },
	{ "register", KEYWORD_CLASS },
	{ "reinterpret_cast", KEYWORD_CLASS },
	{ "return", KEYWORD_CLASS },
	{ "short", KEYWORD_CLASS },
	{ "signed", KEYWORD_CLASS },
	{ "sizeof", KEYWORD_CLASS },
	{ "static", KEYWORD_CLASS },
	{ "static_cast", KEYWORD_CLASS },
	{ "struct", KEYWORD_CLASS },
	{ "switch", KEYWORD_CLASS },
	{ "template", KEYWORD_CLASS },
	{ "this", KEYWORD_CLASS },
	{ "throw", KEYWORD_CLASS },
	{ "true", KEYWORD_CLASS },
	{ "try", KEYWORD_CLASS },
	{ "typedef", KEYWORD_CLASS },
	{ "typeid", KEYWORD_CLASS },
	{ "typename", KEYWORD_CLASS },
	{ "union", KEYWORD_CLASS },
	{ "unsigned", KEYWORD_CLASS },
	{ "virtual", KEYWORD_CLASS },
	{ "void", KEYWORD_CLASS },
	{ "volatile", KEYWORD_CLASS },
	{ "wchar_t", KEYWORD_CLASS },
	{ "while", KEYWORD_CLASS },

/*
    { "INLINE", CUSTOM_CLASS },
    { "INT8", CUSTOM_CLASS },
    { "UINT8", CUSTOM_CLASS },
    { "INT16", CUSTOM_CLASS },
    { "UINT16", CUSTOM_CLASS },
    { "INT32", CUSTOM_CLASS },
    { "UINT32", CUSTOM_CLASS },
    { "INT64", CUSTOM_CLASS },
    { "UINT64", CUSTOM_CLASS },
    { "ARRAY_LENGTH", CUSTOM_CLASS },
*/
	{ NULL, KEYWORD_CLASS }
};



/***************************************************************************
    PROTOTYPES
***************************************************************************/

// core output functions
static int recurse_dir(int srcrootlen, int dstrootlen, astring &srcdir, astring &dstdir, astring &tempheader, astring &tempfooter);
static int output_file(file_type type, int srcrootlen, int dstrootlen, astring &srcfile, astring &dstfile, bool link_to_file, astring &tempheader, astring &tempfooter);

// HTML helpers
static core_file *create_file_and_output_header(astring &filename, astring &templatefile, astring &path);
static void output_footer_and_close_file(core_file *file, astring &templatefile, astring &path);

// path helpers
static astring &normalized_subpath(astring &dest, astring &path, int start);
static void output_path_as_links(core_file *file, astring &path, bool end_is_directory, bool link_to_file);
static bool find_include_file(astring &srcincpath, int srcrootlen, int dstrootlen, astring &srcfile, astring &dstfile, astring &filename);



/***************************************************************************
    MAIN
***************************************************************************/

/*-------------------------------------------------
    main - main entry point
-------------------------------------------------*/

void usage(const char *argv0)
{
	fprintf(stderr, "Usage:\n%s <srcroot> <destroot> <template.html> [-Iincpath [-Iincpath [...]]]\n", argv0);
	exit(1);
}

int main(int argc, char *argv[])
{
	// loop over arguments
	include_path **incpathhead = &incpaths;
	astring srcdir, dstdir, tempfilename, tempheader, tempfooter;
	int unadorned = 0;
	for (int argnum = 1; argnum < argc; argnum++)
	{
		char *arg = argv[argnum];

		// include path?
		if (arg[0] == '-' && arg[1] == 'I')
		{
			*incpathhead = new include_path;
			if (*incpathhead != NULL)
			{
				(*incpathhead)->next = NULL;
				(*incpathhead)->path.cpy(&arg[2]).replacechr('/', PATH_SEPARATOR[0]);
				incpathhead = &(*incpathhead)->next;
			}
		}

		// other parameter
		else if (arg[0] != '-' && unadorned == 0)
		{
			srcdir.cpy(arg);
			unadorned++;
		}
		else if (arg[0] != '-' && unadorned == 1)
		{
			dstdir.cpy(arg);
			unadorned++;
		}
		else if (arg[0] != '-' && unadorned == 2)
		{
			tempfilename.cpy(arg);
			unadorned++;
		}
		else
			usage(argv[0]);
	}

	// make sure we got 3 parameters
	if (srcdir.len() == 0 || dstdir.len() == 0 || tempfilename.len() == 0)
		usage(argv[0]);

	// read the template file into an astring
	UINT32 bufsize;
	void *buffer;
	if (core_fload(tempfilename, &buffer, &bufsize) == FILERR_NONE)
	{
		tempheader.cpy((const char *)buffer, bufsize);
		osd_free(buffer);
	}

	// verify the template
	if (tempheader.len() == 0)
	{
		fprintf(stderr, "Unable to read template file\n");
		return 1;
	}
	int result = tempheader.find(0, "<!--CONTENT-->");
	if (result == -1)
	{
		fprintf(stderr, "Template is missing a <!--CONTENT--> marker\n");
		return 1;
	}
	tempfooter.cpy(tempheader).substr(result + 14, -1);
	tempheader.substr(0, result);

	// recurse over subdirectories
	return recurse_dir(srcdir.len(), dstdir.len(), srcdir, dstdir, tempheader, tempfooter);
}



/***************************************************************************
    CORE OUTPUT FUNCTIONS
***************************************************************************/

static int compare_list_entries(const void *p1, const void *p2)
{
	const list_entry *entry1 = *(const list_entry **)p1;
	const list_entry *entry2 = *(const list_entry **)p2;
	return entry1->name.cmp(entry2->name);
}


/*-------------------------------------------------
    recurse_dir - recurse through a directory
-------------------------------------------------*/

static int recurse_dir(int srcrootlen, int dstrootlen, astring &srcdir, astring &dstdir, astring &tempheader, astring &tempfooter)
{
	static const osd_dir_entry_type typelist[] = { ENTTYPE_DIR, ENTTYPE_FILE };

	// extract a normalized subpath
	astring srcdir_subpath;
	normalized_subpath(srcdir_subpath, srcdir, srcrootlen + 1);

	// create an index file
	astring indexname;
	indexname.printf("%s%c%s", dstdir.cstr(), PATH_SEPARATOR[0], "index.html");
	core_file *indexfile = create_file_and_output_header(indexname, tempheader, srcdir_subpath);

	// output the directory navigation
	core_fprintf(indexfile, "<h3>Viewing Directory: ");
	output_path_as_links(indexfile, srcdir_subpath, true, false);
	core_fprintf(indexfile, "</h3>");

	// iterate first over directories, then over files
	int result = 0;
	for (int entindex = 0; entindex < ARRAY_LENGTH(typelist) && result == 0; entindex++)
	{
		osd_dir_entry_type entry_type = typelist[entindex];

		// open the directory and iterate through it
		osd_directory *dir = osd_opendir(srcdir);
		if (dir == NULL)
		{
			result = 1;
			break;
		}

		// build up the list of files
		const osd_directory_entry *entry;
		int found = 0;
		list_entry *list = NULL;
		while ((entry = osd_readdir(dir)) != NULL)
			if (entry->type == entry_type && entry->name[0] != '.')
			{
				list_entry *lentry = new list_entry;
				lentry->name.cpy(entry->name);
				lentry->next = list;
				list = lentry;
				found++;
			}

		// close the directory
		osd_closedir(dir);

		// skip if nothing found
		if (found == 0)
			continue;

		// allocate memory for sorting
		list_entry **listarray = new list_entry *[found];
		found = 0;
		for (list_entry *curlist = list; curlist != NULL; curlist = curlist->next)
			listarray[found++] = curlist;

		// sort the list
		qsort(listarray, found, sizeof(listarray[0]), compare_list_entries);

		// rebuild the list
		list = NULL;
		while (--found >= 0)
		{
			listarray[found]->next = list;
			list = listarray[found];
		}
		delete[] listarray;

		// iterate through each file
		for (list_entry *curlist = list; curlist != NULL && result == 0; curlist = curlist->next)
		{
			// add a header
			if (curlist == list)
				core_fprintf(indexfile, "\t<h2>%s</h2>\n\t<ul>\n", (entry_type == ENTTYPE_DIR) ? "Directories" : "Files");

			// build the source filename
			astring srcfile;
			srcfile.printf("%s%c%s", srcdir.cstr(), PATH_SEPARATOR[0], curlist->name.cstr());

			// if we have a file, output it
			astring dstfile;
			if (entry_type == ENTTYPE_FILE)
			{
				// make sure we care, first
				file_type type = FILE_TYPE_INVALID;
				for (int extnum = 0; extnum < ARRAY_LENGTH(extension_lookup); extnum++)
					if (core_filename_ends_with(curlist->name, extension_lookup[extnum].extension))
					{
						type = extension_lookup[extnum].type;
						break;
					}

				// if we got a valid file, process it
				if (type != FILE_TYPE_INVALID)
				{
					dstfile.printf("%s%c%s.html", dstdir.cstr(), PATH_SEPARATOR[0], curlist->name.cstr());
					if (indexfile != NULL)
						core_fprintf(indexfile, "\t<li><a href=\"%s.html\">%s</a></li>\n", curlist->name.cstr(), curlist->name.cstr());
					result = output_file(type, srcrootlen, dstrootlen, srcfile, dstfile, srcdir == dstdir, tempheader, tempfooter);
				}
			}

			// if we have a directory, recurse
			else
			{
				dstfile.printf("%s%c%s", dstdir.cstr(), PATH_SEPARATOR[0], curlist->name.cstr());
				if (indexfile != NULL)
					core_fprintf(indexfile, "\t<li><a href=\"%s/index.html\">%s/</a></li>\n", curlist->name.cstr(), curlist->name.cstr());
				result = recurse_dir(srcrootlen, dstrootlen, srcfile, dstfile, tempheader, tempfooter);
			}
		}

		// close the list if we found some stuff
		if (list != NULL)
			core_fprintf(indexfile, "\t</ul>\n");

		// free all the allocated entries
		while (list != NULL)
		{
			list_entry *next = list->next;
			delete list;
			list = next;
		}
	}

	if (indexfile != NULL)
		output_footer_and_close_file(indexfile, tempfooter, srcdir_subpath);
	return result;
}


/*-------------------------------------------------
    output_file - output a file, converting to
    HTML
-------------------------------------------------*/

static int output_file(file_type type, int srcrootlen, int dstrootlen, astring &srcfile, astring &dstfile, bool link_to_file, astring &tempheader, astring &tempfooter)
{
	// extract a normalized subpath
	astring srcfile_subpath;
	normalized_subpath(srcfile_subpath, srcfile, srcrootlen + 1);

	fprintf(stderr, "Processing %s\n", srcfile_subpath.cstr());

	// set some defaults
	bool color_quotes = false;
	const char *comment_start = "";
	const char *comment_start_esc = "";
	const char *comment_end = "";
	const char *comment_end_esc = "";
	const char *comment_inline = "";
	const char *comment_inline_esc = "";
	const char *token_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_#";
	const token_entry *token_table = dummy_token_table;

	// based on the file type, set the comment info
	switch (type)
	{
		case FILE_TYPE_C:
			color_quotes = true;
			comment_start = comment_start_esc = "/*";
			comment_end = comment_end_esc = "*/";
			comment_inline = comment_inline_esc = "//";
			token_table = c_token_table;
			break;

		case FILE_TYPE_MAKE:
			color_quotes = true;
			comment_inline = comment_inline_esc = "#";
			break;

		case FILE_TYPE_XML:
			color_quotes = true;
			comment_start = "<!--";
			comment_start_esc = "&lt;!--";
			comment_end = "-->";
			comment_end_esc = "--&gt;";
			break;

		default:
		case FILE_TYPE_TEXT:
			break;
	}

	// make the token lookup table
	bool is_token[256];
	memset(is_token, 0, sizeof(is_token));
	for (int toknum = 0; token_chars[toknum] != 0; toknum++)
		is_token[(UINT8)token_chars[toknum]] = true;

	// open the source file
	core_file *src;
	if (core_fopen(srcfile, OPEN_FLAG_READ, &src) != FILERR_NONE)
	{
		fprintf(stderr, "Unable to read file '%s'\n", srcfile.cstr());
		return 1;
	}

	// open the output file
	core_file *dst = create_file_and_output_header(dstfile, tempheader, srcfile_subpath);
	if (dst == NULL)
	{
		fprintf(stderr, "Unable to write file '%s'\n", dstfile.cstr());
		core_fclose(src);
		return 1;
	}

	// output the directory navigation
	core_fprintf(dst, "<h3>Viewing File: ");
	output_path_as_links(dst, srcfile_subpath, false, link_to_file);
	core_fprintf(dst, "</h3>");

	// start with some tags
	core_fprintf(dst, "\t<pre class=\"source\">\n");

	// iterate over lines in the source file
	int linenum = 1;
	bool in_comment = false;
	char srcline[4096];
	while (core_fgets(srcline, ARRAY_LENGTH(srcline), src) != NULL)
	{
		// start with the line number
		astring dstline;
		dstline.catprintf("<span class=\"linenum\">%5d</span>&nbsp;&nbsp;", linenum++);

		// iterate over characters in the source line
		bool escape = false;
		bool in_quotes = false;
		bool in_inline_comment = false;
		bool last_token_was_include = false;
		bool last_was_token = false;
		bool quotes_are_linked = false;
		int curcol = 0;
		for (char *srcptr = srcline; *srcptr != 0; )
		{
			UINT8 ch = *srcptr++;

			// track whether or not we are within an extended (C-style) comment
			if (!in_quotes && !in_inline_comment)
			{
				if (!in_comment && ch == comment_start[0] && strncmp(srcptr - 1, comment_start, strlen(comment_start)) == 0)
				{
					dstline.catprintf("<span class=\"comment\">%s", comment_start_esc);
					curcol += strlen(comment_start);
					srcptr += strlen(comment_start) - 1;
					ch = 0;
					in_comment = true;
				}
				else if (in_comment && ch == comment_end[0] && strncmp(srcptr - 1, comment_end, strlen(comment_end)) == 0)
				{
					dstline.catprintf("%s</span>", comment_end_esc);
					curcol += strlen(comment_end);
					srcptr += strlen(comment_end) - 1;
					ch = 0;
					in_comment = false;
				}
			}

			// track whether or not we are within an inline (C++-style) comment
			if (!in_quotes && !in_comment && !in_inline_comment && ch == comment_inline[0] && strncmp(srcptr - 1, comment_inline, strlen(comment_inline)) == 0)
			{
				dstline.catprintf("<span class=\"comment\">%s", comment_inline_esc);
				curcol += strlen(comment_inline);
				srcptr += strlen(comment_inline) - 1;
				ch = 0;
				in_inline_comment = true;
			}

			// if this is the start of a new token, see if we want to color it
			if (!in_quotes && !in_comment && !in_inline_comment && !last_was_token && is_token[ch])
			{
				const token_entry *curtoken;
				char *temp = srcptr;
				int toklength;

				// find the end of the token
				while (*temp != 0 && is_token[(UINT8)*temp])
					temp++;
				toklength = temp - (srcptr - 1);

				// scan the token table
				last_token_was_include = false;
				for (curtoken = token_table; curtoken->token != NULL; curtoken++)
					if (strncmp(srcptr - 1, curtoken->token, toklength) == 0 && strlen(curtoken->token) == toklength)
					{
						dstline.catprintf("<span class=\"%s\">%s</span>", curtoken->color, curtoken->token);
						curcol += strlen(curtoken->token);
						srcptr += strlen(curtoken->token) - 1;
						ch = 0;

						// look for include tokens specially
						if (type == FILE_TYPE_C && strcmp(curtoken->token, "#include") == 0)
							last_token_was_include = true;
						break;
					}
			}
			last_was_token = is_token[ch];

			// if we hit a tab, expand it
			if (ch == 0x09)
			{
				// compute how many spaces
				int spaces = 4 - curcol % 4;
				while (spaces--)
				{
					dstline.cat(' ');
					curcol++;
				}
			}

			// otherwise, copy the source character
			else if (ch != 0x0a && ch != 0x0d && ch != 0)
			{
				// track opening quotes
				if (!in_comment && !in_inline_comment && !in_quotes && (ch == '"' || ch == '\''))
				{
					if (color_quotes)
						dstline.catprintf("<span class=\"string\">%c", ch);
					else
						dstline.cat(ch);
					in_quotes = ch;

					// handle includes
					if (last_token_was_include)
					{
						char *endquote = strchr(srcptr, ch);
						if (endquote != NULL)
						{
							astring filename(srcptr, endquote - srcptr);
							astring target;
							if (find_include_file(target, srcrootlen, dstrootlen, srcfile, dstfile, filename))
							{
								dstline.catprintf("<a href=\"%s\">", target.cstr());
								quotes_are_linked = true;
							}
						}
					}
				}

				// track closing quotes
				else if (!in_comment && !in_inline_comment && in_quotes && ch == in_quotes && !escape)
				{
					if (quotes_are_linked)
						dstline.catprintf("</a>");
					if (color_quotes)
						dstline.catprintf("%c</span>", ch);
					else
						dstline.cat(ch);
					in_quotes = 0;
					quotes_are_linked = false;
				}

				// else just output the current character
				else if (ch == '&')
					dstline.catprintf("&amp;");
				else if (ch == '<')
					dstline.catprintf("&lt;");
				else if (ch == '>')
					dstline.catprintf("&gt;");
				else
					dstline.cat(ch);
				curcol++;
			}

			// Update escape state
			if (in_quotes)
				escape = (ch == '\\' && type == FILE_TYPE_C) ? !escape : 0;
		}

		// finish inline comments
		if (in_inline_comment)
		{
			dstline.catprintf("</span>");
			in_inline_comment = false;
		}

		// append a break and move on
		dstline.catprintf("\n");
		core_fputs(dst, dstline);
	}

	// close tags
	core_fprintf(dst, "\t</pre>\n");

	// close the file
	output_footer_and_close_file(dst, tempfooter, srcfile_subpath);
	core_fclose(src);
	return 0;
}



/***************************************************************************
    HTML OUTPUT HELPERS
***************************************************************************/

/*-------------------------------------------------
    create_file_and_output_header - create a new
    HTML file with a standard header
-------------------------------------------------*/

static core_file *create_file_and_output_header(astring &filename, astring &templatefile, astring &path)
{
	// create the indexfile
	core_file *file;
	if (core_fopen(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS | OPEN_FLAG_NO_BOM, &file) != FILERR_NONE)
		return NULL;

	// print a header
	astring modified(templatefile);
	modified.replace(0, "<!--PATH-->", path.cstr());
	core_fwrite(file, modified.cstr(), modified.len());

	// return the file
	return file;
}


/*-------------------------------------------------
    output_footer_and_close_file - write a
    standard footer to an HTML file and close it
-------------------------------------------------*/

static void output_footer_and_close_file(core_file *file, astring &templatefile, astring &path)
{
	astring modified(templatefile);
	modified.replace(0, "<!--PATH-->", path.cstr());
	core_fwrite(file, modified.cstr(), modified.len());
	core_fclose(file);
}



/***************************************************************************
    HTML OUTPUT HELPERS
***************************************************************************/

/*-------------------------------------------------
    normalized_subpath - normalize a path to
    forward slashes and extract a subpath
-------------------------------------------------*/

static astring &normalized_subpath(astring &dest, astring &path, int start)
{
	return dest.cpysubstr(path, start, -1).replacechr(PATH_SEPARATOR[0], '/');
}


/*-------------------------------------------------
    output_path_as_links - output a path as a
    series of links
-------------------------------------------------*/

static void output_path_as_links(core_file *file, astring &path, bool end_is_directory, bool link_to_file)
{
	// first count how deep we are
	int srcdepth = 0;
	for (int slashindex = path.chr(0, '/'); slashindex != -1; slashindex = path.chr(slashindex + 1, '/'))
		srcdepth++;
	if (end_is_directory)
		srcdepth++;

	// output a link to the root
	core_fprintf(file, "<a href=\"");
	for (int depth = 0; depth < srcdepth; depth++)
		core_fprintf(file, "../");
	core_fprintf(file, "index.html\">&lt;root&gt;</a>/");

	// now output links to each path up the chain
	int curdepth = 0;
	int lastslash = 0;
	for (int slashindex = path.chr(lastslash, '/'); slashindex != -1; slashindex = path.chr(lastslash, '/'))
	{
		astring substr(path, lastslash, slashindex - lastslash);

		curdepth++;
		core_fprintf(file, "<a href=\"");
		for (int depth = curdepth; depth < srcdepth; depth++)
			core_fprintf(file, "../");
		core_fprintf(file, "index.html\">%s</a>/", substr.cstr());

		lastslash = slashindex + 1;
	}

	// and a final link to the current directory
	astring substr(path, lastslash, -1);
	if (end_is_directory)
		core_fprintf(file, "<a href=\"index.html\">%s</a>", substr.cstr());
	else if (link_to_file)
		core_fprintf(file, "<a href=\"%s\">%s</a>", substr.cstr(), substr.cstr());
	else
		core_fprintf(file, "<a href=\"%s.html\">%s</a>", substr.cstr(), substr.cstr());
}


/*-------------------------------------------------
    find_include_file - find an include file
-------------------------------------------------*/

static bool find_include_file(astring &srcincpath, int srcrootlen, int dstrootlen, astring &srcfile, astring &dstfile, astring &filename)
{
	// iterate over include paths and find the file
	for (include_path *curpath = incpaths; curpath != NULL; curpath = curpath->next)
	{
		// a '.' include path is specially treated
		if (curpath->path == ".")
			srcincpath.cpysubstr(srcfile, 0, srcfile.rchr(0, PATH_SEPARATOR[0]));
		else
			srcincpath.cpysubstr(srcfile, 0, srcrootlen + 1).cat(curpath->path);

		// append the filename piecemeal to account for directories
		int lastsepindex = 0;
		int sepindex;
		while ((sepindex = filename.chr(lastsepindex, '/')) != -1)
		{
			// handle .. by removing a chunk from the incpath
			astring pathpart(filename, lastsepindex, sepindex - lastsepindex);
			if (pathpart == "..")
			{
				sepindex = srcincpath.rchr(0, PATH_SEPARATOR[0]);
				if (sepindex != -1)
					srcincpath.substr(0, sepindex);
			}

			// otherwise, append a path separator and the pathpart
			else
				srcincpath.cat(PATH_SEPARATOR).cat(pathpart);

			// advance past the previous index
			lastsepindex = sepindex + 1;
		}

		// now append the filename
		srcincpath.cat(PATH_SEPARATOR).catsubstr(filename, lastsepindex, -1);

		// see if we can open it
		core_file *testfile;
		if (core_fopen(srcincpath, OPEN_FLAG_READ, &testfile) == FILERR_NONE)
		{
			// close the file
			core_fclose(testfile);

			// find the longest matching directory substring between the include and source file
			lastsepindex = 0;
			while ((sepindex = srcincpath.chr(lastsepindex, PATH_SEPARATOR[0])) != -1)
			{
				// get substrings up to the current directory
				astring tempfile(srcfile, 0, sepindex);
				astring tempinc(srcincpath, 0, sepindex);

				// if we don't match, stop
				if (tempfile != tempinc)
					break;
				lastsepindex = sepindex + 1;
			}

			// chop off the common parts of the paths
			astring tempfile(srcfile, lastsepindex, -1);
			srcincpath.substr(lastsepindex, -1).replacechr(PATH_SEPARATOR[0], '/');

			// for each directory left in the filename, we need to prepend a "../"
			while ((sepindex = tempfile.chr(0, PATH_SEPARATOR[0])) != -1)
			{
				tempfile.substr(sepindex + 1, -1);
				srcincpath.ins(0, "../");
			}
			srcincpath.cat(".html");

			// free the strings and return the include path
			return true;
		}
	}
	return false;
}
