/*
 * Copyright (c) 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2006
 *	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>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
/*
 * Don't remove the following code which seems meaningless.
 * Since WIN32 has another SLIST_ENTRY, we removed the definition
 * so as not to cause the conflict.
 */
#ifdef SLIST_ENTRY
#undef SLIST_ENTRY
#endif
#endif

#include "global.h"
#include "regex.h"
#include "const.h"

static void usage(void);
static void help(void);

const char *gozillarc = ".gozillarc";
#ifdef __DJGPP__
const char *dos_gozillarc = "_gozillarc";
#endif
STRHASH *sh;

static void load_alias(void);
static const char *alias(const char *);
int main(int, char **);
void getdefinitionURL(const char *, STRBUF *);
void getURL(const char *, STRBUF *);
int isprotocol(const char *);
int convertpath(const char *, const char *, const char *, STRBUF *);
void makefileurl(const char *, int, STRBUF *);
void show_page_by_url(const char *, const char *);
#ifndef isblank
#define isblank(c)	((c) == ' ' || (c) == '\t')
#endif

char cwd[MAXPATHLEN+1];
char root[MAXPATHLEN+1];
char dbpath[MAXPATHLEN+1];

int bflag;
int pflag;
int qflag;
int Cflag;
int vflag;
int show_version;
int linenumber = 0;
int debug;

static void
usage(void)
{
	if (!qflag)
		fputs(usage_const, stderr);
	exit(2);
}
static void
help(void)
{
	fputs(usage_const, stdout);
	fputs(help_const, stdout);
	exit(0);
}

/*
 * load_alias: load alias value.
 *
 * [$HOME/.gozillarc]
 * +-----------------------
 * |a:http://www.gnu.org
 * |f = file:/usr/share/xxx.html
 * |www	http://www.xxx.yyy/
 */
static void
load_alias(void)
{
	FILE *ip;
	STRBUF *sb = strbuf_open(0);
	char *p;
	int flag = STRBUF_NOCRLF;
	struct sh_entry *ent;

	sh = strhash_open(10);
	if (!(p = get_home_directory()))
		goto end;
	if (!test("r", makepath(p, gozillarc, NULL)))
#ifdef __DJGPP__
		if (!test("r", makepath(p, dos_gozillarc, NULL)))
#endif
			goto end;
	if (!(ip = fopen(makepath(p, gozillarc, NULL), "r")))
#ifdef __DJGPP__
		if (!(ip = fopen(makepath(p, dos_gozillarc, NULL), "r")))
#endif
			goto end;
	while ((p = strbuf_fgets(sb, ip, flag)) != NULL) {
		char *name, *value;

		flag &= ~STRBUF_APPEND;
		if (*p == '#')
			continue;
		if (strbuf_unputc(sb, '\\')) {
			flag |= STRBUF_APPEND;
			continue;
		}
		while (*p && isblank(*p))	/* skip spaces */
			p++;
		name = p;
		while (*p && isalnum(*p))	/* get name */
			p++;
		*p++ = 0;
		while (*p && isblank(*p))	/* skip spaces */
			p++;
		if (*p == '=' || *p == ':') {
			p++;
			while (*p && isblank(*p))/* skip spaces */
				p++;
		}
		value = p;
		while (*p && !isblank(*p))	/* get value */
			p++;
		*p = 0;
		ent = strhash_assign(sh, name, 1);
		if (ent->value)
			(void)free(ent->value);
		ent->value = check_strdup(value);
	}
	fclose(ip);
end:
	strbuf_close(sb);
}
/*
 * alias: get alias value.
 *
 *	i)	alias_name	alias name
 *	r)			its value
 */
static const char *
alias(const char *alias_name)
{
	struct sh_entry *ent = strhash_assign(sh, alias_name, 0);
	return ent ? ent->value : NULL;
}

/*
 * locate_HTMLdir: locate HTML directory made by htags(1).
 *
 *	r)		HTML directory
 */
static const char *
locate_HTMLdir(void)
{
	static char htmldir[MAXPATHLEN+1];

	if (test("d", makepath(dbpath, "HTML", NULL)))
		strlimcpy(htmldir, makepath(dbpath, "HTML", NULL), sizeof(htmldir));
	else if (test("d", makepath(root, "HTML", NULL)))
		strlimcpy(htmldir, makepath(root, "HTML", NULL), sizeof(htmldir));
	else if (test("d", makepath(root, "html/HTML", NULL)))
		/* Doxygen makes HTML in doxygen's html directory. */
		strlimcpy(htmldir, makepath(root, "html/HTML", NULL), sizeof(htmldir));
	else
		die("hypertext not found. See htags(1).");
	if (vflag)
		fprintf(stdout, "HTML directory '%s'.\n", htmldir);
	return (const char *)htmldir;
}
int
main(int argc, char **argv)
{
	char c;
	const char *p, *browser = NULL, *definition = NULL;
	STRBUF *arg = strbuf_open(0);
	STRBUF *URL = strbuf_open(0);

	while (--argc > 0 && ((c = (++argv)[0][0]) == '-' || c == '+')) {
		if (argv[0][1] == '-') {
			if (!strcmp("--help", argv[0]))
				help();
			else if (!strcmp("--version", argv[0]))
				show_version++;
			else if (!strcmp("--quiet", argv[0])) {
				qflag++;
				vflag = 0;
			} else if (!strcmp("--verbose", argv[0])) {
				vflag++;
				qflag = 0;
			} else
				usage();
			continue;
		}
		if (c == '+') {
			linenumber = atoi(argv[0] + 1);
			continue;
		}
		p = argv[0] + 1;
		switch (*p) {
		case 'b':
			browser = argv[1];
			--argc; ++argv;
			break;
		case 'd':
			definition = argv[1];
			--argc; ++argv;
			break;
		case 'p':
			pflag++;
			break;
		case 'q':
			qflag++;
			setquiet();
			break;
		case 'v':
			vflag++;
			break;
		default:
			usage();
		}
	}
	if (show_version)
		version(progname, vflag);
	/*
	 * Load aliases from .gozillarc.
	 */
	load_alias();

	/*
	 * Decide browser.
	 */
	if (!browser && getenv("BROWSER"))
		browser = getenv("BROWSER");
	if (!browser && alias("BROWSER"))
		browser = alias("BROWSER");
	if (!browser)
		browser = "mozilla";

	/*
	 * Replace alias name.
	 */
	if (definition == NULL) {
		if (argc == 0)
			usage();
		strbuf_puts(arg, argv[0]);
		/*
		 * Replace with alias value.
		 */
		if ((p = alias(strbuf_value(arg))) != NULL) {
			strbuf_reset(arg);
			strbuf_puts(arg, p);
		}
	}
	/*
	 * Get URL.
	 */
	if (!definition && isprotocol(strbuf_value(arg))) {
		strbuf_puts(URL, strbuf_value(arg));
	} else {
		getdbpath(cwd, root, dbpath, 0);
		if (definition)
			getdefinitionURL(definition, URL);
		else
			getURL(strbuf_value(arg), URL);
	}
	if (pflag) {
		fprintf(stdout, "%s\n", strbuf_value(URL));
		if (vflag)
			fprintf(stdout, "using browser '%s'.\n", browser);
		exit(0);
	}
	/*
	 * Show URL's page.
	 */
	show_page_by_url(browser, strbuf_value(URL));
	exit(0);
}

/*
 * getdefinitionURL: get URL includes specified definition.
 *
 *	i)	arg	definition name
 *	o)	URL	URL begin with 'file:'
 */
void
getdefinitionURL(const char *arg, STRBUF *URL)
{
	const char *path;
	char *p;
	STRBUF *sb = NULL;
	DBOP *dbop = NULL;
	SPLIT ptable;
	int status = -1;
	const char *htmldir = locate_HTMLdir();

	path = makepath(htmldir, "MAP.db", NULL);
	if (test("f", path))
		dbop = dbop_open(path, 0, 0, 0);
	if (!dbop) {
		path = makepath(htmldir, "MAP", NULL);
		if (!test("f", path))
			die("'%s' not found. Please reconstruct hypertext using the latest htags(1).", path);
		dbop = dbop_open(path, 0, 0, 0);
	}
	if (dbop) {
		if ((p = (char *)dbop_get(dbop, arg)) != NULL) {
			if (split(p, 2, &ptable) != 2)
				die("illegal format.");
			status = 0;
		}
		dbop_close(dbop);
	} else {
		FILE *fp;

		sb = strbuf_open(0);
		fp = fopen(path, "r");
		if (fp) {
			while ((p = strbuf_fgets(sb, fp, STRBUF_NOCRLF)) != NULL) {
				if (split(p, 2, &ptable) != 2)
					die("illegal format.");
				if (!strcmp(arg, ptable.part[0].start)) {
					status = 0;
					break;
				}
			}
			fclose(fp);
		}
	}
	if (status == -1)
		die("definition %s not found.", arg);
	strbuf_reset(URL);
	/*
	 * convert path into URL.
	 */
	makefileurl(makepath(htmldir, ptable.part[1].start, NULL), 0, URL);
	recover(&ptable);
	if (sb != NULL)
		strbuf_close(sb);
}
/*
 * getURL: get URL of the specified file.
 *
 *	i)	file	file name
 *	o)	URL	URL begin with 'file:'
 */
void
getURL(const char *file, STRBUF *URL)
{
	char *p;
	char rootdir[MAXPATHLEN+1];
	char buf[MAXPATHLEN+1];
	STRBUF *sb = strbuf_open(0);
	const char *htmldir = locate_HTMLdir();

        /*
         * rootdir always ends with '/'.
         */
        if (!strcmp(root, "/"))
                strlimcpy(rootdir, root, sizeof(rootdir));
        else
                snprintf(rootdir, sizeof(rootdir), "%s/", root);
	if (!test("f", file) && !test("d", file))
		die("path '%s' not found.", file);
	p = normalize(file, rootdir, cwd, buf, sizeof(buf));
	if (convertpath(dbpath, htmldir, p, sb) == 0)
		makefileurl(strbuf_value(sb), linenumber, URL);
	else
		makefileurl(realpath(file, buf), 0, URL);
	strbuf_close(sb);
}
/*
 * isprotocol: return 1 if url has a procotol.
 *
 *	i)	url	URL
 *	r)		1: protocol, 0: file
 */
int
isprotocol(const char *url)
{
	const char *p;

	if (locatestring(url, "file:", MATCH_AT_FIRST))
		return 1;
	/*
	 * protocol's style is like http://xxx.
	 */
	for (p = url; *p && *p != ':'; p++)
		if (!isalnum(*p))
			return 0;
	if (!*p)
		return 0;
	if (*p++ == ':' && *p++ == '/' && *p == '/')
		return 1;
	return 0;
}
/*
 * convertpath: convert source file into hypertext path.
 *
 *	i)	dbpath	dbpath
 *	i)	htmldir	HTML directory made by htags(1)
 *	i)	path	source file path
 *	o)	sb	string buffer
 *	r)		0: normal, -1: error
 */
int
convertpath(const char *dbpath, const char *htmldir, const char *path, STRBUF *sb)
{
	static const char *suffix[] = {".html", ".htm"};
	static const char *gz = ".gz";
	int i, lim = sizeof(suffix)/sizeof(char *);
	const char *p;

	strbuf_reset(sb);
	strbuf_puts(sb, htmldir);
	strbuf_puts(sb, "/S/");
	/*
	 * new style.
	 */
	if (gpath_open(dbpath, 0) == 0) {
		int tag1 = strbuf_getlen(sb);

		p = gpath_path2fid(path, NULL);
		if (p == NULL) {
			gpath_close();
			return -1;
		}
		gpath_close();
		strbuf_puts(sb, p);
		for (i = 0; i < lim; i++) {
			int tag2 = strbuf_getlen(sb);
			strbuf_puts(sb, suffix[i]);
			if (test("f", strbuf_value(sb)))
				return 0;
			strbuf_puts(sb, gz);
			if (test("f", strbuf_value(sb)))
				return 0;
			strbuf_setlen(sb, tag2);
		}
		strbuf_setlen(sb, tag1);
	}
	/*
	 * old style.
	 */
	for (p = path + 1; *p; p++)
		strbuf_putc(sb, (*p == '/') ? ' ' : *p);
	for (i = 0; i < lim; i++) {
		int tag = strbuf_getlen(sb);
		strbuf_puts(sb, suffix[i]);
		if (test("f", strbuf_value(sb)))
			return 0;
		strbuf_puts(sb, gz);
		if (test("f", strbuf_value(sb)))
			return 0;
		strbuf_setlen(sb, tag);
	}
	return -1;
}
/*
 * makefileurl: make url which start with 'file:'.
 *
 *	i)	path	path name (absolute)
 *	i)	line	!=0: line number
 *	o)	url	URL
 *
 * makefileurl('/dir/a.html', 10)   => 'file:///dir/a.html#L10'
 *
 * (Windows32 environment)
 * makefileurl('c:/dir/a.html', 10) => 'file://c|/dir/a.html#L10'
 */
void
makefileurl(const char *path, int line, STRBUF *url)
{
	strbuf_puts(url, "file://");
#if _WIN32 || __DJGPP__
	/*
	 * copy drive name. (c: -> c|)
	 */
	if (isalpha(*path) && *(path+1) == ':') {
		strbuf_putc(url, *path);
		strbuf_putc(url, '|');
		path += 2;
	}
#endif
	strbuf_puts(url, path);
	if (line) {
		strbuf_puts(url, "#L");
		strbuf_putn(url, line);
	}
}
/*
 * show_page_by_url: show page by url
 *
 *	i)	browser browser name
 *	i)	url	URL
 */
#if defined(_WIN32)
/* Windows32 version */
void
show_page_by_url(const char *browser, const char *url)
{
	if (ShellExecute(NULL, NULL, browser, url, NULL, SW_SHOWNORMAL) <= (HINSTANCE)32)
		die("Cannot load %s (error = 0x%04x).", browser, GetLastError());
}
#elif defined(__DJGPP__)
/* DJGPP version */
void
show_page_by_url(const char *browser, const char *url)
{
	char com[MAXFILLEN+1];
	char *path;

	/*
	 * assume a Windows browser if it's not on the path.
	 */
	if (!(path = usable(browser))) {
		/*
		 * START is an internal command in XP, external in 9X.
		 */
		if (!(path = usable("start")))
			path = "cmd /c start";
		snprintf(com, sizeof(com), "%s %s \"%s\"", path, browser, url);
	} else {
		snprintf(com, sizeof(com), "%s \"%s\"", path, url);
	}
	system(com);
}
#else
/* UNIX version */
void
show_page_by_url(const char *browser, const char *url)
{
	char com[1024];

	/*
	 * Browsers which have openURL() command.
	 */
	if (locatestring(browser, "mozilla", MATCH_AT_LAST) ||
	    locatestring(browser, "netscape", MATCH_AT_LAST) ||
	    locatestring(browser, "netscape-remote", MATCH_AT_LAST))
	{
		char *path;

		if (!(path = usable(browser)))
			die("%s not found in your path.", browser);
		snprintf(com, sizeof(com), "%s -remote \"openURL(%s)\"", path, url);
	}
	/*
	 * Generic browser.
	 */
	else {
		snprintf(com, sizeof(com), "%s \"%s\"", browser, url);
	}
	system(com);
}
#endif
