#include "cgit.h"
static const char cgit_doctype[] =
"\n";
static const char cgit_error[] =
"
%s
";
static const char cgit_lib_error[] =
"%s: %s
";
char *cgit_root         = "/var/git";
char *cgit_root_title   = "Git repository browser";
char *cgit_css          = "/cgit.css";
char *cgit_logo         = "/git-logo.png";
char *cgit_logo_link    = "http://www.kernel.org/pub/software/scm/git/docs/";
char *cgit_virtual_root = NULL;
char *cgit_repo_name    = NULL;
char *cgit_repo_desc    = NULL;
char *cgit_repo_owner   = NULL;
char *cgit_query_repo   = NULL;
char *cgit_query_page   = NULL;
char *cgit_query_head   = NULL;
int cgit_parse_query(char *txt, configfn fn)
{
	char *t = txt, *value = NULL, c;
	if (!txt)
		return 0;
	while((c=*t) != '\0') {
		if (c=='=') {
			*t = '\0';
			value = t+1;
		} else if (c=='&') {
			*t = '\0';
			(*fn)(txt, value);
			txt = t+1;
			value = NULL;
		}
		t++;
	}
	if (t!=txt)
		(*fn)(txt, value);
	return 0;
}
void cgit_global_config_cb(const char *name, const char *value)
{
	if (!strcmp(name, "root"))
		cgit_root = xstrdup(value);
	else if (!strcmp(name, "root-title"))
		cgit_root_title = xstrdup(value);
	else if (!strcmp(name, "css"))
		cgit_css = xstrdup(value);
	else if (!strcmp(name, "logo"))
		cgit_logo = xstrdup(value);
	else if (!strcmp(name, "logo-link"))
		cgit_logo_link = xstrdup(value);
	else if (!strcmp(name, "virtual-root"))
		cgit_virtual_root = xstrdup(value);
}
void cgit_repo_config_cb(const char *name, const char *value)
{
	if (!strcmp(name, "name"))
		cgit_repo_name = xstrdup(value);
	else if (!strcmp(name, "desc"))
		cgit_repo_desc = xstrdup(value);
	else if (!strcmp(name, "owner"))
		cgit_repo_owner = xstrdup(value);
}
void cgit_querystring_cb(const char *name, const char *value)
{
	if (!strcmp(name,"r"))
		cgit_query_repo = xstrdup(value);
	else if (!strcmp(name, "p"))
		cgit_query_page = xstrdup(value);
	else if (!strcmp(name, "h"))
		cgit_query_head = xstrdup(value);
}
char *cgit_repourl(const char *reponame)
{
	if (cgit_virtual_root) {
		return fmt("%s/%s/", cgit_virtual_root, reponame);
	} else {
		return fmt("?r=%s", reponame);
	}
}
char *cgit_pageurl(const char *reponame, const char *pagename, 
		   const char *query)
{
	if (cgit_virtual_root) {
		return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, 
			   pagename, query);
	} else {
		return fmt("?r=%s&p=%s&%s", reponame, pagename, query);
	}
}
static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
				int flags, void *cb_data)
{
	struct commit *commit;
	char buf[256], *url;
	commit = lookup_commit(sha1);
	if (commit && !parse_commit(commit)){
		html("| ");
		url = cgit_pageurl(cgit_query_repo, "log", 
				   fmt("h=%s", refname));
		html_link_open(url, NULL, NULL);
		strncpy(buf, refname, sizeof(buf));
		html_txt(buf);
		html_link_close();
		html(" | ");
		pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, buf,
				    sizeof(buf), 0, NULL, NULL, 0);
		html_txt(buf);
		html(" | 
\n");
	} else {
		html("| ");
		html_txt(buf);
		html(" | ");
		htmlf("*** bad ref %s", sha1_to_hex(sha1));
		html(" | 
\n");
	}
	return 0;
}
static void cgit_print_docstart(char *title)
{
	html("Content-Type: text/html; charset=utf-8\n");
	html("\n");
	html(cgit_doctype);
	html("\n");
	html("\n");
	html("");
	html_txt(title);
	html("\n");
	html("\n");
	html("\n");
	html("\n");
}
static void cgit_print_docend()
{
	html("\n\n");
}
static void cgit_print_pageheader(char *title)
{
	html("");
}
static void cgit_print_repolist()
{
	DIR *d;
	struct dirent *de;
	struct stat st;
	char *name;
	cgit_print_docstart(cgit_root_title);
	cgit_print_pageheader(cgit_root_title);
	if (!(d = opendir("."))) {
		htmlf(cgit_lib_error, "Unable to scan repository directory",
		      strerror(errno));
		cgit_print_docend();
		return;
	}
	html("Repositories
\n");
	html("");
	html("| Name | Description | Owner | 
|---|
\n");
	while ((de = readdir(d)) != NULL) {
		if (de->d_name[0] == '.')
			continue;
		if (stat(de->d_name, &st) < 0)
			continue;
		if (!S_ISDIR(st.st_mode))
			continue;
		cgit_repo_name = cgit_repo_desc = cgit_repo_owner = NULL;
		name = fmt("%s/.git/info/cgit", de->d_name);
		if (cgit_read_config(name, cgit_repo_config_cb))
			continue;
		html("| ");
		html_link_open(cgit_repourl(de->d_name), NULL, NULL);
		html_txt(cgit_repo_name);
		html_link_close();
		html(" | ");
		html_txt(cgit_repo_desc);
		html(" | ");
		html_txt(cgit_repo_owner);
		html(" | 
\n");
	}
	closedir(d);
	html("
");
	cgit_print_docend();
}
static void cgit_print_branches()
{
	html("");
	html("| Branch name | Head commit | 
|---|
\n");
	for_each_branch_ref(cgit_print_branch_cb, NULL);
	html("
");
}
static int get_one_line(char *txt)
{
	char *t;
	for(t=txt; *t != '\n' && t != '\0'; t++)
		;
	*t = '\0';
	return t-txt-1;
}
static void cgit_print_commit_shortlog(struct commit *commit)
{
	char *h, *t, *p; 
	char *tree = NULL, *author = NULL, *subject = NULL;
	int len;
	time_t sec;
	struct tm *time;
	char buf[32];
	h = t = commit->buffer;
	
	if (strncmp(h, "tree ", 5))
		die("Bad commit format: %s", 
		    sha1_to_hex(commit->object.sha1));
	
	len = get_one_line(h);
	tree = h+5;
	h += len + 2;
	while (!strncmp(h, "parent ", 7))
		h += get_one_line(h) + 2;
	
	if (!strncmp(h, "author ", 7)) {
		author = h+7;
		h += get_one_line(h) + 2;
		t = author;
		while(t!=h && *t!='<') 
			t++;
		*t='\0';
		p = t;
		while(--t!=author && *t==' ')
			*t='\0';
		while(++p!=h && *p!='>')
			;
		while(++p!=h && !isdigit(*p))
			;
		t = p;
		while(++p && isdigit(*p))
			;
		*p = '\0';
		sec = atoi(t);
		time = gmtime(&sec);
	}
	while((len = get_one_line(h)) > 0)
		h += len+2;
	h++;
	len = get_one_line(h);
	subject = h;
	html("| ");
	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", time);
	html_txt(buf);
	html(" | ");
	char *qry = fmt("h=%s", sha1_to_hex(commit->object.sha1));
	char *url = cgit_pageurl(cgit_query_repo, "view", qry);
	html_link_open(url, NULL, NULL);
	html_txt(subject);
	html_link_close();
	html(" | ");
	html_txt(author);
	html(" | 
\n");
}
static void cgit_print_log(const char *tip, int ofs, int cnt)
{
	struct rev_info rev;
	struct commit *commit;
	const char *argv[2] = {NULL, tip};
	int n = 0;
	
	init_revisions(&rev, NULL);
	rev.abbrev = DEFAULT_ABBREV;
	rev.commit_format = CMIT_FMT_DEFAULT;
	rev.verbose_header = 1;
	rev.show_root_diff = 0;
	setup_revisions(2, argv, &rev, NULL);
	prepare_revision_walk(&rev);
	html("Log
");
	html("");
	html("| Date | Message | Author | 
|---|
\n");
	while ((commit = get_revision(&rev)) != NULL && n++ < 100) {
		cgit_print_commit_shortlog(commit);
		free(commit->buffer);
		commit->buffer = NULL;
		free_commit_list(commit->parents);
		commit->parents = NULL;
	}
	html("
\n");
}
static void cgit_print_repo_summary()
{
	html("");
	html_txt("Repo summary page");
	html("
");
	cgit_print_branches();
}
static void cgit_print_object(char *hex)
{
	unsigned char sha1[20];
	//struct object *object;
	char type[20];
	unsigned char *buf;
	unsigned long size;
	if (get_sha1_hex(hex, sha1)){
		htmlf(cgit_error, "Bad hex value");
	        return;
	}
	if (sha1_object_info(sha1, type, NULL)){
		htmlf(cgit_error, "Bad object name");
		return;
	}
	buf = read_sha1_file(sha1, type, &size);
	if (!buf) {
		htmlf(cgit_error, "Error reading object");
		return;
	}
	buf[size] = '\0';
	html("Object view
");
	htmlf("sha1=%s
type=%s
size=%i
", hex, type, size);
	html("");
	html_txt(buf);
	html("");
}
static void cgit_print_repo_page()
{
	if (chdir(cgit_query_repo) || 
	    cgit_read_config(".git/info/cgit", cgit_repo_config_cb)) {
		char *title = fmt("%s - %s", cgit_root_title, "Bad request");
		cgit_print_docstart(title);
		cgit_print_pageheader(title);
		htmlf(cgit_lib_error, "Unable to scan repository",
		      strerror(errno));
		cgit_print_docend();
		return;
	}
	
	char *title = fmt("%s - %s", cgit_repo_name, cgit_repo_desc);
	cgit_print_docstart(title);
	cgit_print_pageheader(title);
	if (!cgit_query_page)
		cgit_print_repo_summary();
	else if (!strcmp(cgit_query_page, "log")) {
		cgit_print_log(cgit_query_head, 0, 100);
	} else if (!strcmp(cgit_query_page, "view")) {
		cgit_print_object(cgit_query_head);
	}
	cgit_print_docend();
}
int main(int argc, const char **argv)
{
	if (cgit_read_config("/etc/cgitrc", cgit_global_config_cb))
		die("Error reading config: %d %s", errno, strerror(errno));
	chdir(cgit_root);
	cgit_parse_query(getenv("QUERY_STRING"), cgit_querystring_cb);
	if (cgit_query_repo)
		cgit_print_repo_page();
	else
		cgit_print_repolist();
	return 0;
}