/*
 * Copyright (c) 2004, 2005 Tama Communications Corporation
 *
 * This file is part of GNU GLOBAL.
 *
 * 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/>.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#ifdef STDC_HEADERS
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#else
#include <sys/file.h>
#endif

#include "global.h"
#include "common.h"
#include "htags.h"
#include "path2url.h"

/*
 * Tag definitions
 *
 * Htags generates HTML tag by default.
 */
const char *html_begin		= "<html>";
const char *html_end		= "</html>";
const char *html_head_begin	= "<head>";
const char *html_head_end	= "</head>";
const char *html_title_begin	= "<title>";
const char *html_title_end	= "</title>";
const char *body_begin		= "<body text='#191970' bgcolor='#f5f5dc' vlink='gray'>";
const char *body_end		= "</body>";
const char *title_begin		= "<h1><font color='#cc0000'>";
const char *title_end		= "</font></h1>";
const char *header_begin	= "<h2>";
const char *header_end		= "</h2>";
const char *cvslink_begin	= "<font size='-1'>";
const char *cvslink_end		= "</font>";
const char *caution_begin	= "<center>\n<blockquote>";
const char *caution_end		= "</blockquote>\n</center>";
const char *list_begin		= "<ol>";
const char *list_end		= "</ol>";
const char *item_begin		= "<li>";
const char *item_end		= "";
const char *flist_begin		= "<table cellpadding='2' width='100%'>";
const char *flist_end		= "</table>";
const char *fline_begin		= "<tr>";
const char *fline_end		= "</tr>";
const char *fitem_begin		= "<td nowrap>";
const char *fitem_end		= "</td>";
const char *define_list_begin	= "<dl>";
const char *define_list_end	= "</dl>";
const char *define_term_begin	= "<dt>";
const char *define_term_end	= "";
const char *define_desc_begin	= "<dd>";
const char *define_desc_end	= "";
const char *table_begin		= "<table>";
const char *table_end		= "</table>";
const char *comment_begin	= "<i><font color='green'>";
const char *comment_end		= "</font></i>";
const char *sharp_begin		= "<font color='darkred'>";
const char *sharp_end		= "</font>";
const char *brace_begin		= "<font color='red'>";
const char *brace_end		= "</font>";
const char *verbatim_begin	= "<pre>";
const char *verbatim_end	= "</pre>";
const char *reserved_begin	= "<b>";
const char *reserved_end	= "</b>";
const char *position_begin	= "<font color='gray'>";
const char *position_end	= "</font>";
const char *warned_line_begin	= "<span style='background-color:yellow'>";
const char *warned_line_end	= "</span>";
const char *error_begin		= "<h1><font color='#cc0000'>";
const char *error_end		= "</font></h1>";
const char *message_begin	= "<h3>";
const char *message_end		= "</h3>";
const char *string_begin	= "<u>";
const char *string_end		= "</u>";
const char *quote_great		= "&gt;";
const char *quote_little	= "&lt;";
const char *quote_amp		= "&amp;";
const char *quote_space		= "&nbsp;";
const char *hr			= "<hr>";
const char *br			= "<br>";
const char *empty_element	= "";
const char *noframes_begin	= "<noframes>";
const char *noframes_end	= "</noframes>";

/*
 * 1: Enforce XHTML1.0 strict or XHTML1.1.
 */
static int strict_xhtml = 0;

/*
 * print string and new line.
 *
 * This function is a replacement of fprintf(op, "%s\n", s) in htags.
 */
int
fputs_nl(const char *s, FILE *op)
{
	fputs(s, op);
	putc('\n', op);
	return 0;
}
/*
 * XHTML support.
 *
 * If the --xhtml option is specified, htags(1) generates XHTML output.
 * We define each style for the tags in 'style.css' in this directory.
 */
void
setup_xhtml(void)
{
	if (!strcmp(xhtml_version, "1.1") && !Fflag)
		strict_xhtml = 1;
	html_begin		= "<html xmlns='http://www.w3.org/1999/xhtml'>";
	html_end		= "</html>";
	html_head_begin		= "<head>";
	html_head_end		= "</head>";
	html_title_begin	= "<title>";
	html_title_end		= "</title>";
	body_begin		= "<body>";
	body_end		= "</body>";
	title_begin		= "<h1 class='title'>";
	title_end		= "</h1>";
	header_begin		= "<h2 class='header'>";
	header_end		= "</h2>";
	cvslink_begin		= "<span class='cvs'>";
	cvslink_end		= "</span>";
	caution_begin		= "<div class='caution'>";
	caution_end		= "</div>";
	list_begin		= "<ol>";
	list_end		= "</ol>";
	item_begin		= "<li>";
	item_end		= "</li>";
	flist_begin		= "<table class='flist'>";
	flist_end		= "</table>";
	fline_begin		= "<tr class='flist'>";
	fline_end		= "</tr>";
	fitem_begin		= "<td class='flist'>";
	fitem_end		= "</td>";
	define_list_begin	= "<dl>";
	define_list_end		= "</dl>";
	define_term_begin	= "<dt>";
	define_term_end		= "</dt>";
	define_desc_begin	= "<dd>";
	define_desc_end		= "</dd>";
	table_begin		= "<table>";
	table_end		= "</table>";
	comment_begin		= "<em class='comment'>";
	comment_end		= "</em>";
	sharp_begin		= "<em class='sharp'>";
	sharp_end		= "</em>";
	brace_begin		= "<em class='brace'>";
	brace_end		= "</em>";
	verbatim_begin		= "<pre>";
	verbatim_end		= "</pre>";
	reserved_begin		= "<strong class='reserved'>";
	reserved_end		= "</strong>";
	position_begin		= "<em class='position'>";
	position_end		= "</em>";
	warned_line_begin	= "<em class='warned'>";
	warned_line_end		= "</em>";
	error_begin		= "<h2 class='error'>";
	error_end		= "</h2>";
	message_begin		= "<h3 class='message'>";
	message_end		= "</h3>";
	string_begin		= "<em class='string'>";
	string_end		= "</em>";
	quote_great		= "&gt;";
	quote_little		= "&lt;";
	quote_amp		= "&amp;";
	quote_space		= "&nbsp;";
	hr			= "<hr />";
	br			= "<br />";
	empty_element		= " /";
	noframes_begin		= "<noframes>";
	noframes_end		= "</noframes>";
}
/*
 * Generate upper directory.
 */
const char *
upperdir(const char *dir)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	strbuf_sprintf(sb, "../%s", dir);
	return strbuf_value(sb);
}
/*
 * Load text from file with replacing @PARENT_DIR@ macro.
 * Macro @PARENT_DIR@ is replaced with the parent directory
 * of the 'HTML' directory.
 */
static const char *
sed(FILE *ip, int place)
{
	STATIC_STRBUF(sb);
	const char *parent_dir = (place == SUBDIR) ? "../.." : "..";
	int c, start_position = -1;

	strbuf_clear(sb);
	while ((c = fgetc(ip)) != EOF) {
		strbuf_putc(sb, c);
		if (c == '@') {
			int curpos = strbuf_getlen(sb);
			if (start_position == -1) {
				start_position = curpos - 1;
			} else {
				if (!strncmp("@PARENT_DIR@",
					strbuf_value(sb) + start_position,
					curpos - start_position))
				{
					strbuf_setlen(sb, start_position);
					strbuf_puts(sb, parent_dir);
					start_position = -1;
				} else {
					start_position = curpos - 1;
				}
			}
		} else if (!isalpha(c) && c != '_') {
			if (start_position != -1)
				start_position = -1;
		}
	}
	return strbuf_value(sb);
}
/*
 * Generate custom header.
 */
const char *
gen_insert_header(int place)
{ 
	static FILE *ip;

	if (ip != NULL) {
		rewind(ip);
	} else {
		ip = fopen(insert_header, "r");
		if (ip == NULL)
			die("cannot open include header file '%s'.", insert_header);
	}
	return sed(ip, place);
}
/*
 * Generate custom footer.
 */
const char *
gen_insert_footer(int place)
{
	static FILE *ip;

	if (ip != NULL) {
		rewind(ip);
	} else {
		ip = fopen(insert_footer, "r");
		if (ip == NULL)
			die("cannot open include footer file '%s'.", insert_footer);
	}
	return sed(ip, place);
}
/*
 * Generate beginning of generic page
 *
 *	i)	title	title of this page
 *	i)	place	SUBDIR: this page is in sub directory
 *			TOPDIR: this page is in the top directory
 *	i)	use_frameset
 *			use frameset document type or not
 */
static const char *
gen_page_generic_begin(const char *title, int place, int use_frameset)
{
	STATIC_STRBUF(sb);
	const char *dir = (place == SUBDIR) ? "../" : "";

	strbuf_clear(sb);
	if (enable_xhtml) {
		/*
		 * Since some browser cannot treat "<?xml...>", we don't
		 * write the declaration as long as XHTML1.1 is not required.
		 */
		if (strict_xhtml) {
			strbuf_puts_nl(sb, "<?xml version='1.0' encoding='ISO-8859-1'?>");
			strbuf_sprintf(sb, "<?xml-stylesheet type='text/css' href='%sstyle.css'?>\n", dir);
		}
		/*
		 * If the --frame option are specified then we take
		 * 'XHTML 1.0 Frameset' for index.html
		 * and 'XHTML 1.0 Transitional' for other files,
		 * else if the config variable 'xhtml_version' is
		 * set to '1.1' then we take 'XHTML 1.1',
		 * else 'XHTML 1.0 Transitional'.
		 */
		if (use_frameset)
			strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Frameset//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd'>");
		else if (!Fflag && strict_xhtml)
			strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>");
		else
			strbuf_puts_nl(sb, "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>");
	}
	strbuf_puts_nl(sb, html_begin);
	strbuf_puts_nl(sb, html_head_begin);
	strbuf_puts(sb, html_title_begin);
	strbuf_puts(sb, title);
	strbuf_puts_nl(sb, html_title_end);
	strbuf_sprintf(sb, "<meta name='robots' content='noindex,nofollow'%s>\n", empty_element);
	strbuf_sprintf(sb, "<meta name='generator' content='GLOBAL-%s'%s>\n", get_version(), empty_element);
    /* Stingll adds */
	strbuf_sprintf(sb, "<meta http-equiv='Content-Style-Type' content='text/css'%s charset='UTF-8'>\n", empty_element);
	if (enable_xhtml) {
        /* Stingll adds */
		/*strbuf_sprintf(sb, "<meta http-equiv='Content-Style-Type' content='text/css'%s>\n", empty_element);*/
		strbuf_sprintf(sb, "<meta http-equiv='Content-Style-Type' content='text/css'%s charset='UTF-8'>\n", empty_element);
		strbuf_sprintf(sb, "<link rel='stylesheet' type='text/css' href='%sstyle.css'%s>\n", dir, empty_element);
	}
	strbuf_puts(sb, html_head_end);
	return strbuf_value(sb);
}
/*
 * Generate beginning of normal page
 *
 *	i)	title	title of this page
 *	i)	place	SUBDIR: this page is in sub directory
 *			TOPDIR: this page is in the top directory
 */
const char *
gen_page_begin(const char *title, int place)
{
	return gen_page_generic_begin(title, place, 0);
}
/*
 * Generate beginning of frameset page
 *
 *	i)	title	title of this page
 */
const char *
gen_page_frameset_begin(const char *title)
{
	return gen_page_generic_begin(title, TOPDIR, 1);
}
/*
 * Generate end of page
 */
const char *
gen_page_end(void)
{
	return html_end;
}

/*
 * Generate image tag.
 *
 *	i)	where	Where is the icon directory?
 *			CURRENT: current directory
 *			PARENT: parent directory
 *	i)	file	icon file without suffix.
 *	i)	alt	alt string
 */
const char *
gen_image(int where, const char *file, const char *alt)
{
	STATIC_STRBUF(sb);
	const char *dir = (where == PARENT) ? "../icons" : "icons";

	strbuf_clear(sb);
	if (enable_xhtml)
		strbuf_sprintf(sb, "<img class='icon' src='%s/%s.%s' alt='[%s]'%s>",
			dir, file, icon_suffix, alt, empty_element);
	else
		strbuf_sprintf(sb, "<img src='%s/%s.%s' alt='[%s]' %s%s>",
			dir, file, icon_suffix, alt, icon_spec, empty_element);
	return strbuf_value(sb);
}
/*
 * Generate name tag.
 */
const char *
gen_name_number(int number)
{
	static char buf[32];

	snprintf(buf, sizeof(buf), "L%d", number);
	return gen_name_string(buf);
}
/*
 * Generate name tag.
 */
const char *
gen_name_string(const char *name)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	if (enable_xhtml) {
		/*
		 * Since some browser cannot understand "<a id='xxx' />",
		 * we put both of 'id=' and 'name=' as long as XHTML1.1
		 * is not required. XHTML1.1 prohibit 'name='.
		 */
		if (strict_xhtml)
			strbuf_sprintf(sb, "<a id='%s' />", name);
		else
			strbuf_sprintf(sb, "<a id='%s' name='%s' />", name, name);
	} else {
		strbuf_sprintf(sb, "<a name='%s'>", name);
	}
	return strbuf_value(sb);
}
/*
 * Generate anchor begin tag.
 * (complete format)
 *
 *	i)	dir	directory
 *	i)	file	file
 *	i)	suffix	suffix
 *	i)	key	key
 *	i)	title	title='xxx'
 *	i)	target	target='xxx'
 *	r)		generated anchor tag
 */
const char *
gen_href_begin_with_title_target(const char *dir, const char *file, const char *suffix, const char *key, const char *title, const char *target)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	/*
	 * Construct URL.
	 * href='dir/file.suffix#key'
	 */
	strbuf_puts(sb, "<a href='");
	if (file) {
		if (dir) {
			strbuf_puts(sb, dir);
			strbuf_putc(sb, '/');
		}
		strbuf_puts(sb, file);
		if (suffix) {
			strbuf_putc(sb, '.');
			strbuf_puts(sb, suffix);
		}
	}
	if (key) {
		strbuf_putc(sb, '#');
		/*
		 * If the key starts with a digit, it assumed line number.
		 * XHTML 1.1 profibits number as an anchor.
		 */
		if (isdigit(*key))
			strbuf_putc(sb, 'L');
		strbuf_puts(sb, key);
	}
	strbuf_putc(sb, '\'');
	if (target)
		strbuf_sprintf(sb, " target='%s'", target);
	if (title)
		strbuf_sprintf(sb, " title='%s'", title);
	strbuf_putc(sb, '>');
	return strbuf_value(sb);
}
/*
 * Generate simple anchor begin tag.
 */
const char *
gen_href_begin_simple(const char *file)
{
	return gen_href_begin_with_title_target(NULL, file, NULL, NULL, NULL, NULL);
}
/*
 * Generate anchor begin tag without title and target.
 */
const char *
gen_href_begin(const char *dir, const char *file, const char *suffix, const char *key)
{
	return gen_href_begin_with_title_target(dir, file, suffix, key, NULL, NULL);
}
/*
 * Generate anchor begin tag without target.
 */
const char *
gen_href_begin_with_title(const char *dir, const char *file, const char *suffix, const char *key, const char *title)
{
	return gen_href_begin_with_title_target(dir, file, suffix, key, title, NULL);
}
/*
 * Generate anchor end tag.
 */
const char *
gen_href_end(void)
{
	return "</a>";
}
/*
 * Generate list begin tag.
 */
const char *
gen_list_begin(void)
{
	STATIC_STRBUF(sb);

	if (strbuf_empty(sb)) {
		strbuf_clear(sb);
		if (table_list) {
			if (enable_xhtml) {
				strbuf_sprintf(sb, "%s\n%s%s%s%s",
					table_begin, 
					"<tr><th class='tag'>tag</th>",
					"<th class='line'>line</th>",
					"<th class='file'>file</th>",
					"<th class='code'>source code</th></tr>");
			} else {
				strbuf_sprintf(sb, "%s\n%s%s%s%s",
					table_begin, 
					"<tr><th nowrap align='left'>tag</th>",
					"<th nowrap align='right'>line</th>",
					"<th nowrap align='center'>file</th>",
					"<th nowrap align='left'>source code</th></tr>");
			}
		} else {
			strbuf_puts(sb, verbatim_begin);
		}
	}
	return strbuf_value(sb);
}
/*
 * Generate list body.
 *
 * s must be choped.
 */
const char *
gen_list_body(const char *srcdir, const char *ctags_x)		/* virtually const */
{
	STATIC_STRBUF(sb);
	const char *p, *filename, *fid;
	SPLIT ptable;

	strbuf_clear(sb);
	if (split((char *)ctags_x, 4, &ptable) < 4) {
		recover(&ptable);
		die("too small number of parts in list_body().\n'%s'", ctags_x);
	}
	filename = ptable.part[PART_PATH].start + 2;	/* remove './' */
	fid = path2fid(filename);
	if (table_list) {
		if (enable_xhtml) {
			strbuf_puts(sb, "<tr><td class='tag'>");
			strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
			strbuf_puts(sb, ptable.part[PART_TAG].start);
			strbuf_puts(sb, gen_href_end());
			strbuf_sprintf(sb, "</td><td class='line'>%s</td><td class='file'>%s</td><td class='code'>",
				ptable.part[PART_LNO].start, filename);
		} else {
			strbuf_puts(sb, "<tr><td nowrap>");
			strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
			strbuf_puts(sb, ptable.part[PART_TAG].start);
			strbuf_puts(sb, gen_href_end());
			strbuf_sprintf(sb, "</td><td nowrap align='right'>%s</td><td nowrap align='left'>%s</td><td nowrap>",
				ptable.part[PART_LNO].start, filename);
		}

		for (p = ptable.part[PART_LINE].start; *p; p++) {
			unsigned char c = *p;

			if (c == '&')
				strbuf_puts(sb, quote_amp);
			else if (c == '<')
				strbuf_puts(sb, quote_little);
			else if (c == '>')
				strbuf_puts(sb, quote_great);
			else if (c == ' ')
				strbuf_puts(sb, quote_space);
			else if (c == '\t') {
				strbuf_puts(sb, quote_space);
				strbuf_puts(sb, quote_space);
			} else
				strbuf_putc(sb, c);
		}
		strbuf_puts(sb, "</td></tr>");
		recover(&ptable);
	} else {
		int done = 0;

		strbuf_puts(sb, gen_href_begin(srcdir, fid, HTML, ptable.part[PART_LNO].start));
		strbuf_puts(sb, ptable.part[PART_TAG].start);
		strbuf_puts(sb, gen_href_end());
		p = ctags_x + strlen(ptable.part[PART_TAG].start);
		recover(&ptable);

		for (; *p; p++) {
			unsigned char c = *p;

			/* ignore "./" in path name */
			if (!done && c == '.' && *(p + 1) == '/') {
				p++;
				done = 1;
			} else if (c == '&')
				strbuf_puts(sb, quote_amp);
			else if (c == '<')
				strbuf_puts(sb, quote_little);
			else if (c == '>')
				strbuf_puts(sb, quote_great);
			else
				strbuf_putc(sb, c);
		}
	}
	return strbuf_value(sb);
}
/*
 * Generate list end tag.
 */
const char *
gen_list_end(void)
{
	return table_list ? table_end : verbatim_end;
}
/*
 * Generate div begin tag.
 *
 *	i)	align	right,left,center
 */
const char *
gen_div_begin(const char *align)
{
	STATIC_STRBUF(sb);

	if (strbuf_empty(sb)) {
		strbuf_clear(sb);
		if (align) {
			/*
			 * In XHTML, alignment is defined in the file 'style.css'.
			 */
			if (enable_xhtml)
				strbuf_sprintf(sb, "<div class='%s'>", align);
			else
				strbuf_sprintf(sb, "<div align='%s'>", align);
		} else {
			strbuf_puts(sb, "<div>");
		}
	}
	return strbuf_value(sb);
}
/*
 * Generate div end tag.
 */
const char *
gen_div_end(void)
{
	return "</div>";
}
/*
 * Generate beginning of form
 *
 *	i)	target	target
 */
const char *
gen_form_begin(const char *target)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	strbuf_sprintf(sb, "<form method='get' action='%s'", action);
	if (target)
		strbuf_sprintf(sb, " target='%s'", target);
	strbuf_puts(sb, ">");
	return strbuf_value(sb);
}
/*
 * Generate end of form
 */
const char *
gen_form_end(void)
{
	return "</form>";
}
/*
 * Generate input tag
 */
const char *
gen_input(const char *name, const char *value, const char *type)
{
	return gen_input_with_title_checked(name, value, type, 0, NULL);
}
/*
 * Generate input radiobox tag
 */
const char *
gen_input_radio(const char *name, const char *value, int checked, const char *title)
{
	return gen_input_with_title_checked(name, value, "radio", checked, title);
}
/*
 * Generate input checkbox tag
 */
const char *
gen_input_checkbox(const char *name, const char *value, const char *title)
{
	return gen_input_with_title_checked(name, value, "checkbox", 0, title);
}
/*
 * Generate input radio tag
 */
const char *
gen_input_with_title_checked(const char *name, const char *value, const char *type, int checked, const char *title)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	strbuf_puts(sb, "<input");
	if (type)
		strbuf_sprintf(sb, " type='%s'", type);
	if (name)
		strbuf_sprintf(sb, " name='%s'", name);
	if (value)
		strbuf_sprintf(sb, " value='%s'", value);
	if (checked) {
		if (enable_xhtml)
			strbuf_puts(sb, " checked='checked'");
		else
			strbuf_puts(sb, " checked");
	}
	if (title)
		strbuf_sprintf(sb, " title='%s'", title);
	strbuf_sprintf(sb, "%s>", empty_element);
	return strbuf_value(sb);
}
/*
 * Generate beginning of frameset
 *
 *	i)	target	target
 */
const char *
gen_frameset_begin(const char *contents)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	strbuf_sprintf(sb, "<frameset %s>", contents);
	return strbuf_value(sb);
}
/*
 * Generate end of frameset
 */
const char *
gen_frameset_end(void)
{
	return "</frameset>";
}
/*
 * Generate beginning of frame
 *
 *	i)	target	target
 */
const char *
gen_frame(const char *name, const char *src)
{
	STATIC_STRBUF(sb);

	strbuf_clear(sb);
	strbuf_sprintf(sb, "<frame name='%s' id='%s' src='%s'%s>", name, name, src, empty_element);
	return strbuf_value(sb);
}
