/*****************************************************************************\
*                                                                             *
*  Name   : marks_maker                                                       *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Turns a link database in HOOPLE format into a web page, when given a     *
*  suitable template file.  The template file must have the phrase:           *
*        $INSERT_LINKS_HERE                                                   *
*  at the point where the generated links are supposed to be stored.          *
*                                                                             *
*******************************************************************************
* Copyright (c) 2005-$now By Author.  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 is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#include "bookmark_tree.cpp"

#include <basis/function.h>
#include <basis/guards.h>
#include <basis/istring.h>
#include <opsystem/application_shell.h>
#include <opsystem/byte_filer.h>
#include <opsystem/command_line.h>
#include <loggers/file_logger.h>
#include <opsystem/filename.h>
#include <data_struct/static_memory_gremlin.h>
#include <textual/list_parsing.h>

using namespace nodes;

//#define DEBUG_MARKS
  // uncomment to have more debugging noise.

#undef BASE_LOG
#define BASE_LOG(s) program_wide_logger().log(s)
#undef LOG
#define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger(), \
   isprintf("line %d: ", _categories._line_number) + s)

const int MAX_FILE_SIZE = 4 * MEGABYTE;
  // the largest file we'll read.

////////////////////////////////////////////////////////////////////////////

class marks_maker : public application_shell
{
public:
  marks_maker()
      : application_shell(static_class_name()), _need_closure(false) {}
  IMPLEMENT_CLASS_NAME("marks_maker");
  virtual int execute();
  int print_instructions(const filename &program_name);

  int write_marks_page(const istring &output_filename,
          const istring &template_filename);
    // given a tree of links, this writes out a web page to "output_filename"
    // using a template file "template_filename".

private:
  bookmark_tree _categories;  // our tree of categories.
  bool _need_closure;  // true if our <div> needs a closure.

//this needs to gather any strings that would have gone into functions.
//instead, they need to be written into the current node's string.
//when a new function def would be seen, then we need to push a new node
//for accumulating the text.

  // these handle outputting text for categories and links.
  void write_category(inner_mark_tree *node, istring &output);
  void write_link(inner_mark_tree *node, const link_record &linko,
         istring &output);
};

////////////////////////////////////////////////////////////////////////////

int marks_maker::print_instructions(const filename &program_name)
{
  isprintf to_show("%s:\n\
This program needs three filenames as command line parameters.  The -i flag\n\
is used to specify the input filename, the -t flag specifies a template web\n\
page which is used as the wrapper around the links, and the -o flag specifies\n\
the web page to be created.  The input file is expected to be in the HOOPLE\n\
link database format.  The output file will be created from the template file\n\
by finding the phrase $INSERT_LINKS_HERE in it and replacing that with html\n\
formatted link and categories from the input file.  Another tag of $TODAYS_DATE\n\
will be replaced with the date when the output file is regenerated.\n\
The HOOPLE link format is documented here:\n\
    http://hoople.org/guides/link_database/format_manifesto.txt\n\
", program_name.basename().raw().s(), program_name.basename().raw().s());
  program_wide_logger().log(to_show.s());
  return 12;
}

void marks_maker::write_category(inner_mark_tree *node, istring &output)
{
  FUNCDEF("write_category");
  // calculate proper heading number.
  int heading_num = node->depth();
  if (heading_num < 1) heading_num = 1;
  else if (heading_num > 2) heading_num = 2;
  istring heading = isprintf("%d", heading_num);

  if (_need_closure) {
    output += "</div>";
    output += log_base::platform_ending();
    _need_closure = false;
  }

  // add formatting to indent appropriately...
  int spaces = 4 * node->depth();
  output += isprintf("<div style=\"margin-left: %d0px;\">", spaces);
  output += log_base::platform_ending();
  _need_closure = true;

  output += "<h";
  output += heading;
  output += ">";
  output += node->name();
  output += "</h";
  output += heading;
  output += ">";
  output += log_base::platform_ending();
}

void marks_maker::write_link(inner_mark_tree *formal(node),
    const link_record &linko, istring &output)
{
  FUNCDEF("write_link");
  // write an html link definition.
  if (!linko._url) {
    // this just appears to be a comment line.

    if (!linko._description) return;  // it's a nothing line.

    output += linko._description;
    output += "<br>";
    output += log_base::platform_ending();
    return;
  }

//hmmm: move
const int MAX_URL_DISPLAYED = 58;
const int MAX_DESCRIP_DISPLAYED = 72;

//hmmm...  this is chopping in the middle; would it look more normal
//         to just snap the whole end off?
  istring chomped_url = linko._url;
  if (chomped_url.length() > MAX_URL_DISPLAYED) {
    chomped_url.zap(MAX_URL_DISPLAYED / 2,
        chomped_url.length() - MAX_URL_DISPLAYED / 2 - 1);
    chomped_url.insert(MAX_URL_DISPLAYED / 2, "...");
  }

//hmmm: this is chopping the tail off, which seems reasonable for a
//      long description.
  istring description = linko._description;
  if (description.length() > MAX_DESCRIP_DISPLAYED) {
    description.zap(MAX_DESCRIP_DISPLAYED - 1, description.end());
    description += "...";
  }

  output += "<li>";
  output += description + "&nbsp;&nbsp;&nbsp;";
  output += istring("(") + chomped_url + ")" + "&nbsp;&nbsp;&nbsp;";
  output += "<a href=\"javascript:open_mark('";
  output += linko._url;
  output += "')\">";
  output += "[launch]";
  output += "</a>&nbsp;&nbsp;<a href=\"";
  output += linko._url;
  output += "\">";
  output += "[go-to]";
  output += "</a></li>";
  output += log_base::platform_ending();
}

int marks_maker::execute()
{
  FUNCDEF("execute");
  SET_DEFAULT_COMBO_LOGGER;

  command_line cmds(__argc, __argv);  // process the command line parameters.
  istring input_filename;  // we'll store our link database name here.
  istring output_filename;  // where the web page we're creating goes.
  istring template_filename;  // the wrapper html code that we'll stuff.
  if (!cmds.get_value('i', input_filename, false))
    return print_instructions(cmds.program_name());
  if (!cmds.get_value('o', output_filename, false))
    return print_instructions(cmds.program_name());
  if (!cmds.get_value('t', template_filename, false))
    return print_instructions(cmds.program_name());

  BASE_LOG(istring("input file: ") + input_filename);
  BASE_LOG(istring("output file: ") + output_filename);
  BASE_LOG(istring("template file: ") + template_filename);

  filename outname(output_filename);
  if (outname.exists()) {
    non_continuable_error(class_name(), func, istring("the output file ")
        + output_filename + " already exists.  It would be over-written if "
        "we continued.");
  }

  int ret = _categories.read_csv_file(input_filename);
  if (ret) return ret;

  ret = write_marks_page(output_filename, template_filename);
  if (ret) return ret;
  
  return 0;
}

int marks_maker::write_marks_page(const istring &output_filename,
    const istring &template_filename)
{
  FUNCDEF("write_marks_page");
  istring long_string;
    // this is our accumulator of links.  it is the semi-final result that will
    // be injected into the template file.

  long_string += "<script type=\"text/javascript\">\n";
  long_string += "<!--\n";

  long_string += "function open_mark(url) {\n";
  long_string += "  winner = window.open(url, url, '');\n";
  long_string += "  winner.focus();\n";
  long_string += "}\n";

  long_string += "//-->\n";
  long_string += "</script>\n";

  // traverse the tree in prefix order.
  tree::iterator itty = _categories.access_root().start(tree::prefix);
  tree *curr = NIL;
  while ( (curr = itty.next()) ) {
    inner_mark_tree *nod = (inner_mark_tree *)curr;
    // print out the category on this node.
    write_category(nod, long_string);

    // print the link for all of the ones stored at this node.
    for (int i = 0; i < nod->_links.elements(); i++) {
      link_record *lin = nod->_links.borrow(i);
      write_link(nod, *lin, long_string);
    }
  }

  if (_need_closure) {
    long_string += "</div>";
    long_string += log_base::platform_ending();
    _need_closure = false;
  }

  byte_filer template_file(template_filename, "r");
  istring full_template;
  if (!template_file.good())
    non_continuable_error(class_name(), func, "the template file could not be opened");
  template_file.read(full_template, MAX_FILE_SIZE);
  template_file.close();

  // replace the tag with the long string we created.
  bool found_it = full_template.replace("$INSERT_LINKS_HERE", long_string);
  if (!found_it)
    non_continuable_error(class_name(), func, "the template file is missing "
        "the insertion point");
  full_template.replace("$TODAYS_DATE", timestamp(false, true));

  filename outname(output_filename);
  byte_filer output_file(output_filename, "w");
  if (!output_file.good())
    non_continuable_error(class_name(), func, "the output file could not be opened");
  // write the newly generated web page out now.
  output_file.write(full_template);
  output_file.close();

// show the tree.
//  BASE_LOG("");
//  BASE_LOG(_categories.access_root().text_form());

  BASE_LOG(isprintf("wrote %d links in %d categories.",
      _categories.link_count(), _categories.category_count()));
  BASE_LOG("");

  return 0;
}

////////////////////////////////////////////////////////////////////////////

HOOPLE_MAIN(marks_maker, )

#ifdef __BUILD_STATIC_APPLICATION__
  // static dependencies found by buildor_gen_deps.sh:
  #include <basis/array.cpp>
  #include <basis/byte_array.cpp>
  #include <basis/callstack_tracker.cpp>
  #include <basis/chaos.cpp>
  #include <basis/convert_utf.cpp>
  #include <basis/definitions.cpp>
  #include <basis/earth_time.cpp>
  #include <basis/guards.cpp>
  #include <basis/istring.cpp>
  #include <basis/log_base.cpp>
  #include <basis/memory_checker.cpp>
  #include <basis/mutex.cpp>
  #include <basis/object_base.cpp>
  #include <basis/outcome.cpp>
  #include <basis/packable.cpp>
  #include <basis/portable.cpp>
  #include <basis/sequence.cpp>
  #include <basis/set.cpp>
  #include <basis/utility.cpp>
  #include <basis/version_record.cpp>
  #include <data_struct/amorph.cpp>
  #include <data_struct/bit_vector.cpp>
  #include <data_struct/byte_hasher.cpp>
  #include <data_struct/configurator.cpp>
  #include <data_struct/hash_table.cpp>
  #include <data_struct/pointer_hash.cpp>
  #include <data_struct/stack.cpp>
  #include <data_struct/static_memory_gremlin.cpp>
  #include <data_struct/string_hash.cpp>
  #include <data_struct/string_hasher.cpp>
  #include <data_struct/string_table.cpp>
  #include <data_struct/symbol_table.cpp>
  #include <data_struct/table_configurator.cpp>
  #include <loggers/console_logger.cpp>
  #include <loggers/file_logger.cpp>
  #include <loggers/locked_logger.cpp>
  #include <loggers/null_logger.cpp>
  #include <loggers/program_wide_logger.cpp>
  #include <nodes/node.cpp>
  #include <nodes/path.cpp>
  #include <nodes/symbol_tree.cpp>
  #include <nodes/tree.cpp>
  #include <opsystem/application_base.cpp>
  #include <opsystem/application_shell.cpp>
  #include <opsystem/byte_filer.cpp>
  #include <opsystem/command_line.cpp>
  #include <opsystem/critical_events.cpp>
  #include <opsystem/directory.cpp>
  #include <opsystem/filename.cpp>
  #include <opsystem/ini_config.cpp>
  #include <opsystem/ini_parser.cpp>
  #include <opsystem/path_configuration.cpp>
  #include <opsystem/rendezvous.cpp>
  #include <textual/byte_format.cpp>
  #include <textual/list_parsing.cpp>
  #include <textual/parser_bits.cpp>
  #include <textual/string_manipulation.cpp>
  #include <textual/tokenizer.cpp>
#endif // __BUILD_STATIC_APPLICATION__

