## Copyright (C) 2008 Soren Hauberg <soren@hauberg.org>
## Copyright (C) 2014, 2015 Julien Bect <jbect@users.sourceforge.net>
## Copyright (C) 2015 Oliver Heimlich <oheim@posteo.de>
##
## 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 3 of the License, or (at
## your option) any later version.
##
## This program is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING.  If not, see
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} generate_package_html (@var{name}, @var{outdir}, @var{options})
## Generate @t{HTML} documentation for a package.
##
## The function reads information about package @var{name} using the
## package system. This is then used to generate bunch of
## @t{HTML} files; one for each function in the package, and one overview
## page. The files are all placed in the directory @var{outdir}, which defaults
## to the current directory. The @var{options} structure is used to control
## the design of the web pages.
##
## As an example, the following code generates the web pages for the @t{image}
## package, with a design suitable for the @t{Octave-Forge} project.
##
## @example
## options = get_html_options ("octave-forge");
## generate_package_html ("image", "image_html", options);
## @end example
##
## The resulting files will be available in the @t{"image_html"} directory. The
## overview page will be called @t{"image_html/overview.html"}.
##
## As a convenience, if @var{options} is a string, a structure will
## be generated by calling @code{get_html_options}. This means the above
## example can be reduced to the following.
##
## @example
## generate_package_html ("image", "image_html", "octave-forge");
## @end example
##
## If you want to include prepared package documentation in html format,
## you have to set @var{options}.package_doc manually with the filename
## of its texinfo source, which must be in the packages "doc" directory.
## Contained images are automatically copied if they are at the paths
## specified in the texinfo source relative to the packages "doc"
## directory.
##
## It should be noted that the function only works for installed packages.
## @seealso{get_html_options}
## @end deftypefn

function generate_package_html (name = [], outdir = "htdocs", options = struct ())

  ## Check input
  if (isempty (name))
    list = pkg ("list");
    for k = 1:length (list)
      generate_package_html (list{k}.name, outdir, options);
    endfor
    return;
  elseif (isstruct (name))
    desc = name;
    if (isfield (name, "name"))
      packname = desc.name;
    else
      packname = "";
    endif
  elseif (ischar (name))
    packname = name;
    pkg ("load", name);
    desc = (pkg ("describe", name)){1};
  else
    error (["First input must either be the name of a ", ...
            "package, or a structure giving its description."]);
  endif

  if (isempty (outdir))
    outdir = packname;
  elseif (!ischar (outdir))
    error ("Second input argument must be a string");
  endif

  ## Create output directory if needed
  if (!exist (outdir, "dir"))
    [succ, msg] = mkdir (outdir);
    if (!succ)
      error ("Unable to create directory %s:\n %s", outdir, msg);
    endif
  endif

  ## Create package directory if needed
  packdir = fullfile (outdir, packname);
  if (!exist (packdir, "dir"))
    [succ, msg] = mkdir (packdir);
    if (!succ)
      error ("Unable to create directory %s:\n %s", packdir, msg);
    endif
  endif

  ## Process input argument 'options'
  if (ischar (options)) || (isstruct (options))
    options = get_html_options (options);
  else
    error ("Third input argument must be a string or a structure");
  endif

  ## Function directory
  local_fundir = options.function_dir;
  fundir = fullfile (packdir, local_fundir);


  ## Create function directory if needed
  if (!exist (fundir, "dir"))
    [succ, msg] = mkdir (fundir);
    if (!succ)
      error ("Unable to create directory %s:\n %s", fundir, msg);
    endif
  endif

  ##################################################
  ## Generate html pages for individual functions ##
  ##################################################

  num_categories = length (desc.provides);
  anchors = implemented = cell (1, num_categories);
  for k = 1:num_categories
    F = desc.provides{k}.functions;
    category = desc.provides{k}.category;

    ## Create a valid anchor name by keeping only alphabetical characters
    anchors{k} = regexprep (category, "[^a-zA-Z]", "_");

    ## For each function in category
    num_functions = length (F);
    implemented{k} = cell (1, num_functions);
    for l = 1:num_functions
      fun = F{l};
      if (fun(1) == "@")
        ## Extract @-directory name from function name
        at_dir = fullfile (fundir, fileparts (fun));
        ## Create directory if needed
        if (!exist (at_dir, "dir"))
          [succ, msg] = mkdir (at_dir);
          if (!succ)
            error ("Unable to create directory %s:\n %s", at_dir, msg);
          endif
        endif
        ## Package root is two level upper in the case of an @-directory
        pkgroot = "../../";
      else
        pkgroot = "../";
      endif
      root = ["../" pkgroot];
      outname = fullfile (fundir, sprintf ("%s.html", fun));
      try
        html_help_text (fun, outname, options, root, pkgroot, packname);
        implemented{k}{l} = true;
      catch
        err = lasterror ();
        if (strfind (err.message, "not found"))
          warning ("marking '%s' as not implemented", fun);
          implemented{k}{l} = false;
        else
          rethrow (err);
        endif
      end_try_catch
    endfor
  endfor

  #########################
  ## Write overview file ##
  #########################
  first_sentences = cell (1, num_categories);
  if options.include_overview
  
    ## Create filename for the overview page
    overview_filename = options.overview_filename;
    overview_filename = strrep (overview_filename, "%name", desc.name);
    overview_filename = strrep (overview_filename, " ", "_");

    fid = fopen (fullfile (packdir, overview_filename), "w");
    if (fid < 0)
      error ("Couldn't open overview file for writing");
    endif

    [header, title, footer] = get_header_title_and_footer ...
      ("overview", options, desc.name, "../", "", packname);

    fprintf (fid, "%s\n", header);
    fprintf (fid, "<h2 class=\"tbdesc\">%s</h2>\n\n", desc.name);

    fprintf (fid, "  <div class=\"package_description\">\n");
    fprintf (fid, "    %s\n", desc.description);
    fprintf (fid, "  </div>\n\n");

    fprintf (fid, "<p>Select category:  <select name=\"cat\" onchange=\"location = this.options[this.selectedIndex].value;\">\n");
    for k = 1:num_categories
      category = desc.provides{k}.category;
      fprintf (fid, "    <option value=\"#%s\">%s</option>\n", anchors{k}, category);
    endfor
    fprintf (fid, "  </select></p>\n\n");

    ## Generate function list by category
    for k = 1:num_categories
      F = desc.provides{k}.functions;
      category = desc.provides{k}.category;
      fprintf (fid, "  <h3 class=\"category\"><a name=\"%s\">%s</a></h3>\n\n",
               anchors{k}, category);

      first_sentences{k} = cell (1, length (F));

      ## For each function in category
      for l = 1:length (F)
        fun = F{l};
        if (implemented{k}{l})
          first_sentences{k}{l} = get_first_help_sentence (fun, 200);
          first_sentences{k}{l} = strrep (first_sentences{k}{l}, "\n", " ");

          link = sprintf ("%s/%s.html", local_fundir, fun);
          fprintf (fid, "    <div class=\"func\"><b><a href=\"%s\">%s</a></b></div>\n",
                   link, fun);
          fprintf (fid, "    <div class=\"ftext\">%s</div>\n\n", ...
                   first_sentences{k}{l});
        else
          fprintf (fid, "    <div class=\"func\"><b>%s</b></div>\n", fun);
          fprintf (fid, "    <div class=\"ftext\">Not implemented.</div>\n\n");
        endif
      endfor
    endfor

    fprintf (fid, "\n%s\n", footer);
    fclose (fid);
  endif

  ################################################
  ## Write function data for alphabetical lists ##
  ################################################
  if options.include_alpha
    for letter = "a":"z"
      [name_filename, desc_filename] = get_alpha_database (outdir, desc.name, letter);
      name_fid = fopen (name_filename, "w");
      desc_fid = fopen (desc_filename, "w");
      if (name_fid == -1 || desc_fid == -1)
        error ("Could not open alphabet database for writing");
      endif

      for k = 1:num_categories
        F = desc.provides{k}.functions;
        for l = 1:length (F)
          fun = F{l};
          if (implemented{k}{l} && lower (fun (1)) == letter)
            fs = first_sentences{k}{l};

            fprintf (name_fid, "%s\n", fun);
            fprintf (desc_fid, "%s\n", fs);
          endif
        endfor
      endfor

      fclose (name_fid);
      fclose (desc_fid);
    endfor
  endif

  #####################################################
  ## Write short description for forge overview page ##
  #####################################################

  if options.include_package_list_item

    pkg_list_item_filename = options.pkg_list_item_filename;

    text = strrep (options.package_list_item, "%name", desc.name);
    text = strrep (text, "%version", desc.version);
    text = strrep (text, "%extension", "tar.gz");
    text = strrep (text, "%shortdescription", desc.description);

    fid = fopen (fullfile (packdir, pkg_list_item_filename), "w");
    if (fid > 0)
      fprintf (fid, text);
      fclose (fid);
    else
      error ("Unable to open file %s.", pkg_list_item_filename);
    endif
  endif

  #####################
  ## Write NEWS file ##
  #####################
  if (! options.include_package_news)
    write_package_news = false;
  else
    ## Get detailed information about the package
    all_list = pkg ("list");
    list = [];
    for k = 1:length (all_list)
      if (strcmp (all_list{k}.name, packname))
        list = all_list{k};
      endif
    endfor
    if (isempty (list))
      error ("Couldn't locate package '%s'", packname);
    endif

    ## Read news
    filename = fullfile (list.dir, "packinfo", "NEWS");
    fid = fopen (filename, "r");
    if (fid < 0)
      warning ("generate_package_html: couldn't open NEWS for reading");
      write_package_news = false;
    else
      write_package_news = true;
      news_content = char (fread (fid).');
      fclose (fid);

      ## Open output file
      news_filename = "NEWS.html";

      fid = fopen (fullfile (packdir, news_filename), "w");
      if (fid < 0)
        error ("Couldn't open NEWS file for writing");
      endif

      ## For the NEWS page, use the header and footer of the overview page
      [header, title, footer] = get_header_title_and_footer ...
        ("overview", options, desc.name, "../", "", packname);

      ## Write output
      fprintf (fid, "%s\n", header);
      fprintf (fid, "<h2 class=\"tbdesc\">NEWS for '%s' Package</h2>\n\n", desc.name);
      fprintf (fid, "<p><a href=\"index.html\">Return to the '%s' package</a></p>\n\n", desc.name);

      fprintf (fid, "<pre>%s</pre>\n\n", insert_char_entities (news_content));

      fprintf (fid, "\n%s\n", footer);
      fclose (fid);
    endif
  endif

  #########################################
  ## Should we include the package doc ? ##
  #########################################

  write_package_documentation = ~ isempty (options.package_doc);
  if write_package_documentation
    [~, doc_fn, doc_ext] = fileparts (options.package_doc);
    doc_root_dir = fullfile (list.dir, "doc");
    doc_src = fullfile (doc_root_dir, [doc_fn, doc_ext]);
    doc_subdir = "package_doc";
    doc_out_dir = fullfile (packdir, doc_subdir);
  endif

  ######################
  ## Write index file ##
  ######################

  if options.include_package_page
    ## Get detailed information about the package
    all_list = pkg ("list");
    list = [];
    for k = 1:length (all_list)
      if (strcmp (all_list{k}.name, packname))
        list = all_list{k};
      endif
    endfor
    if (isempty (list))
      error ("Couldn't locate package '%s'", packname);
    endif

    ## Open output file
    index_filename = "index.html";

    fid = fopen (fullfile (packdir, index_filename), "w");
    if (fid < 0)
      error ("Couldn't open index file for writing");
    endif

    ## Write output
    [header, title, footer] = get_header_title_and_footer ...
      ("index", options, desc.name, "../", "", packname);

    fprintf (fid, "%s\n", header);
    fprintf (fid, "<h2 class=\"tbdesc\">%s</h2>\n\n", desc.name);

    fprintf (fid, "<table>\n");
    fprintf (fid, "<tr><td rowspan=\"2\" class=\"box_table\">\n");
    fprintf (fid, "<div class=\"package_box\">\n");
    fprintf (fid, "  <div class=\"package_box_header\"></div>\n");
    fprintf (fid, "  <div class=\"package_box_contents\">\n");
    fprintf (fid, "    <table>\n");
    fprintf (fid, "      <tr><td class=\"package_table\">Package Version:</td><td>%s</td></tr>\n",
            list.version);
    fprintf (fid, "      <tr><td class=\"package_table\">Last Release Date:</td><td>%s</td></tr>\n",
             list.date);
    fprintf (fid, "      <tr><td class=\"package_table\">Package Author:</td><td>%s</td></tr>\n",
             insert_char_entities (list.author));
    fprintf (fid, "      <tr><td class=\"package_table\">Package Maintainer:</td><td>%s</td></tr>\n",
             insert_char_entities (list.maintainer));
    fprintf (fid, "      <tr><td class=\"package_table\">License:</td><td><a href=\"COPYING.html\">");
    if (isfield (list, "license"))
      fprintf (fid, "%s</a></td></tr>\n", list.license);
    else
      fprintf (fid, "Read license</a></td></tr>\n");
    endif
    fprintf (fid, "    </table>\n");
    fprintf (fid, "  </div>\n");
    fprintf (fid, "</div>\n");
    fprintf (fid, "</td>\n\n");

    fprintf (fid, "<td>\n");
    if (! isempty (options.download_link))
      fprintf (fid, "<div class=\"download_package\">\n");
      fprintf (fid, "  <table><tr><td>\n");
      fprintf (fid, "    <img src=\"../download.png\" alt=\"Package download icon\"/>\n");
      fprintf (fid, "  </td><td>\n");
      link = strrep (options.download_link, "%name", desc.name);
      link = strrep (link, "%version", desc.version);
      fprintf (fid, "    <a href=\"%s\"\n", link);
      fprintf (fid, "     class=\"download_link\">\n");
      fprintf (fid, "      Download Package\n");
      fprintf (fid, "    </a><br />\n");
      fprintf (fid, "    <a href=\"http://sourceforge.net/projects/octave/files/\"");
      fprintf (fid, " class=\"older_versions_download\">(older versions)</a>\n");
      fprintf (fid, "  </td></tr></table>\n");
      fprintf (fid, "</div>\n");
    endif
    fprintf (fid, "</td></tr>\n");
    fprintf (fid, "<tr><td>\n");
    fprintf (fid, "<div class=\"package_function_reference\">\n");
    fprintf (fid, "  <table><tr><td>\n");
    fprintf (fid, "    <img src=\"../doc.png\" alt=\"Function reference icon\"/>\n");
    fprintf (fid, "  </td><td>\n");
    fprintf (fid, "    <a href=\"%s\" class=\"function_reference_link\">\n", ...
             overview_filename);
    fprintf (fid, "      Function Reference\n");
    fprintf (fid, "    </a>\n");
    fprintf (fid, "  </td></tr>\n");
    if (write_package_documentation)
      fprintf (fid, "  <tr><td>\n");
      fprintf (fid, "    <img src=\"../manual.png\" alt=\"Package doc icon\"/>\n");
      fprintf (fid, "  </td><td>\n");
      fprintf (fid, "    <a href=\"%s\" class=\"package_doc\">\n", ...
               fullfile (doc_subdir, "index.html"));
      fprintf (fid, "      Package Documentation\n");
      fprintf (fid, "    </a>\n");
      fprintf (fid, "  </td></tr>\n");
    endif
    if (write_package_news)
      fprintf (fid, "  <tr><td>\n");
      fprintf (fid, "    <img src=\"../news.png\" alt=\"Package news icon\"/>\n");
      fprintf (fid, "  </td><td>\n");
      fprintf (fid, "    <a href=\"NEWS.html\">\n");
      fprintf (fid, "      NEWS\n");
      fprintf (fid, "    </a>\n");
      fprintf (fid, "  </td></tr>\n");
    endif
    fprintf (fid, "  </table>\n");
    fprintf (fid, "</div>\n");
    fprintf (fid, "</td></tr>\n");
    fprintf (fid, "</table>\n\n");

    fprintf (fid, "<h3>Description</h3>\n");
    fprintf (fid, "  <div id=\"description_box\">\n")
    fprintf (fid, list.description);
    fprintf (fid, "  </div>\n\n")

    fprintf (fid, "<h3>Details</h3>\n");
    fprintf (fid, "  <table id=\"extra_package_table\">\n");

    if (isfield (list, "depends"))
      fprintf (fid, "    <tr><td>Dependencies: </td><td>\n");
      for k = 1:length (list.depends)
        p = list.depends{k}.package;
        if (isfield (list.depends{k}, "operator") && isfield (list.depends{k}, "version"))
          o = list.depends{k}.operator;
          v = list.depends{k}.version;
          vt = sprintf ("(%s %s) ", o, v);
        else
          vt = "";
        endif

        if (strcmpi (p, "octave"))
          fprintf (fid, "<a href=\"http://www.octave.org\">Octave</a> ");
        else
          fprintf (fid, "<a href=\"../%s/index.html\">%s</a> ", p, p);
        endif
        fprintf (fid, vt);
      endfor
      fprintf (fid, "</td></tr>\n");
    endif

    if (isfield (list, "systemrequirements"))
      fprintf (fid, "    <tr><td>Runtime system dependencies:</td><td>%s</td></tr>\n", list.systemrequirements);
    endif

    if (isfield (list, "buildrequires"))
      fprintf (fid, "    <tr><td>Build dependencies:</td><td>%s</td></tr>\n", list.buildrequires);
    endif

    ## if the package does not specify, then it is not autoloaded. Also, sometimes
    ## the value is 1 (true) but other times the value is a string
    if (isfield (list, "autoload") && (list.autoload == 1 ||
                                       any (strcmpi ({"yes", "on", "true"}, list.autoload))))
      a = "Yes";
    else
      a = "No";
    endif
    fprintf (fid, "    <tr><td>Autoload:</td><td>%s</td></tr>\n", a);

    fprintf (fid, "  </table>\n\n");

    fprintf (fid, "\n%s\n", footer);
    fclose (fid);
  endif

  ########################
  ## Write COPYING file ##
  ########################
  if options.include_package_license
    ## Get detailed information about the package
    all_list = pkg ("list");
    list = [];
    for k = 1:length (all_list)
      if (strcmp (all_list{k}.name, packname))
        list = all_list{k};
      endif
    endfor
    if (isempty (list))
      error ("Couldn't locate package '%s'", packname);
    endif

    ## Read license
    filename = fullfile (list.dir, "packinfo", "COPYING");
    fid = fopen (filename, "r");
    if (fid < 0)
      error ("Couldn't open license for reading");
    endif
    copying_contents = char (fread (fid).');
    fclose (fid);

    ## Open output file
    copying_filename = "COPYING.html";

    fid = fopen (fullfile (packdir, copying_filename), "w");
    if (fid < 0)
      error ("Couldn't open COPYING file for writing");
    endif

    ## For the COPYING page, use the header and footer of the overview page
    [header, title, footer] = get_header_title_and_footer ...
      ("overview", options, desc.name, "../", "", packname);

    ## Write output
    fprintf (fid, "%s\n", header);
    fprintf (fid, "<h2 class=\"tbdesc\">License for '%s' Package</h2>\n\n", desc.name);
    fprintf (fid, "<p><a href=\"index.html\">Return to the '%s' package</a></p>\n\n", desc.name);

    fprintf (fid, "<pre>%s</pre>\n\n", insert_char_entities (copying_contents));

    fprintf (fid, "\n%s\n", footer);
    fclose (fid);
  endif

  #################################
  ## Write package documentation ##
  #################################
  if (write_package_documentation)

    ## Convert texinfo source
    status = system (sprintf ("%s --html -o %s %s",
                              makeinfo_program (),
                              doc_out_dir,
                              doc_src));
    if (status == 127)
      error ("Program `%s' not found", makeinfo_program ());
    elseif (status)
      error ("Program `%s' returned failure code %i",
             makeinfo_program (), status);
    endif

    ## Read image references from generated files and copy images
    filelist = glob (fullfile (doc_out_dir, "*.html"));
    for id = 1 : numel (filelist)
      copy_images (filelist{id}, doc_root_dir, doc_out_dir);
    endfor

  endif

endfunction

function copy_images (file, doc_root_dir, doc_out_dir)

  if ((fid = fopen (file)) < 0)
    error ("Couldn't open %s for reading", file);
  endif
  while (! isnumeric (l = fgetl (fid)))
    m = regexp (l, "<img.+src=""([^""]+)"".*>", "tokens");
    if (! isempty (m))
      url = m{1}{1};
      ## exclude external links
      if (isempty (strfind (url, "//")))
        if (! isempty (strfind (url, "..")))
          warning ("not copying image %s because path contains '..'",
                   url);
        else
          if (! isempty (imgdir = fileparts (url)) &&
              ! strcmp (imgdir, "./") &&
              ! exist (imgoutdir = fullfile (doc_out_dir, imgdir), "dir"))
            [succ, msg] = mkdir (imgoutdir);
            if (!succ)
              error ("Unable to create directory %s:\n %s", imgoutdir, msg);
            endif
          endif
          if (! ([status, msg] = copyfile (fullfile (doc_root_dir, url),
                                           fullfile (doc_out_dir, url))))
            warning ("could not copy image file %s: %s", url, msg);
          endif
        endif
      endif
    endif
  endwhile
  fclose (fid);

endfunction
