/*
 * This file is part of cxxwrap
 * Copyright (c) 1998, 1999 David Deaven (deaven@execpc.com)
 *
 *   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 2 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, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: man.cxx,v 1.16 2001/06/09 05:03:45 deaven Exp $
 */

#include <sys/types.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "Class.h"
#include "EnumCType.h"
#include "JavadocLink.h"
#include "ClassJar.h"
#include "man.h"

void ManualEmitter::start_page(FILE* f, const char* title)
{
    time_t clock = time(NULL);
    struct tm* ltime = localtime(&clock);
    char date[128];
    strftime(date, 128, "%e %b %Y", ltime);
    fprintf(f, 
      ".\\\" Generated by cxxwrap, do not edit this file\n"
      ".TH \"%s\" 3 \"%s\"\n",
      title, date);
}

void ManualEmitter::start_section(FILE* f, const char* title)
{
	fprintf(f, ".SH %s\n.PP\n", title);
}

const char* ManualEmitter::emit_link_text(FILE* f, const char* text)
{
    const char* eol = strchr(text, '}');
    if (!eol)
    {
	fputc((int) *text, f);
	return text;
    }

    int len = eol - text;

    char* buf = new char[len + 2];
    strncpy(buf, text, len);
    buf[len] = '\0';

    JavadocLink l(buf, jar);
    if (l.hrefText())
    {
	fprintf(f, "%s (%s)", l.linkText(), l.hrefText());
    }
    else
    {
	fprintf(f, "%s", l.linkText());
    }

    delete[] buf;

    return eol;
}

const char*
ManualEmitter::emit_comment_word(FILE* f, const char* first, const char* p)
{
    if (*p == '\n')
    {
	fputc((int) *p, f);

	const char* l = p + 1;
	while (*l != '\0' && isspace(*l)) l++;
	if (*l == '*') // skip leading stars
	{
	    p = l;
	}
    }
    else if (isalnum(*p) && (p == first || isspace(*(p - 1))))
    {
	// check for known class names
	char buf[TOKEN_BUFFER_SIZE];
	strncpy(buf, p, 127);
	char* s = buf;
	while (isalnum(*s)) s++;
	*s = '\0';
	if (jar->findClassByName(buf))
	{
	    fprintf(f, "%s(3)", buf);
	    p += strlen(buf) - 1;
	}
	else
	{
	    fputc((int) *p, f);
	}
    }
    else if (strncmp(p, "{@link ", 7) == 0)
    {
	p = emit_link_text(f, p + 7);
    }
    else if (*p == '<')
    {
	if (is_html_tag(p))
	{
	    if (!strncasecmp(p, "<b>", 3))
	    {
		p += 2;
		fprintf(f, "\n.B ");

	    }
	    else if (!strncasecmp(p, "<p>", 3))
	    {
		p += 2;
		fprintf(f, "\n.PP\n");
	    }
	    else
	    {
		while (*p != '\0' && *p != '>')
		{
		    p++; // skipping
		}
	    }
	}
	else
	{
	    fputc((int) *p, f);
	}
    }
    else
    {
	fputc((int) *p, f);
    }

    return p;
}

void ManualEmitter::emit_gathered_tags(FILE* f, CommentTag* tags,
    const char* tag, const char* title)
{
    CommentTag* list = tags->enumerateTag(tag);
    if (list)
    {
	fprintf(f, "\n.PP\n.TP\n%s\n", title);
	CommentTag* t;
	for (t = list; t; t = t->next)
	{
	    fprintf(f, "\n");
	    emit_comment_text(f, t->value);
	}
	fprintf(f, "\n\n");
    }
    delete list;
}

void ManualEmitter::emit_sees(FILE* f, CommentTag* tags)
{
    CommentTag* list = tags->enumerateTag("see");
    if (list)
    {
	fprintf(f, "\nSee also:\n");
	CommentTag* t;
	for (t = list; t; t = t->next)
	{
	    JavadocLink link(t->value, jar);
	    if (link.hrefText())
	    {
		fprintf(f, "%s (%s)",
		    link.linkText(), link.hrefText());
	    }
	    else
	    {
		emit_comment_text(f, link.linkText());
	    }
	}
	fprintf(f, "\n\n");
    }
    delete list;
}

void ManualEmitter::emit_params(FILE* f, CommentTag* tags)
{
    CommentTag* list = tags->enumerateTag("param");
    if (list)
    {
	fprintf(f, "\n.PP\n.TP\n.B Parameters:\n");
	CommentTag* t;
	for (t = list; t; t = t->next)
	{
	    fprintf(f, "\n.IR ");
	    const char* p = t->value;
	    while (*p != '\0' && isspace(*p)) p++;
	    while (*p != '\0' && !isspace(*p)) fputc((int) *(p++), f);
	    fprintf(f, " -\n");
	    while (*p != '\0' && isspace(*p)) p++;
	    emit_comment_text(f, p);
	}
	fprintf(f, "\n");
    }
    delete list;
}

void ManualEmitter::emit_comment(FILE* f, const char* comment)
{
    CommentParser* cp = comment_parser(comment);

    // mark it as deprecated if it is
    CommentTag* tags = cp->tags();
    CommentTag* t;
    if (tags && (t = tags->findTag("deprecated")))
    {
	fprintf(f, ".B DEPRECATED:\n");
	emit_comment_text(f, t->value);
    }

    // write the body of the comment
    const char* p = cp->body();
    if (p)
    {
	emit_comment_text(f, p);
    }

    // write the comment tags
    if (tags)
    {
	// all of the parameters
	emit_params(f, tags);
	emit_gathered_tags(f, tags, "return", "Returns:");

	// anything but {deprecated,param,return,see}
	for (t = tags; t; t = t->next)
	{
	    // tags not to output in this section
	    static char* skip[] = {
		"param", "return", "deprecated", "see", "header", "package",
		NULL
	    };

	    int doit = 1;
	    char** s;
	    for (s = skip; *s; s++)
	    {
		if (strcmp(*s, t->tag) == 0)
		{
		    doit = 0;
		    break;
		}
	    }

	    if (doit)
	    {
		// first character upper case, add colon
		fprintf(f, "\n.PP\n.TP\n");
		fputc((int) toupper(*(t->tag)), f);
		fprintf(f, "%s:\n", t->tag + 1);
		emit_comment_text(f, t->value);
	    }
	}

	// see also(s)
	emit_sees(f, tags);
    }

    delete cp;
}

int ManualEmitter::emit_page_for_one(Class* c, const char* path)
{
    // open appropriately-named files

    FILE *fout;
    
    {
	char buf[FILENAME_MAX];
	sprintf(buf, "%s/man3/%s.3", path, c->name());
        fout = open_file(buf, "w");
	if (!fout) {
	    fprintf(stderr, "cannot open output file \"%s\"\n", buf);
	    return -1;
	}
    }

    printf("writing man page for class %s\n", c->name());

    // write a file header

    start_page(fout, "API");
    fprintf(fout, ".SH NAME\n%s class API\n", c->name());

    if (c->parentClass() || c->parent)
    {
	start_section(fout, "HERITAGE");
	emit_tree(fout, c);
	fprintf(fout, "\n\n");
    }

    if (c->type()->source_file)
    {
	const char* p = c->type()->source_file;
	while (isspace(*p) || *p == '.' || *p == '/') p++;
	start_section(fout, "SYNOPSIS");
	// look for a @header tag in the class comment
	if (c->comment)
	{
	    CommentParser* cp = comment_parser(c->comment);
	    CommentTag* t = cp->tags()->findTag("header");
	    if (t) 
	    {
		emit_comment_text(fout, t->value);
		fprintf(fout, "\n");
	    }
	    delete cp;
	}
	fprintf(fout, "#include <%s>\n\n", p);
    }

    if (c->comment) 
    {
	start_section(fout, "DESCRIPTION");
	emit_comment(fout, c->comment);
	fprintf(fout, "\n\n");
    }

    // go through the methods, emitting code for each

    int i;
    Method* m;
    Method* mlist = c->completeMethodList();
    int count = 0;
    for (m = mlist; m; m = m->next) count++;
    Method** mtab = new Method*[count];
    // main method listing is reverse order in mlist XXX
    for (m = mlist, i = count; m; m = m->next) mtab[--i] = m;

    int numEnumerations = 0;

    for (i = 0; i < count; i++)
    {
	m = mtab[i];
	if (m->type->modifiers & CType::M_ENUM)
	    numEnumerations++;
    }

    if (numEnumerations > 0)
    {
	start_section(fout, "ENUMERATIONS");

	for (i = 0; i < count; i++)
	{
	    m = mtab[i];

	    if (m->type->modifiers & CType::M_ENUM)
	    {
		EnumCType* ect = (EnumCType*) m->type; // XXX
		fprintf(fout, "\n.TP\nenum %s\n\n", ect->getName());
		if (m->comment)
		{
		    emit_comment(fout, m->comment);
		}
		fprintf(fout, "\n\nValues:\n.TP\n");
		Arg* a;
		for (a = ect->getSymbols(); a != NULL; a = a->next)
		{
		    fprintf(fout, "\n\n%s\n", a->name);
		    if (a->comment)
		    {
			emit_comment(fout, a->comment);
		    }
		}
		fprintf(fout, "\n\n");
	    }
	}
    }

    start_section(fout, "CONSTRUCTORS");
    for (i = 0; i < count; i++)
    {
	m = mtab[i];
	if (m->type->modifiers & (CType::M_CTOR | CType::M_DTOR))
	{
	    emit_method(fout, m);
	}
    }

    start_section(fout, "PUBLIC METHODS");
    int seen_inherited = 0;
    for (i = 0; i < count; i++)
    {
	m = mtab[i];

	if (!(m->type->modifiers &
	    (CType::M_ENUM | CType::M_CTOR | CType::M_DTOR | CType::M_PROTECTED)))
	{
	    if (c->isInheritedMethod(m) && !seen_inherited)
	    {
		seen_inherited = 1;
	    }
	    emit_method(fout, m);
	}
    }

    if (!global.public_doc)
    {
	start_section(fout, "PROTECTED METHODS");
	for (i = 0; i < count; i++)
	{
	    m = mtab[i];
	    
	    if (!(m->type->modifiers &
		(CType::M_ENUM | CType::M_CTOR | CType::M_DTOR)) &&
		(m->type->modifiers & CType::M_PROTECTED))
	    {
		emit_method(fout, m);
	    }
	}
    }

    delete[] mtab;
    delete mlist;

    // close the file

    fclose(fout);

    return 0;
}

static int ccmp(Class** a, Class** b)
{
    return strcmp((*a)->name(), (*b)->name());
}

void ManualEmitter::emit_method(FILE* fout, Method* m)
{
    char buf[TOKEN_BUFFER_SIZE];
    sprintf(buf, "%s %s", m->type->CTypeName(), m->name);
    int len = strlen(buf);

    fprintf(fout, "%s", buf);

    if (! (m->type->modifiers & CType::M_ATTRIBUTE))
    {
	fprintf(fout, "(");
	Arg* a;
	for (a = m->args; a != NULL; a = a->next)
	{
	    const char* ctn = a->type->CTypeName();
	    if (strcmp(ctn, "void") == 0 && a->next == NULL)
	    {
		// "void" --> "" in strict typecheck C style
	    }
	    else
	    {
		if (a != m->args)
		{
		    int l;
		    for (l = 0; l <= len; l++) fputc(' ', fout);
		}
		fprintf(fout, "%s %s%s",
		    ctn, a->name, a->next ? ",\n" : "");
	    }
	}
	fprintf(fout, ")");
    }
    fprintf(fout, "\n\n");
    if (m->comment)
    {
	emit_comment(fout, m->comment);
    }
    fprintf(fout, "\n\n");
}

ManualEmitter::ManualEmitter(ClassJar* jar_, const char* path_) :
    Emitter(jar_),
    path(path_)
{
}

ManualEmitter::~ManualEmitter()
{
}

int ManualEmitter::emit(int /* argc */, char** /* argv */)
{
    char buf[FILENAME_MAX];

    // make sure the path exists
    sprintf(buf, "%s/man3", path);
    make_path(buf);

    // open the intro page
    sprintf(buf, "%s/man3/intro.3", path);
    FILE* f = open_file(buf, "w");
    if (f == NULL)
    {
	fprintf(stderr, "cannot open intro page \"%s\"\n", buf);
	return -1;
    }

    // do all of the classes
    Class *c;
    ClassJarIterator iter(jar->iterator());
    while ((c = iter.next()))
    {
	emit_page_for_one(c, path);
    }

    // do the intro page
    start_page(f, "intro");

    fprintf(f, ".SH CLASSES\n.PP\n");

    qsort(ctab, class_count, sizeof(Class*),
	(int (*)(const void *, const void *)) ccmp);

    int i;
    for (i = 0; i < class_count; i++)
    {
	c = ctab[i];
	fprintf(f, ".TP\n.I %s\n", c->name());
    }
    fclose(f);
    printf("wrote intro man page \"%s\"\n", buf);

    return 0;
}
