/*
 * click-fastclassifier.cc -- specialize Click classifiers
 * Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2000-2001 Mazu Networks, Inc.
 * Copyright (c) 2001 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 <click/error.hh>
#include <click/confparse.hh>
#include <click/straccum.hh>
#include <click/clp.h>
#include <click/driver.hh>
#include "toolutils.hh"
#include "elementmap.hh"
#include "click-fastclassifier.hh"
#include "md5.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdarg.h>

#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 KERNEL_OPT		306
#define USERLEVEL_OPT		307
#define SOURCE_OPT		308
#define CONFIG_OPT		309
#define REVERSE_OPT		310
#define COMBINE_OPT		311
#define COMPILE_OPT		312
#define QUIET_OPT		313
#define VERBOSE_OPT		314

static Clp_Option options[] = {
  { "classes", 0, COMPILE_OPT, 0, Clp_Negate },
  { "clickpath", 'C', CLICKPATH_OPT, Clp_ArgString, 0 },
  { "combine", 0, COMBINE_OPT, 0, Clp_Negate },
  { "config", 'c', CONFIG_OPT, 0, Clp_Negate },
  { "expression", 'e', EXPRESSION_OPT, Clp_ArgString, 0 },
  { "file", 'f', ROUTER_OPT, Clp_ArgString, 0 },
  { "help", 0, HELP_OPT, 0, 0 },
  { "kernel", 'k', KERNEL_OPT, 0, 0 },
  { "output", 'o', OUTPUT_OPT, Clp_ArgString, 0 },
  { "quiet", 'q', QUIET_OPT, 0, Clp_Negate },
  { "reverse", 'r', REVERSE_OPT, 0, Clp_Negate },
  { "source", 's', SOURCE_OPT, 0, Clp_Negate },
  { "user", 'u', USERLEVEL_OPT, 0, 0 },
  { "verbose", 'V', VERBOSE_OPT, 0, Clp_Negate },
  { "version", 'v', VERSION_OPT, 0, 0 }
};

static const char *program_name;
static String::Initializer string_initializer;
static String runclick_prog;
static String click_buildtool_prog;
static String quiet_arg;
static bool verbose;

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

void
usage()
{
  printf("\
'Click-fastclassifier' transforms a router configuration by replacing generic\n\
Classifier elements with specific generated code. The resulting configuration\n\
has both Click-language files and object files.\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 output to FILE.\n\
      --no-combine              Do not combine adjacent Classifiers.\n\
      --no-classes              Do not generate FastClassifier elements.\n\
  -k, --kernel                  Compile into Linux kernel binary package.\n\
  -u, --user                    Compile into user-level binary package.\n\
  -s, --source                  Write source code only.\n\
  -c, --config                  Write new configuration only.\n\
  -r, --reverse                 Reverse transformation.\n\
  -q, --quiet                   Compile any packages quietly.\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);
}


// Classifier related stuff

static bool
combine_classifiers(RouterT *router, ElementT *from, int from_port, ElementT *to)
{
  ElementClassT *classifier_t = ElementClassT::base_type("Classifier");
  assert(from->type() == classifier_t && to->type() == classifier_t);
  
  // find where 'to' is heading for
  Vector<int> first_hop, second_hop;
  router->find_connection_vector_from(from, first_hop);
  router->find_connection_vector_from(to, second_hop);

  // check for weird configurations
  for (int i = 0; i < first_hop.size(); i++)
    if (first_hop[i] < 0)
      return false;
  for (int i = 0; i < second_hop.size(); i++)
    if (second_hop[i] < 0)
      return false;
  if (second_hop.size() == 0)
    return false;

  // combine configurations
  Vector<String> from_words, to_words;
  cp_argvec(from->configuration(), from_words);
  cp_argvec(to->configuration(), to_words);
  if (from_words.size() != first_hop.size()
      || to_words.size() != second_hop.size())
    return false;
  Vector<String> new_words;
  for (int i = 0; i < from_port; i++)
    new_words.push_back(from_words[i]);
  for (int i = 0; i < to_words.size(); i++)
    if (to_words[i] == "-")
      new_words.push_back(from_words[from_port]);
    else if (from_words[from_port] == "-")
      new_words.push_back(to_words[i]);
    else
      new_words.push_back(from_words[from_port] + " " + to_words[i]);
  for (int i = from_port + 1; i < from_words.size(); i++)
    new_words.push_back(from_words[i]);
  from->configuration() = cp_unargvec(new_words);

  // change connections
  router->kill_connection(first_hop[from_port]);
  for (int i = from_port + 1; i < first_hop.size(); i++)
    router->change_connection_from(first_hop[i], PortT(from, i + to_words.size() - 1));
  const Vector<ConnectionT> &conn = router->connections();
  for (int i = 0; i < second_hop.size(); i++)
    router->add_connection(PortT(from, from_port + i), conn[second_hop[i]].to());

  return true;
}

static bool
try_combine_classifiers(RouterT *router, ElementT *classifier)
{
  ElementClassT *classifier_t = ElementClassT::base_type("Classifier");
  if (classifier->type() != classifier_t)
    // cannot combine IPClassifiers yet
    return false;

  Vector<PortT> branches;
  router->find_connections_to(PortT(classifier, 0), branches);
  for (int i = 0; i < branches.size(); i++)
    if (branches[i].element->type() == classifier_t) {
      // perform a combination
      if (combine_classifiers(router, branches[i].element, branches[i].port, classifier)) {
	try_combine_classifiers(router, classifier);
	return true;
      }
    }

  return false;
}

static void
try_remove_classifiers(RouterT *router, Vector<ElementT *> &classifiers)
{
  for (int i = 0; i < classifiers.size(); i++) {
    Vector<PortT> v;
    router->find_connections_to(PortT(classifiers[i], 0), v);
    if (v.size() == 0) {
      classifiers[i]->kill();
      classifiers[i] = classifiers.back();
      classifiers.pop_back();
      i--;
    }
  }

  router->remove_dead_elements();
  router->compact_connections();
}


/*
 * FastClassifier structures
 */

const String &
Classifier_Program::handler_value(const String &name) const
{
  for (int i = 0; i < handler_names.size(); i++)
    if (handler_names[i] == name)
      return handler_values[i];
  return String::empty_string();
}

bool
operator!=(const Classifier_Insn &s1, const Classifier_Insn &s2)
{
  return (s1.yes != s2.yes
	  || s1.no != s2.no
	  || s1.offset != s2.offset
	  || s1.mask.u != s2.mask.u
	  || s1.value.u != s2.value.u);
}

bool
operator==(const Classifier_Insn &s1, const Classifier_Insn &s2)
{
  return !(s1 != s2);
}

bool
operator==(const Classifier_Program &c1, const Classifier_Program &c2)
{
  if (c1.type != c2.type
      || c1.safe_length != c2.safe_length
      || c1.output_everything != c2.output_everything
      || c1.noutputs != c2.noutputs
      || c1.align_offset != c2.align_offset
      || c1.program.size() != c2.program.size())
    return false;
  for (int i = 0; i < c1.program.size(); i++)
    if (c1.program[i] != c2.program[i])
      return false;
  if (c1.handler_names.size() != c2.handler_names.size())
    return false;
  for (int i = 0; i < c1.handler_names.size(); i++)
    if (c1.handler_values[i] != c2.handler_value(c1.handler_names[i]))
      return false;
  return true;
}

bool
operator!=(const Classifier_Program &c1, const Classifier_Program &c2)
{
  return !(c1 == c2);
}


/*
 * registering CIDs
 */

struct FastClassifier_Cid {
  String name;
  int guaranteed_packet_length;
  void (*headers)(const Classifier_Program &, StringAccum &);
  void (*checked_body)(const Classifier_Program &, StringAccum &);
  void (*unchecked_body)(const Classifier_Program &, StringAccum &);
  void (*push_body)(const Classifier_Program &, StringAccum &);
};

static HashMap<String, int> cid_name_map(-1);
static Vector<FastClassifier_Cid *> cids;
static Vector<String> interesting_handler_names;

int
add_classifier_type(const String &name, int guaranteed_packet_length,
	void (*headers)(const Classifier_Program &, StringAccum &),
	void (*checked_body)(const Classifier_Program &, StringAccum &),
	void (*unchecked_body)(const Classifier_Program &, StringAccum &),
	void (*push_body)(const Classifier_Program &, StringAccum &))
{
  FastClassifier_Cid *cid = new FastClassifier_Cid;
  cid->name = name;
  cid->guaranteed_packet_length = guaranteed_packet_length;
  cid->headers = headers;
  cid->checked_body = checked_body;
  cid->unchecked_body = unchecked_body;
  cid->push_body = push_body;
  cids.push_back(cid);
  cid_name_map.insert(cid->name, cids.size() - 1);
  return cids.size() - 1;
}

void
add_interesting_handler(const String &name)
{
  interesting_handler_names.push_back(name);
}


/*
 * translating Classifiers
 */

static String
translate_class_name(const String &s)
{
  StringAccum sa;
  for (int i = 0; i < s.length(); i++)
    if (s[i] == '_')
      sa << "_u";
    else if (s[i] == '@')
      sa << "_a";
    else if (s[i] == '/')
      sa << "_s";
    else
      sa << s[i];
  return sa.take_string();
}

static Vector<String> gen_eclass_names;
static Vector<String> gen_cxxclass_names;
static Vector<String> old_configurations;

static Vector<int> program_map;
static Vector<Classifier_Program> all_programs;

static void
change_landmark(ElementT *e)
{
  int colon = e->landmark().find_right(':');
  if (colon >= 0)
    e->set_landmark(e->landmark().substring(0, colon) + "<click-fastclassifier>" + e->landmark().substring(colon));
  else
    e->set_landmark(e->landmark() + "<click-fastclassifier>");
}

static void
copy_elements(RouterT *oldr, RouterT *newr, ElementClassT *type)
{
  if (type)
    for (RouterT::type_iterator x = oldr->begin_elements(type); x; x++)
      newr->get_element(x->name(), type, x->configuration(), "");
}

static RouterT *
classifiers_program(RouterT *r, const Vector<ElementT *> &classifiers)
{
    RouterT *nr = new RouterT;
    
    ElementT *idle = nr->add_anon_element(ElementClassT::base_type("Idle"));
    const Vector<String> &old_requirements = r->requirements();
    for (int i = 0; i < old_requirements.size(); i++)
	nr->add_requirement(old_requirements[i]);
  
    // copy AlignmentInfos and AddressInfos
    copy_elements(r, nr, ElementClassT::base_type("AlignmentInfo"));
    copy_elements(r, nr, ElementClassT::base_type("AddressInfo"));

    // copy all classifiers
    for (int i = 0; i < classifiers.size(); i++) {
	ElementT *c = classifiers[i];
    
	// add new classifier and connections to idle
	ElementT *nc = nr->get_element(c->name(), c->type(), c->configuration(), c->landmark());
  
	nr->add_connection(idle, i, nc, 0);
	// count number of output ports
	int noutputs = c->noutputs();
	for (int j = 0; j < noutputs; j++)
	    nr->add_connection(nc, j, idle, 0);
    }

    return nr;
}

static void
analyze_classifiers(RouterT *nr, const Vector<ElementT *> &classifiers,
		    ErrorHandler *errh)
{
  // get classifiers
  HashMap<String, int> classifier_map(-1);
  Vector<Classifier_Program> iprograms;
  for (int i = 0; i < classifiers.size(); i++) {
    classifier_map.insert(classifiers[i]->name(), i);
    iprograms.push_back(Classifier_Program());
  }

  // read the relevant handlers from user-level 'click'
  String handler_text;
  {
    StringAccum cmd_sa;
    cmd_sa << runclick_prog;
    for (int i = 0; i < interesting_handler_names.size(); i++)
      cmd_sa << " -h '*." << interesting_handler_names[i] << "'";
    cmd_sa << " -q";
    if (verbose)
      errh->message("Running command '%s' on configuration:\n%s", cmd_sa.c_str(), nr->configuration_string().c_str());
    handler_text = shell_command_output_string(cmd_sa.take_string(), nr->configuration_string(), errh);
  }

  // assign handlers to programs; assume handler results contain no par breaks
  {
    const char *s = handler_text.data();
    int len = handler_text.length();
    int pos = 0;
    String ename, hname, hvalue;
    while (pos < len) {
      // read element name
      int pos1 = pos;
      while (pos1 < len && s[pos1] != '.' && !isspace(s[pos1]))
	pos1++;
      ename = handler_text.substring(pos, pos1 - pos);
      bool ok = false;
      
      // read handler name
      if (pos1 < len && s[pos1] == '.') {
	pos1 = pos = pos1 + 1;
	while (pos1 < len && s[pos1] != ':' && !isspace(s[pos1]))
	  pos1++;
	hname = handler_text.substring(pos, pos1 - pos);
	
	// skip to EOL; data is good
	if (pos1 < len && s[pos1] == ':') {
	  for (pos1++; pos1 < len && s[pos1]!='\n' && s[pos1]!='\r'; pos1++)
	    /* nada */;
	  if (pos1 < len - 1 && s[pos1] == '\r' && s[pos1+1] == '\n')
	    pos1++;
	  pos1++;
	  ok = true;
	}
      }
      
      // skip to paragraph break
      int last_line_start = pos1;
      for (pos = pos1; pos1 < len; pos1++)
	if (s[pos1] == '\r' || s[pos1] == '\n') {
	  bool done = (pos1 == last_line_start);
	  if (pos1 < len - 1 && s[pos1] == '\r' && s[pos1+1] == '\n')
	    pos1++;
	  last_line_start = pos1 + 1; // loop will add 1 to pos1
	  if (done)
	    break;
	}
      hvalue = handler_text.substring(pos, pos1 - pos);

      // skip remaining whitespace
      for (pos = pos1; pos < len && isspace(s[pos]); pos++)
	/* nada */;

      // assign value to program if appropriate
      int prog_index = (ok ? classifier_map[ename] : -1);
      if (prog_index >= 0) {
	iprograms[prog_index].handler_names.push_back(hname);
	iprograms[prog_index].handler_values.push_back(hvalue);
      }
    }
  }

  // now parse each program
  for (int ci = 0; ci < iprograms.size(); ci++) {
    // check if valid handler
    String program = iprograms[ci].handler_value("program");
    if (!program) {
      program_map.push_back(-1);
      continue;
    }
    ElementT *c = classifiers[ci];

    // yes: valid handler; now parse program
    Classifier_Program &prog = iprograms[ci];
    String classifier_tname = c->type_name();
    prog.type = cid_name_map[classifier_tname];
    assert(prog.type >= 0);
    
    prog.safe_length = prog.output_everything = prog.align_offset = -1;
    prog.noutputs = c->noutputs();
    while (program) {
      // find step
      String step = program.substring(program.begin(), find(program, '\n'));
      program = program.substring(step.end() + 1, program.end());
      // check for many things
      if (isdigit(step[0]) || isspace(step[0])) {
	// real step
	Classifier_Insn e;
	int crap, pos;
	int v[4], m[4];
	sscanf(step.c_str(), "%d %d/%2x%2x%2x%2x%%%2x%2x%2x%2x yes->%n",
	       &crap, &e.offset, &v[0], &v[1], &v[2], &v[3],
	       &m[0], &m[1], &m[2], &m[3], &pos);
	for (int i = 0; i < 4; i++) {
	  e.value.c[i] = v[i];
	  e.mask.c[i] = m[i];
	}
	// read yes destination
	step = step.substring(pos);
	if (step[0] == '[') {
	  sscanf(step.c_str(), "[%d] no->%n", &e.yes, &pos);
	  e.yes = -e.yes;
	} else
	  sscanf(step.c_str(), "step %d no->%n", &e.yes, &pos);
	// read no destination
	step = step.substring(pos);
	if (step[0] == '[') {
	  sscanf(step.c_str(), "[%d]", &e.no);
	  e.no = -e.no;
	} else
	  sscanf(step.c_str(), "step %d", &e.no);
	// push expr onto list
	prog.program.push_back(e);
      } else if (sscanf(step.c_str(), "all->[%d]", &prog.output_everything))
	/* nada */;
      else if (sscanf(step.c_str(), "safe length %d", &prog.safe_length))
	/* nada */;
      else if (sscanf(step.c_str(), "alignment offset %d", &prog.align_offset))
	/* nada */;
    }

    // search for an existing fast classifier with the same program
    bool found_program = false;
    for (int i = 0; i < all_programs.size() && !found_program; i++)
      if (prog == all_programs[i]) {
	program_map.push_back(i);
	found_program = true;
      }

    if (!found_program) {
      // set new names
      String class_name = "Fast" + classifier_tname + "@@" + c->name();
      String cxx_name = translate_class_name(class_name);
      prog.eclass = ElementClassT::base_type(class_name);
      
      // add new program
      all_programs.push_back(prog);
      gen_eclass_names.push_back(class_name);
      gen_cxxclass_names.push_back(cxx_name);
      old_configurations.push_back(c->configuration());
      program_map.push_back(all_programs.size() - 1);
    }
  }

  // complain if any programs missing
  for (int i = 0; i < iprograms.size(); i++)
    if (program_map[i] < 0)
      errh->fatal("classifier program missing for '%s :: %s'!", classifiers[i]->name_c_str(), classifiers[i]->type_name().c_str());
}

static void
output_classifier_program(int which,
			  StringAccum &header, StringAccum &source,
			  ErrorHandler *)
{
  String cxx_name = gen_cxxclass_names[which];
  String class_name = gen_eclass_names[which];
  const Classifier_Program &prog = all_programs[which];
  FastClassifier_Cid *cid = cids[prog.type];
  if (cid->headers) {
    cid->headers(prog, source);
    cid->headers = 0;		// only call cid->headers once
  }
  
  header << "class " << cxx_name << " : public Element {\n\
  void devirtualize_all() { }\n\
 public:\n  "
	 << cxx_name << "() { }\n  ~" << cxx_name << "() { }\n\
  const char *class_name() const { return \"" << class_name << "\"; }\n\
  const char *port_count() const { return \"1/" << prog.noutputs << "\"; }\n\
  const char *processing() const { return PUSH; }\n";

  if (prog.output_everything >= 0) {
    header << "  void push(int, Packet *);\n};\n";
    source << "void\n" << cxx_name << "::push(int, Packet *p)\n{\n";
    if (prog.output_everything < prog.noutputs)
      source << "  output(" << prog.output_everything << ").push(p);\n";
    else
      source << "  p->kill();\n";
    source << "}\n";
  } else {
    bool need_checked = (prog.safe_length >= cid->guaranteed_packet_length);
    if (need_checked) {
      header << "  void length_checked_push(Packet *);\n";
      source << "void\n" << cxx_name << "::length_checked_push(Packet *p)\n{\n";
      cid->checked_body(prog, source);
      source << "}\n";
    }
    
    header << "  inline void length_unchecked_push(Packet *);\n\
  void push(int, Packet *);\n};\n";
    source << "inline void\n" << cxx_name
	   << "::length_unchecked_push(Packet *p)\n{\n";
    cid->unchecked_body(prog, source);
    source << "}\n";

    source << "void\n" << cxx_name << "::push(int, Packet *p)\n{\n";
    cid->push_body(prog, source);
    source << "}\n";
  }
}


static void
compile_classifiers(RouterT *r, const String &package_name,
		    RouterT *nr, Vector<ElementT *> &classifiers,
		    int compile_drivers, ErrorHandler *errh)
{
    // create C++ files
    StringAccum header, source, source_body;
    header << "#ifndef CLICK_" << package_name << "_HH\n"
	   << "#define CLICK_" << package_name << "_HH\n"
	   << "#include <click/package.hh>\n#include <click/element.hh>\n";

    // analyze Classifiers into programs
    analyze_classifiers(nr, classifiers, errh);

    // add requirement
    r->add_requirement(package_name);

    // write Classifier programs
    for (int i = 0; i < all_programs.size(); i++)
	output_classifier_program(i, header, source_body, errh);

    // change element landmarks and types
    for (int i = 0; i < classifiers.size(); i++) {
	ElementT *classifier_e = classifiers[i];
	const Classifier_Program &prog = all_programs[program_map[i]];
	classifier_e->set_type(prog.eclass);
	classifier_e->set_configuration(String());
	change_landmark(classifier_e);
    }
  
    // write final text
    header << "#endif\n";
    source << "/** click-compile: -w */\n";
    {
	StringAccum elem2package, cmd_sa;
	int nclasses = gen_cxxclass_names.size();
	for (int i = 0; i < nclasses; i++)
	    elem2package <<  "-\t\"" << package_name << ".hh\"\t" << gen_cxxclass_names[i] << '-' << gen_eclass_names[i] << '\n';
	cmd_sa << click_buildtool_prog << " elem2package " << package_name;
	source << shell_command_output_string(cmd_sa.take_string(), elem2package.take_string(), errh);
    }
    source << "CLICK_DECLS\n" << source_body << "CLICK_ENDDECLS\n";

    // compile files if required
    String tmpdir;
  
    if (compile_drivers) {
	// create temporary directory
	if (!(tmpdir = click_mktmpdir(errh)))
	    exit(1);
    
	String filename = tmpdir + package_name + ".hh";
	FILE *f = fopen(filename.c_str(), "w");
	if (!f)
	    errh->fatal("%s: %s", filename.c_str(), strerror(errno));
	fwrite(header.data(), 1, header.length(), f);
	fclose(f);

	String cxx_filename = package_name + "_.cc";
	f = fopen((tmpdir + cxx_filename).c_str(), "w");
	if (!f)
	    errh->fatal("%s%s: %s", tmpdir.c_str(), cxx_filename.c_str(), strerror(errno));
	fwrite(source.data(), 1, source.length(), f);
	fclose(f);
    
	// compile kernel module
	if (compile_drivers & Driver::LINUXMODULE) {
	    StringAccum compile_command;
	    compile_command << click_buildtool_prog << " makepackage -C "
			    << tmpdir << " -t linuxmodule " << quiet_arg
			    << package_name << " " << package_name << "_.cc 1>&2";
	    int compile_retval = system(compile_command.c_str());
	    if (compile_retval == 127)
		errh->fatal("could not run '%s'", compile_command.c_str());
	    else if (compile_retval < 0)
		errh->fatal("could not run '%s': %s", compile_command.c_str(), strerror(errno));
	    else if (compile_retval != 0)
		errh->fatal("'%s' failed", compile_command.c_str());
	}

	// compile userlevel
	if (compile_drivers & Driver::USERLEVEL) {
	    StringAccum compile_command;
	    compile_command << click_buildtool_prog << " makepackage -C "
			    << tmpdir << " -t userlevel " << quiet_arg
			    << package_name << " " << package_name << "_.cc 1>&2";
	    int compile_retval = system(compile_command.c_str());
	    if (compile_retval == 127)
		errh->fatal("could not run '%s'", compile_command.c_str());
	    else if (compile_retval < 0)
		errh->fatal("could not run '%s': %s", compile_command.c_str(), strerror(errno));
	    else if (compile_retval != 0)
		errh->fatal("'%s' failed", compile_command.c_str());
	}
    }

    // add .cc, .hh and .?o files to archive
    {
	ArchiveElement ae = init_archive_element(package_name + ".cc", 0600);
	ae.data = source.take_string();
	r->add_archive(ae);

	ae.name = package_name + ".hh";
	ae.data = header.take_string();
	r->add_archive(ae);

	if (compile_drivers & Driver::LINUXMODULE) {
	    ae.name = package_name + ".ko";
	    ae.data = file_string(tmpdir + ae.name, errh);
	    r->add_archive(ae);
	}
	
	if (compile_drivers & Driver::USERLEVEL) {
	    ae.name = package_name + ".uo";
	    ae.data = file_string(tmpdir + ae.name, errh);
	    r->add_archive(ae);
	}
    }

    // add elementmap to archive
    {
	if (r->archive_index("elementmap-fastclassifier.xml") < 0)
	    r->add_archive(init_archive_element("elementmap-fastclassifier.xml", 0600));
	ArchiveElement &ae = r->archive("elementmap-fastclassifier.xml");
	ElementMap em(ae.data);
	ElementTraits t;
	t.header_file = package_name + ".hh";
	t.source_file = package_name + ".cc";
	t.processing_code = "h/h";
	t.flow_code = "x/x";
	for (int i = 0; i < gen_eclass_names.size(); i++) {
	    t.name = gen_eclass_names[i];
	    t.cxx = gen_cxxclass_names[i];
	    em.add(t);
	}
	ae.data = em.unparse("fastclassifier");
    }

    // add classifier configurations to archive
    {
	if (r->archive_index("fastclassifier_info") < 0)
	    r->add_archive(init_archive_element("fastclassifier_info", 0600));
	ArchiveElement &ae = r->archive("fastclassifier_info");
	StringAccum sa;
	for (int i = 0; i < gen_eclass_names.size(); i++) {
	    sa << gen_eclass_names[i] << '\t'
	       << cids[all_programs[i].type]->name << '\t'
	       << cp_quote(old_configurations[i]) << '\n';
	}
	ae.data += sa.take_string();
    }
}



static void
reverse_transformation(RouterT *r, ErrorHandler *)
{
  // parse fastclassifier_config
  if (r->archive_index("fastclassifier_info") < 0)
    return;
  ArchiveElement &fc_ae = r->archive("fastclassifier_info");
  Vector<String> click_names, old_type_names, configurations;
  parse_tabbed_lines(fc_ae.data, &click_names, &old_type_names,
		     &configurations, (void *)0);

  // prepare type_map : type -> configuration #
  HashMap<ElementClassT *, int> type_map(-1);
  for (int i = 0; i < click_names.size(); i++)
    type_map.insert(ElementClassT::base_type(click_names[i]), i);

  // change configuration
  for (int i = 0; i < r->nelements(); i++) {
    ElementT *e = r->element(i);
    int x = type_map[e->type()];
    if (x >= 0) {
      e->set_configuration(configurations[x]);
      e->set_type(ElementClassT::base_type(old_type_names[x]));
    }
  }

  // remove requirements
  {
    Vector<String> requirements = r->requirements();
    for (int i = 0; i < requirements.size(); i++)
      if (requirements[i].substring(0, 14) == "fastclassifier")
	r->remove_requirement(requirements[i]);
  }
  
  // remove archive elements
  for (int i = 0; i < r->narchive(); i++) {
    ArchiveElement &ae = r->archive(i);
    if (ae.name.substring(0, 14) == "fastclassifier"
	|| ae.name == "elementmap-fastclassifier.xml")
      ae.name = String();
  }
}

extern "C" {
void add_fast_classifiers_1();
void add_fast_classifiers_2();
}
  
int
main(int argc, char **argv)
{
  click_static_initialize();
  CLICK_DEFAULT_PROVIDES;
  ErrorHandler *errh = new PrefixErrorHandler(ErrorHandler::default_handler(), "click-fastclassifier: ");

  // 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;
  const char *output_file = 0;
  int compile_drivers = 0;
  bool combine_classifiers = true;
  bool do_compile = true;
  bool source_only = false;
  bool config_only = false;
  bool reverse = false;
  bool file_is_expr = false;
  
  while (1) {
    int opt = Clp_Next(clp);
    switch (opt) {
      
     case HELP_OPT:
      usage();
      exit(0);
      break;
      
     case VERSION_OPT:
      printf("click-fastclassifier (Click) %s\n", CLICK_VERSION);
      printf("Copyright (c) 1999-2000 Massachusetts Institute of Technology\n\
Copyright (c) 2000-2001 Mazu Networks, Inc.\n\
Copyright (c) 2001 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 ROUTER_OPT:
     case EXPRESSION_OPT:
     case Clp_NotOption:
      if (router_file) {
	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) {
	errh->error("output file specified twice");
	goto bad_option;
      }
      output_file = clp->arg;
      break;

     case COMBINE_OPT:
      combine_classifiers = !clp->negated;
      break;
      
     case COMPILE_OPT:
      do_compile = !clp->negated;
      break;
      
     case REVERSE_OPT:
      reverse = !clp->negated;
      break;
      
     case SOURCE_OPT:
      source_only = !clp->negated;
      break;
      
     case CONFIG_OPT:
      config_only = !clp->negated;
      break;
      
     case KERNEL_OPT:
      compile_drivers |= Driver::LINUXMODULE;
      break;
      
     case USERLEVEL_OPT:
      compile_drivers |= Driver::USERLEVEL;
      break;

     case QUIET_OPT:
      quiet_arg = (clp->negated ? "" : "-q ");
      break;

     case VERBOSE_OPT:
      verbose = !clp->negated;
      break;

     bad_option:
     case Clp_BadOption:
      short_usage();
      exit(1);
      break;
      
     case Clp_Done:
      goto done;
      
    }
  }
  
 done:
  RouterT *r = read_router(router_file, file_is_expr, errh);
  if (r)
    r->flatten(errh);
  if (!r || errh->nerrors() > 0)
    exit(1);
  if (source_only || config_only)
    compile_drivers = 0;

  // open output file
  FILE *outf = stdout;
  if (output_file && strcmp(output_file, "-") != 0) {
    outf = fopen(output_file, "w");
    if (!outf)
      errh->fatal("%s: %s", output_file, strerror(errno));
  }

  // handle reverse case
  if (reverse) {
    reverse_transformation(r, errh);
    write_router_file(r, outf, errh);
    exit(0);
  }

  // install classifier handlers
  add_interesting_handler("program");
  add_fast_classifiers_1();
  add_fast_classifiers_2();
  
  // find Click binaries
  runclick_prog = clickpath_find_file("click", "bin", CLICK_BINDIR, errh);
  click_buildtool_prog = clickpath_find_file("click-buildtool", "bin", CLICK_BINDIR, errh);

  // find Classifiers
  Vector<ElementT *> classifiers;
  for (RouterT::iterator x = r->begin_elements(); x; x++)
    if (cid_name_map[x->type_name()] >= 0)
      classifiers.push_back(x);

  // quit early if no Classifiers
  if (classifiers.size() == 0) {
    if (source_only)
      errh->message("no Classifiers in router");
    else
      write_router_file(r, outf, errh);
    exit(0);
  }

  // try combining classifiers
  if (combine_classifiers) {
    bool any_combined = false;
    for (int i = 0; i < classifiers.size(); i++)
      any_combined |= try_combine_classifiers(r, classifiers[i]);
    if (any_combined)
      try_remove_classifiers(r, classifiers);
  }

  // create classifiers program
  RouterT *classprogr = classifiers_program(r, classifiers);

  // figure out package name
  String package_name;
  {
      md5_state_t pms;
      char buf[MD5_TEXT_DIGEST_SIZE];
      String s = classprogr->configuration_string();
      md5_init(&pms);
      md5_append(&pms, (const md5_byte_t *) s.data(), s.length());
      md5_final_text(&pms, buf);
      package_name = "clickfc_" + String(buf);
  }
  
  if (do_compile)
    compile_classifiers(r, package_name, classprogr, classifiers, compile_drivers, errh);

  // write output
  if (source_only) {
    if (r->archive_index(package_name + ".hh") < 0) {
      errh->error("no source code generated");
      exit(1);
    }
    const ArchiveElement &aeh = r->archive(package_name + ".hh");
    const ArchiveElement &aec = r->archive(package_name + ".cc");
    fwrite(aeh.data.data(), 1, aeh.data.length(), outf);
    fwrite(aec.data.data(), 1, aec.data.length(), outf);
  } else if (config_only) {
    String config = r->configuration_string();
    fwrite(config.data(), 1, config.length(), outf);
  } else
    write_router_file(r, outf, errh);
  
  exit(0);
}

// generate Vector template instance
#include <click/vector.cc>
template class Vector<Classifier_Insn>;
template class Vector<Classifier_Program>;


syntax highlighted by Code2HTML, v. 0.9.1