/*****************************************************************************\
*                                                                             *
*  Name   : marks_maker_mozilla                                               *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Turns a link database in HOOPLE format into a web page in the style of   *
*  Mozilla's bookmark export.  This should allow the created web page to be   *
*  imported back into the Mozilla bookmarks manager.                          *
*                                                                             *
*******************************************************************************
* Copyright (c) 2006-$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(), s)

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

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

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

  int write_marks_page(const istring &output_filename);
    // given a tree of links, this writes out a mozilla bookmark format
    // web page to "output_filename".

private:
  bookmark_tree _categories;  // our tree of categories.

  istring recurse_on_node(inner_mark_tree *nod);
    // the main recursive method that spiders down the tree.

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

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

int marks_maker_mozilla::print_instructions(const filename &program_name)
{
  isprintf to_show("%s:\n\
This program needs two filenames as command line parameters.  The -i flag\n\
is used to specify the input filename and the -o flag specifies the web page\n\
to be created.  The input file is expected to be in the HOOPLE link database\n\
format.  The output file will be created from the template file by finding\n\
the phrase $INSERT_LINKS_HERE in it and replacing that with html formatted\n\
link and categories from the input file.  Another tag of $TODAYS_DATE will\n\
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_mozilla::write_category_start(inner_mark_tree *node,
    istring &output)
{
  FUNCDEF("write_category_start");
  // add spaces to indent appropriately...
  int spaces = 4 * node->depth();
  output += istring(' ', spaces);

  // add the beginning of the category stuff.
  output += "<dl> <p>";
  output += log_base::platform_ending();

  // add more spaces for the indent.
  spaces = 4 * (node->depth() + 1);
  output += istring(' ', spaces);

  // then add the category heading.
  output += "<dt> <h3>";
  output += node->name();
  output += "</h3>";
  output += log_base::platform_ending();
}

void marks_maker_mozilla::write_category_end(int depth, istring &output)
{
  FUNCDEF("write_category_end");
  // add spaces to indent appropriately...
  int spaces = 4 * (depth + 1);
  output += istring(' ', spaces);

  // add the beginning of the category stuff.
  output += "</dl> <p>";
  output += log_base::platform_ending();
}

void marks_maker_mozilla::write_link(inner_mark_tree *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.

//not done yet.
/*
    int spaces = 4 * node->depth();
    output += istring(' ', spaces);

    output += "<dt> ";
    output += "<a href=\"";
    output += linko._description;
    output += "<br>";
    output += log_base::platform_ending();
*/
    return;
  }

  // add spaces to push things out to right indentation.
  int spaces = 4 * (node->depth() + 2);
  output += istring(' ', spaces);

  // print out the link formatting.
  output += "<dt> ";
  output += "<a href=\"";
  // drop in the URL.
  output += linko._url;
  output += "\">";
  // put the description in also.
  output += linko._description;
  // close the link formatting.
  output += "</a>";
  output += log_base::platform_ending();
}

int marks_maker_mozilla::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());

  BASE_LOG(istring("input file: ") + input_filename);
  BASE_LOG(istring("output file: ") + output_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);
  if (ret) return ret;
  
  return 0;
}

istring marks_maker_mozilla::recurse_on_node(inner_mark_tree *nod)
{
  FUNCDEF("recurse_on_node");
  istring to_return;

  // print out the category on this node.
  write_category_start(nod, to_return);

  // 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, to_return);
  }

  // zoom down into sub-categories.
  for (int i = 0; i < nod->branches(); i++) {
    to_return += recurse_on_node((inner_mark_tree *)nod->branch(i));
  }

  // finish this category.
  write_category_end(nod->depth(), to_return);

  return to_return;
}

int marks_maker_mozilla::write_marks_page(const istring &output_filename)
{
  FUNCDEF("write_marks_page");
  istring long_string;
    // this is our accumulator of links and html formatting.

  // create the header piece 
  long_string += "\
<!DOCTYPE NETSCAPE-Bookmark-file-1>\n\
<!-- This file was automatically generated by ";

  // add the program name that's generating this.
  long_string += filename(__argv[0]).basename();
  long_string += ".\n";

  // add a generation time to the comments.
  long_string += "    It was generated on ";
  long_string += timestamp(true, true);
  long_string += "\n";

  // tack on some more html formatting, including the title and first header.
  long_string += "\
-->\n\
<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n\
<TITLE>Generated Bookmarks</TITLE>\n\
<H1>Generated Bookmarks</H1>\n";

  // now traverse the tree of bookmarks in postfix order.
  inner_mark_tree *top = (inner_mark_tree *)&_categories.access_root();
  long_string += recurse_on_node(top);

  // write the output file from the accumulated string.
  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(long_string);
  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_mozilla, )

#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__

