// -*- c-basic-offset: 4 -*-
/*
 * click-pretty.cc -- pretty-print Click configurations
 * Eddie Kohler
 *
 * Copyright (c) 2001-2002 International Computer Science Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/pathvars.h>

#include "routert.hh"
#include "lexert.hh"
#include "lexertinfo.hh"
#include "html.hh"
#include <click/error.hh>
#include <click/driver.hh>
#include <click/straccum.hh>
#include <click/confparse.hh>
#include <click/clp.h>
#include "toolutils.hh"
#include "processingt.hh"
#include "elementmap.hh"

#define HELP_OPT		300
#define VERSION_OPT		301
#define CLICKPATH_OPT		302
#define ROUTER_OPT		303
#define EXPRESSION_OPT		304
#define OUTPUT_OPT		305
#define CLASS_URLS_OPT		306
#define TEMPLATE_OPT		307
#define WRITE_TEMPLATE_OPT	308
#define DEFINE_OPT		309
#define PACKAGE_URLS_OPT	310

#define FIRST_DRIVER_OPT	1000
#define USERLEVEL_OPT		(1000 + Driver::USERLEVEL)
#define LINUXMODULE_OPT		(1000 + Driver::LINUXMODULE)
#define BSDMODULE_OPT		(1000 + Driver::BSDMODULE)

static Clp_Option options[] = {
    { "bsdmodule", 'b', BSDMODULE_OPT, 0, 0 },
    { "clickpath", 'C', CLICKPATH_OPT, Clp_ArgString, 0 },
    { "class-docs", 'u', CLASS_URLS_OPT, Clp_ArgString, 0 },
    { "define", 'd', DEFINE_OPT, Clp_ArgString, 0 },
    { "expression", 'e', EXPRESSION_OPT, Clp_ArgString, 0 },
    { "file", 'f', ROUTER_OPT, Clp_ArgString, 0 },
    { "help", 0, HELP_OPT, 0, 0 },
    { "kernel", 'k', LINUXMODULE_OPT, 0, 0 },
    { "linuxmodule", 'l', LINUXMODULE_OPT, 0, 0 },
    { "output", 'o', OUTPUT_OPT, Clp_ArgString, 0 },
    { "package-docs", 0, PACKAGE_URLS_OPT, Clp_ArgString, 0 },
    { "template", 't', TEMPLATE_OPT, Clp_ArgString, 0 },
    { "userlevel", 0, USERLEVEL_OPT, 0, 0 },
    { "version", 'v', VERSION_OPT, 0, 0 },
    { "write-template", 0, WRITE_TEMPLATE_OPT, 0, Clp_Negate },
};

static String::Initializer string_initializer;
static const char *program_name;
static HashMap<String, String> definitions;
static int specified_driver = -1;

static const char *default_template = "\
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n\
<html><head>\n\
<meta http-equiv='Content-Type' content='text/html; charset=ISO-8859-1'>\n\
<meta http-equiv='Content-Style-Type' content='text/css'>\n\
<style type='text/css'><!--\n\
SPAN.c-kw {\n\
  font-weight: bold;\n\
}\n\
SPAN.c-cd {\n\
  font-style: italic;\n\
}\n\
SPAN.c-cfg {\n\
  color: green;\n\
}\n\
SPAN.c-cmt {\n\
  color: gray;\n\
}\n\
SPAN.c-err {\n\
  color: red;\n\
  font-weight: bold;\n\
}\n\
SPAN.c-err:hover {\n\
  color: black;\n\
  font-weight: bold;\n\
  background-color: #000;\n\
}\n\
P.ei, P.eit {\n\
  margin-top: 0px;\n\
  margin-bottom: 0px;\n\
  text-indent: -2em;\n\
  margin-left: 2em;\n\
  font-family: sans-serif;\n\
}\n\
P.et {\n\
  font-family: sans-serif;\n\
}\n\
TABLE.conntable TR TD {\n\
  font-family: sans-serif;\n\
  font-size: small;\n\
}\n\
--></style>\n\
<body>\n\
<h1>Configuration</h1>\n\
<~config>\n\
<h1><a name='index'>Element index</a></h1>\n\
<table cellspacing='0' cellpadding='0' border='0'>\n\
<tr valign='top'>\n\
<td><~elements\n\
  entry='<p class=\"ei\"><b><~name></b> :: <~type link> - <~configlink sep=\", \"><a href=\"#et-<~name>\"><i>table</i></a></p>'\n\
  typeentry='<p class=\"eit\"><b><~type link></b> (type)<br><i>see</i> <~typerefs sep=\", \"></p>'\n\
  typeref='<~name>'\n\
  configlink='<i>config</i>'\n\
  column='1/2' /--></td>\n\
<td width='15'>&nbsp;</td>\n\
<td><~elements\n\
  entry='<p class=\"ei\"><b><~name></b> :: <~type link> - <~configlink sep=\", \"><a href=\"#et-<~name>\"><i>table</i></a></p>'\n\
  typeentry='<p class=\"eit\"><b><~type link></b> (type)<br><i>see</i> <~typerefs sep=\", \"></p>'\n\
  typeref='<~name>'\n\
  configlink='<i>config</i>'\n\
  column='2/2' /--></td>\n\
</tr>\n\
</table>\n\
<h1><a name='tables'>Element tables</a></h1>\n\
<table cellspacing='0' cellpadding='0' border='0'>\n\
<tr valign='top'>\n\
<td><~elements\n\
  entry='<table cellspacing=\"0\" cellpadding=\"2\" border=\"0\">\n\
    <tr valign=\"top\"><td colspan=\"2\"><p class=\"et\"><a name=\"et-<~name>\"><b><~name></b></a> :: <~type link> <~configlink></p></td></tr>\n\
    <tr valign=\"top\"><td width=\"20\">&nbsp;</td>\n\
       <td><table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"conntable\">\n\
         <~inputs><~outputs>\n\
       </table></td></tr>\n\
    </table>'\n\
  configlink='<small>(<i>config</i>)</small>'\n\
  inputentry='<tr valign=\"top\"><td>input&nbsp;&nbsp;</td><td align=\"right\"><~port></td><td align=\"center\">&nbsp;(<~processing>)</td><td>&nbsp;&lt;-&nbsp;</td><td><~inputconnections sep=\", \"></td></tr>'\n\
  noinputentry='<tr valign=\"top\"><td colspan=\"5\">no inputs</td></tr>'\n\
  outputentry='<tr valign=\"top\"><td>output&nbsp;</td><td align=\"right\"><~port></td><td align=\"center\">&nbsp;(<~processing>)</td><td>&nbsp;-&gt;&nbsp;</td><td><~outputconnections sep=\", \"></td></tr>'\n\
  nooutputentry='<tr valign=\"top\"><td colspan=\"5\">no outputs</td></tr>'\n\
  inputconnection='<a href=\"#et-<~name>\"><~name></a>&nbsp;[<~port>]'\n\
  outputconnection='[<~port>]&nbsp;<a href=\"#et-<~name>\"><~name></a>'\n\
  noinputconnection='not connected'\n\
  nooutputconnection='not connected'\n\
  column='1/2'\n\
  /-->\n\
</td><td width='20'>&nbsp;</td>\n\
<td><~elements\n\
  entry='<table cellspacing=\"0\" cellpadding=\"2\" border=\"0\">\n\
    <tr valign=\"top\"><td colspan=\"2\"><p class=\"et\"><a name=\"et-<~name>\"><b><~name></b></a> :: <~type link> <~configlink></p></td></tr>\n\
    <tr valign=\"top\"><td width=\"20\">&nbsp;</td>\n\
       <td><table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" class=\"conntable\">\n\
         <~inputs><~outputs>\n\
       </table></td></tr>\n\
    </table>'\n\
  configlink='<small>(<i>config</i>)</small>'\n\
  inputentry='<tr valign=\"top\"><td>input&nbsp;&nbsp;</td><td align=\"right\"><~port></td><td align=\"center\">&nbsp;(<~processing>)</td><td>&nbsp;&lt;-&nbsp;</td><td><~inputconnections sep=\", \"></td></tr>'\n\
  noinputentry='<tr valign=\"top\"><td colspan=\"5\">no inputs</td></tr>'\n\
  outputentry='<tr valign=\"top\"><td>output&nbsp;</td><td align=\"right\"><~port></td><td align=\"center\">&nbsp;(<~processing>)</td><td>&nbsp;-&gt;&nbsp;</td><td><~outputconnections sep=\", \"></td></tr>'\n\
  nooutputentry='<tr valign=\"top\"><td colspan=\"5\">no outputs</td></tr>'\n\
  inputconnection='<a href=\"#et-<~name>\"><~name></a>&nbsp;[<~port>]'\n\
  outputconnection='[<~port>]&nbsp;<a href=\"#et-<~name>\"><~name></a>'\n\
  noinputconnection='not connected'\n\
  nooutputconnection='not connected'\n\
  column='2/2'\n\
  /-->\n\
</td></tr>\n\
</table>\n\
</body>\n\
</html>\n";


// list of classes

static HashMap<ElementClassT *, int> class2cid(-1);
static Vector<ElementClassT *> cid2class;
static Vector<String> cid_hrefs;
static HashMap<String, String> package_hrefs;

static int
cid(ElementClassT *type)
{
    int *cidp = class2cid.findp_force(type, -1);
    if (*cidp < 0) {
	*cidp = cid2class.size();
	cid2class.push_back(type);
	cid_hrefs.push_back(String());
    }
    return *cidp;
}

static void
add_class_href(ElementClassT *type, const String &href)
{
    cid_hrefs[cid(type)] = href;
}

static String
class_href(ElementClassT *type)
{
    String &sp = cid_hrefs[cid(type)];
    if (sp)
	return sp;
    else if (String href = type->documentation_url()) {
	add_class_href(type, href);
	return href;
    } else if (String doc_name = type->documentation_name()) {
	String package_href = package_hrefs["x" + type->package()];
	if (!package_href)
	    package_href = package_hrefs["x"];
	String href = percent_substitute(package_href, 's', doc_name.c_str(), 0);
	add_class_href(type, href);
	return href;
    } else {
	add_class_href(type, String());
	return String();
    }
}


// handle output items

struct OutputItem {
    int pos;
    String text;
    int _other;
    union {
	ElementT *element;
	ElementClassT *eclass;
    } _u;
    bool active : 1;
    bool _end_item : 1;
    int _type;
    enum { OI_NORMAL, OI_ELEMENT_REF, OI_ECLASS_REF };
    
    OutputItem() : pos(-1), _other(-1), active(0), _end_item(0), _type(OI_NORMAL) { }
    OutputItem(int p, const String &t, bool ei) : pos(p), text(t), _other(-1), active(0), _end_item(ei), _type(OI_NORMAL) { }
    OutputItem(int p, ElementT *e, bool ei) : pos(p), _other(-1), active(0), _end_item(ei), _type(OI_ELEMENT_REF) { _u.element = e; }
    OutputItem(int p, ElementClassT *ec, bool ei) : pos(p), _other(-1), active(0), _end_item(ei), _type(OI_ECLASS_REF) { _u.eclass = ec; }

    bool end_item() const		{ return _end_item; }
    OutputItem *other() const;
    int other_index() const		{ return _other; }
    int item_index() const;
    int end_item_index() const;
    void activate(bool a);
};

static Vector<OutputItem> items;
static Vector<OutputItem> end_items;
static bool items_prepared;

inline OutputItem *
OutputItem::other() const
{
    if (_other < 0)
	return 0;
    else if (_end_item)
	return &items[_other];
    else
	return &end_items[_other];
}

inline int
OutputItem::item_index() const
{
    if (_end_item)
	return other_index();
    else
	return other()->other_index();
}

inline int
OutputItem::end_item_index() const
{
    if (_end_item)
	return other()->other_index();
    else
	return other_index();
}

inline void
OutputItem::activate(bool a)
{
    active = other()->active = a;
}

static void
add_item(int p1, const String &t1, int p2, const String &t2)
{
    items.push_back(OutputItem(p1, t1, false));
    end_items.push_back(OutputItem(p2, t2, true));
    items.back()._other = end_items.back()._other = items.size() - 1;
}

static void
add_item(int p1, ElementT *e1, int p2, const String &t2)
{
    items.push_back(OutputItem(p1, e1, false));
    end_items.push_back(OutputItem(p2, t2, true));
    items.back()._other = end_items.back()._other = items.size() - 1;
}

static void
add_item(int p1, ElementClassT *e1, int p2, const String &t2)
{
    items.push_back(OutputItem(p1, e1, false));
    end_items.push_back(OutputItem(p2, t2, true));
    items.back()._other = end_items.back()._other = items.size() - 1;
}

extern "C" {
static OutputItem *compar_items;
static int
item_compar(const void *v1, const void *v2)
{
    const OutputItem &oi1 = compar_items[*((const int *)v1)];
    const OutputItem &oi2 = compar_items[*((const int *)v2)];
    int diff = oi1.pos - oi2.pos;
    if (diff != 0)
	return diff;
    else if (oi1.end_item())
	// Sort end items in reverse order from corresponding start items.
	return oi2.other_index() - oi1.other_index();
    else
	return oi2.other()->pos - oi1.other()->pos;
}
}

static void
prepare_items(int last_pos)
{
    if (items_prepared)
	return;
    items_prepared = true;
    
    add_item(last_pos + 1, "", last_pos + 1, "");
    assert(items.size() == end_items.size());

    // sort items
    for (int which = 0; which < 2; which++) {
	compar_items = (which == 0 ? &items[0] : &end_items[0]);

	Vector<int> permute;
	for (int i = 0; i < items.size(); i++)
	    permute.push_back(i);
	qsort(&permute[0], items.size(), sizeof(int), item_compar);

	Vector<int> rev_permute(items.size(), -1);
	for (int i = 0; i < items.size(); i++)
	    rev_permute[permute[i]] = i;
	
	OutputItem *other_items = (which == 0 ? &end_items[0] : &items[0]);
	for (int i = 0; i < items.size(); i++)
	    other_items[i]._other = rev_permute[other_items[i]._other];

	Vector<OutputItem> new_items(which == 0 ? items : end_items);
	for (int i = 0; i < items.size(); i++)
	    compar_items[i] = new_items[permute[i]];
    }

    // update class references
    for (int i = 0; i < items.size(); i++) {
	OutputItem *s = &items[i], *e = s->other();
	if (s->_type == OutputItem::OI_ECLASS_REF) {
	    if (String href = class_href(s->_u.eclass)) {
		s->text = "<a href='" + href + "'>";
		e->text = "</a>";
	    } else
		s->text = "";
	} else if (s->_type == OutputItem::OI_ELEMENT_REF) {
	    s->text = "<span title='" + s->_u.element->name() + " :: " + s->_u.element->type_name() + "'>";
	}
    }
    
    // combine items that need to be combined (<a href> and <a name>)
    for (int i = 0; i < items.size() - 1; i++) {
	OutputItem *s1 = &items[i], *e1 = s1->other();
	OutputItem *s2 = &items[i+1], *e2 = s2->other();

	if (s1->pos != s2->pos || e1->pos != e2->pos)
	    continue;
	if (s1->text == s2->text && e1->text == e2->text)
	    s2->text = e2->text = "";
	else if (s1->text.substring(0, 3) == "<a "
		 && s2->text.substring(0, 3) == "<a ") {
	    s1->text = s1->text.substring(0, s1->text.length() - 1)
		+ " " + s2->text.substring(3);
	    s2->text = e2->text = "";
	}
    }
}

static String
link_class_decl(ElementClassT *type)
{
    return "decl" + String(cid(type));
}

static String
link_element_decl(ElementT *e)
{
    if (e->router()->declaration_scope())
	return "e" + String(cid(e->router())) + "-" + e->name();
    else
	return "e-" + e->name();
}

class PrettyLexerTInfo : public LexerTInfo { public:

    PrettyLexerTInfo(const String &config)	: _config(config) { }
  
    void add_item(const char *pos1, const String &s1, const char *pos2, const String &s2) {
	::add_item(pos1 - _config.begin(), s1, pos2 - _config.begin(), s2);
    }
    void add_item(const char *pos1, ElementT *e1, const char *pos2, const String &s2) {
	::add_item(pos1 - _config.begin(), e1, pos2 - _config.begin(), s2);
    }
    void add_item(const char *pos1, ElementClassT *e1, const char *pos2, const String &s2) {
	::add_item(pos1 - _config.begin(), e1, pos2 - _config.begin(), s2);
    }
    void notify_comment(const char *pos1, const char *pos2) {
	add_item(pos1, "<span class='c-cmt'>", pos2, "</span>");
    }
    void notify_error(const String &what, const char *pos1, const char *pos2) {
	add_item(pos1, "<span class='c-err' title='" + html_quote_attr(what) + "'>", pos2, "</span>");
    }
    void notify_keyword(const String &, const char *pos1, const char *pos2) {
	add_item(pos1, "<span class='c-kw'>", pos2, "</span>");
    }
    void notify_config_string(const char *pos1, const char *pos2) {
	add_item(pos1, "<span class='c-cfg'>", pos2, "</span>");
    }
    void notify_class_declaration(ElementClassT *ec, bool anonymous, const char *decl_pos1, const char *name_pos1, const char *) {
	if (!anonymous)
	    add_item(name_pos1, "<a name='" + link_class_decl(ec) + "'><span class='c-cd'>", name_pos1 + ec->name().length(), "</span></a>");
	else
	    add_item(decl_pos1, "<a name='" + link_class_decl(ec) + "'>", decl_pos1 + 1, "</a>");
	add_class_href(ec, "#" + link_class_decl(ec));
    }
    void notify_class_extension(ElementClassT *ec, const char *pos1, const char *pos2) {
	add_item(pos1, ec, pos2, "");
    }
    void notify_class_reference(ElementClassT *ec, const char *pos1, const char *pos2) {
	add_item(pos1, ec, pos2, "");
    }
    void notify_element_declaration(ElementT *e, const char *pos1, const char *pos2, const char *decl_pos2) {
	add_item(pos1, "<a name='" + link_element_decl(e) + "'>", pos2, "</a>");
	add_item(pos1, "<span class='c-ed'>", decl_pos2, "</span>");
	notify_element_reference(e, pos1, decl_pos2);
    }
    void notify_element_reference(ElementT *e, const char *pos1, const char *pos2) {
	add_item(pos1, e, pos2, "</span>");
    }

    String _config;

};


void
short_usage()
{
    fprintf(stderr, "Usage: %s [OPTION]... [ROUTERFILE]\n\
Try '%s --help' for more information.\n",
	    program_name, program_name);
}

static RouterT *
pretty_read_router(const char *filename, bool file_is_expr,
		   ErrorHandler *errh, String &config)
{
    // This function is a paraphrase of read_router_file.
  
    // read file string
    int before_nerrors = errh->nerrors();

    if (file_is_expr)
	config = filename;
    else
	config = file_string(filename, errh);
    if (!config && errh->nerrors() != before_nerrors)
	return 0;

    // set readable filename
    if (file_is_expr)
	filename = "<expr>";
    else if (!filename || strcmp(filename, "-") == 0)
	filename = "<stdin>";

    // check for archive
    Vector<ArchiveElement> archive;
    if (config.length() && config[0] == '!') {
	separate_ar_string(config, archive, errh);
	int found = -1;
	for (int i = 0; i < archive.size(); i++)
	    if (archive[i].name == "config")
		found = i;
	if (found >= 0)
	    config = archive[found].data;
	else {
	    errh->error("%s: archive has no 'config' section", filename);
	    config = String();
	}
    }

    // clear list of items
    items.clear();
    end_items.clear();
    items_prepared = false;
  
    // read router
    if (!config.length())
	errh->warning("%s: empty configuration", filename);
    LexerT lexer(ErrorHandler::silent_handler());
    PrettyLexerTInfo pinfo(config);
    lexer.reset(config, filename);
    lexer.set_lexinfo(&pinfo);

    // add archive bits first
    if (lexer.router() && archive.size()) {
	for (int i = 0; i < archive.size(); i++)
	    if (archive[i].live() && archive[i].name != "config")
		lexer.router()->add_archive(archive[i]);
    }

    // read statements
    while (lexer.ystatement())
	/* nada */;

    // done
    return lexer.finish();
}


static void
activate(OutputItem &item, int &first_active)
{
    item.activate(true);
    int iitem = item.item_index();
    if (iitem < first_active)
	first_active = iitem;
}

static void
deactivate(OutputItem &item, int &first_active, int ipos)
{
    item.activate(false);
    int iitem = item.item_index();
    if (iitem == first_active) {
	for (first_active++;
	     first_active < ipos && !items[first_active].active;
	     first_active++)
	    /* nada */;
	if (first_active >= ipos)
	    first_active = items.size();
    }
}

static void
output_config(String r_config, FILE *outf)
{
    // create two sorted lists of objects
    // add sentinel item, sort item lists
    if (!items_prepared)
	prepare_items(r_config.length());

    // loop over characters
    const char *data = r_config.c_str();
    int len = r_config.length();
    int ipos = 0, eipos = 0;
    int first_active = items.size();

    fputs("<pre>", outf);
    for (int pos = 0; pos < len; pos++) {
	while (items[ipos].pos <= pos || end_items[eipos].pos <= pos)
	    if (end_items[eipos].pos <= items[ipos].pos) {
		if (end_items[eipos].active)
		    fputs(end_items[eipos].text.c_str(), outf);
		deactivate(end_items[eipos], first_active, ipos);
		eipos++;
	    } else {
		fputs(items[ipos].text.c_str(), outf);
		activate(items[ipos], first_active);
		ipos++;
	    }

	switch (data[pos]) {

	  case '\n': case '\r':
	    for (int i = ipos - 1; i >= first_active; i--)
		if (items[i].active)
		    fputs(items[i].other()->text.c_str(), outf);
	    fputc('\n', outf);
	    if (data[pos] == '\r' && pos < len - 1 && data[pos+1] == '\n')
		pos++;
	    for (int i = first_active; i < ipos; i++)
		if (items[i].active) {
		    if (items[i].other()->pos <= pos + 1)
			items[i].activate(false);
		    else
			fputs(items[i].text.c_str(), outf);
		}
	    break;

	  case '<':
	    fputs("&lt;", outf);
	    break;

	  case '>':
	    fputs("&gt;", outf);
	    break;

	  case '&':
	    fputs("&amp;", outf);
	    break;

	  default:
	    fputc(data[pos], outf);
	    break;

	}
    }
    fputs("</pre>\n", outf);
}


static bool
parse_columns(const String &s, int &which, int &count)
{
    const char *slash = find(s, '/');
    if (!cp_integer(s.substring(s.begin(), slash), &which)
	|| !cp_integer(s.substring(slash + 1, s.end()), &count)
	|| which <= 0 || which > count) {
	which = count = 1;
	return false;
    } else
	return true;
}

extern "C" {
static const Vector<ConnectionT> *conn_compar_connvec;
static bool conn_compar_from;
static int
conn_compar(const void *v1, const void *v2)
{
    const int *i1 = (const int *)v1, *i2 = (const int *)v2;
    const ConnectionT &c1 = (*conn_compar_connvec)[*i1];
    const ConnectionT &c2 = (*conn_compar_connvec)[*i2];
    const PortT &p1 = (conn_compar_from ? c1.from() : c1.to());
    const PortT &p2 = (conn_compar_from ? c2.from() : c2.to());
    if (p1.element == p2.element)
	return p1.port - p2.port;
    else
	return click_strcmp(p1.element->name(), p2.element->name());
}

static int
element_name_compar(const void *v1, const void *v2)
{
    const ElementT **e1 = (const ElementT **)v1, **e2 = (const ElementT **)v2;
    return click_strcmp((*e1)->name(), (*e2)->name());
}
}

static void
sort_connections(RouterT *r, Vector<int> &conn, bool from)
{
    conn_compar_connvec = &r->connections();
    conn_compar_from = from;
    if (conn.size())
	qsort(&conn[0], conn.size(), sizeof(int), conn_compar);
}


//
// elements
//

static String type_landmark = "$fake_type$";

class ElementsOutput { public:

    ElementsOutput(RouterT *, const ProcessingT &, const HashMap<String, String> &);
    ~ElementsOutput();

    void run(ElementT *, FILE *);
    void run(FILE *);
    
  private:

    RouterT *_router;
    const ProcessingT &_processing;
    const HashMap<String, String> &_main_attrs;
    Vector<ElementT *> _entries;
    Vector<ElementT *> _elements;

    StringAccum _sa;
    String _sep;

    void run_template(String, ElementT *, int, bool);
    String expand(const String &, ElementT *, int, bool);
    
};


ElementsOutput::ElementsOutput(RouterT *r, const ProcessingT &processing, const HashMap<String, String> &main_attrs)
    : _router(r), _processing(processing), _main_attrs(main_attrs)
{
    bool do_elements = main_attrs["entry"];
    bool do_types = main_attrs["typeentry"];

    // get list of elements and/or types
    HashMap<ElementClassT *, int> done_types(-1);
    for (RouterT::iterator x = r->begin_elements(); x; x++) {
	_elements.push_back(x);
	if (do_elements)
	    _entries.push_back(x);
	if (do_types && done_types[x->type()] < 0) {
	    ElementT *fake = new ElementT(x->type_name(), x->type(), "", type_landmark);
	    _entries.push_back(fake);
	    done_types.insert(x->type(), 1);
	}
    }

    // sort by name
    if (_elements.size())
	qsort(&_elements[0], _elements.size(), sizeof(ElementT *), element_name_compar);
    if (_entries.size())
	qsort(&_entries[0], _entries.size(), sizeof(ElementT *), element_name_compar);
}

ElementsOutput::~ElementsOutput()
{
    for (int i = 0; i < _entries.size(); i++)
	if (_entries[i]->landmark() == type_landmark)
	    delete _entries[i];
}

void
ElementsOutput::run_template(String templ_str, ElementT *e, int port, bool is_output)
{
    ElementClassT *t = e->type();
    bool is_type = (e->landmark() == type_landmark);
    
    String tag;
    HashMap<String, String> attrs;
    const char *templ = templ_str.c_str();

    while (templ) {
	templ = output_template_until_tag(templ, _sa, tag, attrs, true, &_sep);

	String next_sep;
	int pre_expansion_pos = _sa.length();
	
	if (tag == "name") {
	    String href, link = attrs["link"].lower();
	    if (link == "type" || (is_type && link))
		href = class_href(t);
	    else if (link)
		href = link_element_decl(e);
	    if (href)
		_sa << _sep << "<a href='" << href << "'>" << e->name() << "</a>";
	    else
		_sa << _sep << e->name();
	} else if (tag == "type") {
	    String href = (attrs["link"] ? class_href(t) : String());
	    if (href)
		_sa << _sep << "<a href='" << href << "'>" << t->name() << "</a>";
	    else
		_sa << _sep << t->name();
	} else if (tag == "config" && !is_type) {
	    int limit = 0;
	    if (attrs["limit"])
		cp_integer(html_unquote(attrs["limit"]), &limit);
	    String config = e->configuration();
	    if (limit && config.length() > limit)
		config = config.substring(0, limit) + "...";
	    if (config && attrs["parens"])
		_sa << _sep << '(' << html_quote_text(config) << ')';
	    else if (config)
		_sa << _sep << html_quote_text(config);
	} else if (tag == "typerefs") {
	    String subsep = attrs["sep"];
	    String text = attrs["entry"];
	    if (!text)
		text = _main_attrs["typeref"];
	    for (int i = 0; i < _elements.size(); i++)
		if (_elements[i]->type() == t) {
		    run_template(text, _elements[i], -1, false);
		    _sep = subsep;
		}
	} else if (tag == "configlink" && !is_type) {
	    String text = attrs["text"];
	    if (!text)
		text = _main_attrs["configlink"];
	    if (text) {
		text = "<a href='#" + link_element_decl(e) + "'>" + text + "</a>";
		run_template(text, e, port, is_output);
		next_sep = attrs["sep"];
	    }
	} else if (tag == "ninputs" && !is_type) {
	    _sa << _sep << e->ninputs();
	    if (attrs["english"])
		_sa << (e->ninputs() == 1 ? " input" : " inputs");
	} else if (tag == "noutputs" && !is_type) {
	    _sa << _sep << e->noutputs();
	    if (attrs["english"])
		_sa << (e->noutputs() == 1 ? " output" : " outputs");
	} else if (tag == "inputs" && !is_type) {
	    if (e->ninputs() == 0) {
		String text = attrs["noentry"];
		if (!text)
		    text = _main_attrs["noinputentry"];
		run_template(text, e, -1, false);
	    } else {
		String subsep = attrs["sep"];
		String text = attrs["entry"];
		if (!text)
		    text = _main_attrs["inputentry"];
		for (int i = 0; i < e->ninputs(); i++) {
		    run_template(text, e, i, false);
		    _sep = subsep;
		}
	    }
	} else if (tag == "outputs" && !is_type) {
	    if (e->noutputs() == 0) {
		String text = attrs["noentry"];
		if (!text)
		    text = _main_attrs["nooutputentry"];
		run_template(text, e, -1, true);
	    } else {
		String subsep = attrs["sep"];
		String text = attrs["entry"];
		if (!text)
		    text = _main_attrs["outputentry"];
		for (int i = 0; i < e->noutputs(); i++) {
		    run_template(text, e, i, true);
		    _sep = subsep;
		}
	    }
	} else if (tag == "inputconnections" && port >= 0 && !is_output) {
	    Vector<int> conn;
	    _router->find_connections_to(PortT(e, port), conn);
	    if (conn.size() == 0) {
		String text = attrs["noentry"];
		if (!text)
		    text = _main_attrs["noinputconnection"];
		run_template(text, e, port, false);
	    } else {
		sort_connections(_router, conn, true);
		String subsep = attrs["sep"];
		String text = attrs["entry"];
		if (!text)
		    text = _main_attrs["inputconnection"];
		for (int i = 0; i < conn.size(); i++) {
		    const ConnectionT &c = _router->connection(conn[i]);
		    run_template(text, c.from_element(), c.from_port(), true);
		    _sep = subsep;
		}
	    }
	} else if (tag == "outputconnections" && port >= 0 && is_output) {
	    Vector<int> conn;
	    _router->find_connections_from(PortT(e, port), conn);
	    if (conn.size() == 0) {
		String text = attrs["noentry"];
		if (!text)
		    text = _main_attrs["nooutputconnection"];
		run_template(text, e, port, true);
	    } else {
		sort_connections(_router, conn, false);
		String subsep = attrs["sep"];
		String text = attrs["entry"];
		if (!text)
		    text = _main_attrs["outputconnection"];
		for (int i = 0; i < conn.size(); i++) {
		    const ConnectionT &c = _router->connection(conn[i]);
		    run_template(text, c.to_element(), c.to_port(), false);
		    _sep = subsep;
		}
	    }
	} else if (tag == "port" && port >= 0) {
	    _sa << _sep << port;
	} else if (tag == "processing" && port >= 0) {
	    int p = (is_output ? _processing.output_processing(PortT(e, port))
		     : _processing.input_processing(PortT(e, port)));
	    if (p == ProcessingT::VAGNOSTIC)
		_sa << _sep << "agnostic";
	    else if (p == ProcessingT::VPUSH)
		_sa << _sep << "push";
	    else if (p == ProcessingT::VPULL)
		_sa << _sep << "pull";
	    else
		_sa << _sep << "??";
	} else if (tag == "processingcode") {
	    _sa << _sep << t->processing_code();
	} else if (tag == "flowcode") {
	    _sa << _sep << t->flow_code();
	} else if (tag == "if") {
	    String s = expand(attrs["test"], e, port, is_output);
	    bool result;
	    if (attrs["eq"])
		result = (expand(attrs["eq"], e, port, is_output) == s);
	    else if (attrs["ne"])
		result = (expand(attrs["ne"], e, port, is_output) != s);
	    else if (attrs["gt"])
		result = (click_strcmp(s, expand(attrs["gt"], e, port, is_output)) > 0);
	    else if (attrs["lt"])
		result = (click_strcmp(s, expand(attrs["lt"], e, port, is_output)) < 0);
	    else if (attrs["ge"])
		result = (click_strcmp(s, expand(attrs["ge"], e, port, is_output)) >= 0);
	    else if (attrs["le"])
		result = (click_strcmp(s, expand(attrs["le"], e, port, is_output)) <= 0);
	    else
		result = (s.length() > 0);

	    if (result)
		run_template(attrs["then"], e, port, is_output);
	    else
		run_template(attrs["else"], e, port, is_output);
	} else if (_main_attrs[tag]) {
	    String text = attrs[tag];
	    run_template(text, e, port, is_output);
	} else if (definitions[tag]) {
	    String text = definitions[tag];
	    run_template(text, e, port, is_output);
	}

	if (_sa.length() != pre_expansion_pos)
	    _sep = next_sep;
    }
}

String
ElementsOutput::expand(const String &s, ElementT *e, int port, bool is_output)
{
    int pos = _sa.length();
    run_template(s, e, port, is_output);
    String result(_sa.data() + pos, _sa.length() - pos);
    _sa.pop_back(_sa.length() - pos);
    return result;
}

void
ElementsOutput::run(ElementT *e, FILE *f)
{
    bool is_type = e->landmark() == type_landmark;
    String templ = _main_attrs[is_type ? "typeentry" : "entry"];
    run_template(templ, e, -1, false);
    fputs(_sa.c_str(), f);
    _sa.clear();
    _sep = _main_attrs["sep"];
}

void
ElementsOutput::run(FILE *f)
{
    // divide into columns
    int which_col, ncol;
    parse_columns(_main_attrs["column"], which_col, ncol);
    int per_col = ((_entries.size() - 1) / ncol) + 1;
    int first = (which_col - 1) * per_col;
    int last = which_col * per_col;
    if (which_col == ncol || last > _entries.size())
	last = _entries.size();

    // actually do output
    for (int i = first; i < last; i++)
	run(_entries[i], f);
}


//
// main loop
//

static void
run_template(const char *templ, RouterT *r, const String &r_config,
	     const ElementMap &emap, const ProcessingT &processing, FILE *outf)
{
    String tag;
    HashMap<String, String> attrs;

    while (templ) {
	templ = output_template_until_tag(templ, outf, tag, attrs, false);

	if (tag == "config")
	    output_config(r_config, outf);
	else if (tag == "elements") {
	    ElementsOutput eo(r, processing, attrs);
	    eo.run(outf);
	} else if (definitions[tag]) {
	    String text = definitions[tag];
	    run_template(text.c_str(), r, r_config, emap, processing, outf);
	}
    }
}

static FILE *
open_output_file(const char *outfile, ErrorHandler *errh)
{
    FILE *outf = stdout;
    if (outfile && strcmp(outfile, "-") != 0) {
	outf = fopen(outfile, "w");
	if (!outf)
	    errh->error("%s: %s", outfile, strerror(errno));
    }
    return outf;
}

static void
pretty_process(const char *infile, bool file_is_expr, const char *outfile,
	       const char *templ, ErrorHandler *errh)
{
    String r_config;
    RouterT *r = pretty_read_router(infile, file_is_expr, errh, r_config);
    if (!r)
	return;

    // open output file
    FILE *outf = open_output_file(outfile, errh);
    if (!outf) {
	delete r;
	return;
    }

    // get element map and processing
    ElementMap emap;
    emap.parse_all_files(r, CLICK_DATADIR, errh);

    int driver = specified_driver;
    if (driver < 0) {
	int driver_mask = 0;
	for (int d = 0; d < Driver::COUNT; d++)
	    if (emap.driver_compatible(r, d))
		driver_mask |= 1 << d;
	if (driver_mask == 0)
	    errh->warning("configuration not compatible with any driver");
	else {
	    for (int d = Driver::COUNT - 1; d >= 0; d--)
		if (driver_mask & (1 << d))
		    driver = d;
	    // don't complain if only a single driver works
	    if ((driver_mask & (driver_mask - 1)) != 0
		&& !emap.driver_indifferent(r, driver_mask, errh))
		errh->warning("configuration not indifferent to driver, picking %s\n(You might want to specify a driver explicitly.)", Driver::name(driver));
	}
    } else if (!emap.driver_compatible(r, driver))
	errh->warning("configuration not compatible with %s driver", Driver::name(driver));

    emap.set_driver(driver);
    ProcessingT processing(r, &emap, errh);

    ElementMap::push_default(&emap);
    
    // process template
    run_template(templ, r, r_config, emap, processing, outf);

    ElementMap::pop_default();
    
    // close files, return
    if (outf != stdout)
	fclose(outf);
    delete r;
}

void
usage()
{
    printf("\
'Click-pretty' reads a Click router configuration and outputs an HTML file,\n\
based on a template, showing that configuration with syntax highlighting.\n\
\n\
Usage: %s [OPTION]... [ROUTERFILE]\n\
\n\
Options:\n\
  -f, --file FILE             Read router configuration from FILE.\n\
  -e, --expression EXPR       Use EXPR as router configuration.\n\
  -o, --output FILE           Write HTML output to FILE.\n\
  -t, --template FILE         Use FILE as the template instead of default.\n\
  -d, --define NAME=TEXT      Define a new tag, NAME, that expands to TEXT.\n\
  -u, --class-docs URL        Link primitive element classes to URL.\n\
      --package-docs PKG=URL  Link element classes in package PKG to URL.\n\
      --write-template        Write template as is, without including router.\n\
  -C, --clickpath PATH        Use PATH for CLICKPATH.\n\
      --help                  Print this message and exit.\n\
  -v, --version               Print version number and exit.\n\
\n\
Report bugs to <click@pdos.lcs.mit.edu>.\n", program_name);
}

int
main(int argc, char **argv)
{
    click_static_initialize();
    CLICK_DEFAULT_PROVIDES;
    ErrorHandler *errh = ErrorHandler::default_handler();
    ErrorHandler *p_errh = new PrefixErrorHandler(errh, "click-pretty: ");

    // read command line arguments
    Clp_Parser *clp =
	Clp_NewParser(argc, argv, sizeof(options)/sizeof(options[0]), options);
    Clp_SetOptionChar(clp, '+', Clp_ShortNegated);
    program_name = Clp_ProgramName(clp);

    const char *router_file = 0;
    bool file_is_expr = false;
    const char *output_file = 0;
    String html_template = default_template;
    bool write_template = false;

    while (1) {
	int opt = Clp_Next(clp);
	switch (opt) {

	  case HELP_OPT:
	    usage();
	    exit(0);
	    break;

	  case VERSION_OPT:
	    printf("click-pretty (Click) %s\n", CLICK_VERSION);
	    printf("Copyright (c) 2001-2002 International Computer Science Institute\n\
This is free software; see the source for copying conditions.\n\
There is NO warranty, not even for merchantability or fitness for a\n\
particular purpose.\n");
	    exit(0);
	    break;

	  case CLICKPATH_OPT:
	    set_clickpath(clp->arg);
	    break;

	  case CLASS_URLS_OPT:
	    package_hrefs.insert("x", clp->arg);
	    break;

	  case PACKAGE_URLS_OPT: {
	      String s = clp->arg;
	      const char *equals = find(s, '=');
	      if (equals == s.end()) {
		  p_errh->error("'--package-urls' option must contain an equals sign");
		  goto bad_option;
	      }
	      package_hrefs.insert("x" + s.substring(s.begin(), equals), s.substring(equals + 1, s.end()));
	      break;
	  }

	  case TEMPLATE_OPT:
	    html_template = file_string(clp->arg, p_errh);
	    break;

	  case DEFINE_OPT: {
	      String s = clp->arg;
	      const char *equals = find(s, '=');
	      if (equals < s.end())
		  definitions.insert(s.substring(s.begin(), equals), s.substring(equals + 1, s.end()));
	      else
		  definitions.insert(s, "");
	      break;
	  }

	  case WRITE_TEMPLATE_OPT:
	    write_template = !clp->negated;
	    break;

	  case ROUTER_OPT:
	  case EXPRESSION_OPT:
	  case Clp_NotOption:
	    if (router_file) {
		p_errh->error("router configuration specified twice");
		goto bad_option;
	    }
	    router_file = clp->arg;
	    file_is_expr = (opt == EXPRESSION_OPT);
	    break;

	  case OUTPUT_OPT:
	    if (output_file) {
		p_errh->error("output file specified twice");
		goto bad_option;
	    }
	    output_file = clp->arg;
	    break;

	  case USERLEVEL_OPT:
	  case LINUXMODULE_OPT:
	  case BSDMODULE_OPT:
	    if (specified_driver >= 0) {
		p_errh->error("driver specified twice");
		goto bad_option;
	    }
	    specified_driver = opt - FIRST_DRIVER_OPT;
	    break;

	  bad_option:
	  case Clp_BadOption:
	    short_usage();
	    exit(1);
	    break;

	  case Clp_Done:
	    goto done;

	}
    }

  done:
    if (write_template) {
	if (FILE *f = open_output_file(output_file, errh)) {
	    fputs(html_template.c_str(), f);
	    fclose(f);
	}
    } else
	pretty_process(router_file, file_is_expr, output_file, html_template.c_str(), errh);
	
    exit(errh->nerrors() > 0 ? 1 : 0);
}

#include <click/vector.cc>
#include <click/hashmap.cc>


syntax highlighted by Code2HTML, v. 0.9.1