/** @file html.c
 * HTML output
 * @author Marko Mäkelä <msmakela@nic.funet.fi>
 */

/* Copyright © 2003 Marko Mäkelä.

   This file is part of PHOTOMOLO, a program for generating
   thumbnail images and HTML files for browsing digital photographs.

   PHOTOMOLO 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, or (at your option)
   any later version.

   PHOTOMOLO 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.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "tree.h"
#include "info.h"
#include "spec.h"
#include "html.h"

/** Write raw string to HTML
 * @param file	the output stream
 * @param s	the string
 * @return	zero on success, nonzero on error
 */
static int
html_raw (FILE* file,
	  const char* s)
{
  return fputs (s, file) == EOF;
}

/** Write appropriately escaped URI string to HTML
 * @param file	the output stream
 * @param s	the string
 * @return	zero on success, nonzero on error
 */
static int
html_uri (FILE* file,
	  const char* s)
{
  for (; *s; s++) {
    switch (*s) {
    default:
      if (*s > 0x20 && *s < 0x7e) {
	if (putc (*s, file) == EOF)
	  return 1;
	break;
      }
      /* fall through */
    case '"': case '#': case '%': case '?': case ':': case '+':
    case '&': case '<': case '>':
      if (3 != fprintf (file, "%%%02x", (unsigned char) *s))
	return 1;
    }
  }
  return 0;
}

/** Write appropriately escaped string to HTML
 * @param file	the output stream
 * @param s	the string
 * @param e	pointer to end of string, or NULL
 * @return	zero on success, nonzero on failure
 */
static int
html_esc (FILE* file,
	  const char* s,
	  const char* e)
{
  for (; *s && (!e || s < e); s++) {
    const char* esc;
    switch (*s) {
    case '&': esc = "&amp;"; break;
    case '<': esc = "&lt;"; break;
    case '>': esc = "&gt;"; break;
    case '"': esc = "&quot;"; break;
    default:
      if (putc (*s, file) == EOF)
	return 1;
      continue;
    }
    if (fputs (esc, file) == EOF)
      return 1;
  }
  return 0;
}

/** Output image information in HTML
 * @param file	the output stream
 * @param mtime	file modification time
 * @param dim	image dimensions in a printable format
 * @param comm	image comments
 * @return	zero on success, nonzero on failure
 */
static int
html_info (FILE* file,
	   const char* mtime,
	   const char* dim,
	   const struct comment* comm)
{
  if (html_raw (file, dim) ||
      html_raw (file, " ") ||
      html_esc (file, mtime, 0))
    return 1;
  for (; comm; comm = comm->next)
    if (html_raw (file, " \n") ||
	html_esc (file, comm->text, 0))
      return 1;
  return 0;
}

/** Generate links to alternative-resolution HTML files
 * @param file	the output stream
 * @param ixnam	index.html file name
 * @param thumb	available thumbnail resolutions
 * @param spec	thumbnail specification
 * @return	zero on success, nonzero on failure
 */
static int
html_link_specs (FILE* file,
		 const char* ixnam,
		 const struct thumb_spec* thumb,
		 const struct thumb_spec* spec)
{
  const struct thumb_spec* specs;
  for (specs = thumb; specs; specs = specs->next) {
    if (spec == specs)
      continue;
    if (html_raw (file, "<link title=\"") ||
	html_esc (file, specstr (specs), 0) ||
	html_raw (file, "\" rel=\"alternate\" href=\"../") ||
	html_uri (file, specs->thumbdir) ||
	html_raw (file, "/") ||
	html_uri (file, ixnam) ||
	html_raw (file, "\">\n"))
      return 1;
  }
  return 0;
}

/** Generate anchors to alternative-resolution HTML files
 * @param file	the output stream
 * @param ixnam	index.html file name
 * @param thumb	available thumbnail resolutions
 * @param spec	thumbnail specification
 * @return	zero on success, nonzero on failure
 */
static int
html_a_specs (FILE* file,
	      const char* ixnam,
	      const struct thumb_spec* thumb,
	      const struct thumb_spec* spec)
{
  const struct thumb_spec* specs;
  for (specs = thumb; specs; specs = specs->next) {
    if (spec == specs
	? (html_esc (file, specstr (specs), 0) ||
	   html_raw (file, "\n"))
	: (html_raw (file, "<a href=\"../") ||
	   html_uri (file, specs->thumbdir) ||
	   html_raw (file, "/") ||
	   html_uri (file, ixnam) ||
	   html_raw (file, "\">") ||
	   html_esc (file, specstr (specs), 0) ||
	   html_raw (file, "</a>\n")))
      return 1;
  }
  return 0;
}

/** Generate a link to a related directory listing
 * @param file	the output stream
 * @param rel	the relation (0="top")
 * @param spec	the thumbnail directory name
 * @param dir	the directory name
 * @param ixnam	index.html file name
 * @return	zero on success, nonzero on failure
 */
static int
html_link (FILE* file,
	   const char* rel,
	   const char* spec,
	   const char* dir,
	   const char* ixnam)
{
  {
    char* s = strrchr (dir, '/');
    if (s)
      dir = s + 1;
  }
  return
    html_raw (file, "<link title=\"") ||
    html_esc (file, dir, 0) ||
    html_raw (file, "\" rel=\"") ||
    html_raw (file, rel ? rel : "top") ||
    html_raw (file, "\" href=\"../../") ||
    (rel && (html_uri (file, dir) ||
	     html_raw (file, "/"))) ||
    html_uri (file, spec) ||
    html_raw (file, "/") ||
    html_uri (file, ixnam) ||
    html_raw (file, "\">\n");
}

/** Generate link to file in the root directory
 * @param file	the output stream
 * @param dir	the subdirectory relative to the root
 * @param css	index.css file name
 */
static int
html_uriref (FILE* file,
	     const char* dir,
	     const char* css)
{
  if (*css == '/' || strstr (css, "://")) /* absolute link */
    return html_raw (file, css);
  else {
    unsigned num_parents;
    const char* s;
    for (num_parents = 1, s = dir; *s; s++)
      if (*s == '/')
	num_parents++;
    while (num_parents--)
      if (html_raw (file, "../"))
	return 1;
    return html_uri (file, css);
  }
}

/** Generate links to parent directories
 * @param file	the output stream
 * @param dir	the directory name
 * @param base	the base name of dir
 * @param ixnam	index.html file name
 * @param spec	the thumbnail directory name
 * @return	zero on success, nonzero on failure
 */
static int
html_parent_links (FILE* file,
		   const char* dir,
		   const char* base,
		   const char* ixnam,
		   const char* spec)
{
  unsigned num_parents;
  const char* s;
  char* e;
  for (num_parents = 0, s = dir; *s; s++)
    if (*s == '/')
      num_parents++;
  for (s = dir; num_parents--; s = e + 1) {
    unsigned i;
    e = strchr (s, '/');
    if (html_raw (file, "<a href=\""))
      return 1;
    for (i = num_parents + 2; i--; )
      if (html_raw (file, "../"))
	return 1;
    if (html_uri (file, spec) ||
	html_raw (file, "/") ||
	html_uri (file, ixnam) ||
	html_raw (file, "\">") ||
	html_esc (file, s, e) ||
	html_raw (file, "</a>/"))
      return 1;
  }
  return 0;
}

/** Generate links to child directories
 * @param file	the output stream
 * @param tree	the directory tree
 * @param ixnam	index.html file name
 * @param spec	the thumbnail directory name
 * @param blen	base directory name length
 * @return	zero on success, nonzero on failure
 */
static int
html_child_links (FILE* file,
		  const struct dirtree* tree,
		  const char* ixnam,
		  const char* spec,
		  unsigned blen)
{
  unsigned i;
  if (!tree->n_dirs)
    return 0;
  if (html_raw (file, "<dl>\n"))
    return 1;
  for (i = 0; i < tree->n_dirs; i++) {
    const char* base = strrchr (tree->dirs[i].name, '/');
    if (base)
      base++;
    else
      base = tree->dirs[i].name;
    if (html_raw (file, "<dt><a href=\"../") ||
	html_uri (file, tree->dirs[i].name + blen + 1) ||
	html_raw (file, "/") ||
	html_uri (file, spec) ||
	html_raw (file, "/") ||
	html_uri (file, ixnam) ||
	html_raw (file, "\">") ||
	html_esc (file, base, 0) ||
	html_raw (file, "</a>"))
      return 1;
    if (tree->dirs[i].n_files)
      fprintf (file, " (%u)", tree->dirs[i].n_files);
    if (html_raw (file, "</dt>\n"))
      return 1;
    if (tree->dirs[i].n_dirs) {
      if (html_raw (file, "<dd>") ||
	  html_child_links (file, &tree->dirs[i], ixnam, spec, blen) ||
	  html_raw (file, "</dd>\n"))
	return 1;
    }
  }
  return html_raw (file, "</dl>");
}

/** Dump the HTML code
 * @param file	the output stream
 * @param tree	the directory
 * @param level	number of '/' characters in the root directory name
 * @param info	image dimensions
 * @param comm	image comments
 * @param thumb	available thumbnail resolutions
 * @param spec	the thumbnail resolution
 * @param html	index.html file name
 * @param css	index.css file name or URI (optional)
 * @param icon	"icon" file name or URI (optional)
 * @param msico	"shortcut icon" file name or URI (optional)
 * @param top	name of parent directory
 * @param prev	name of previous directory
 * @param next	name of next directory
 * @return	zero on success, nonzero on failure
 */
int
dumphtml (FILE* file,
	  const struct dirtree* tree,
	  unsigned level,
	  const struct imageinfo* info,
	  const struct comment*const* comm,
	  const struct thumb_spec* thumb,
	  const struct thumb_spec* spec,
	  const char* html,
	  const char* css,
	  const char* icon,
	  const char* msico,
	  const char* top,
	  const char* prev,
	  const char* next)
{
  unsigned i;
  /** file name */
  char* fn;
  /** root directory name */
  const char* rootname;
  /** length of file name */
  unsigned fnlen = strlen (tree->name) + 1;
  /** length of the thumbnail directory name */
  unsigned speclen = strlen (spec->thumbdir) + 1;
  const char* title = strrchr (tree->name, '/');
  if (title)
    title++;
  else
    title = tree->name;
  for (rootname = tree->name; level && *rootname; rootname++)
    if (*rootname == '/')
      level--;
  if (html_raw (file,
		"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n"
		"<html>\n"
		"<head>\n"
		"<title>") ||
      html_esc (file, rootname, 0) ||
      html_raw (file, " (") ||
      html_esc (file, specstr (spec), 0) ||
      html_raw (file, ")</title>\n"
		"<meta http-equiv=\"Content-Type\""
		" content=\"text/html; charset=iso-8859-1\">\n"
		"<meta name=\"Generator\" content=\"pHoToMoLo 1.0\">\n"
		"<link rel=\"start\" href=\"") ||
      html_uriref (file, rootname, html) ||
      html_raw (file, "\">\n"))
    return 1;
  if (css &&
      (html_raw (file, "<link rel=\"stylesheet\" href=\"") ||
       html_uriref (file, rootname, css) ||
       html_raw (file, "\">\n")))
    return 1;
  if (icon &&
      (html_raw (file, "<link rel=\"icon\" href=\"") ||
       html_uriref (file, rootname, icon) ||
       html_raw (file, "\">\n")))
    return 1;
  if (msico &&
      (html_raw (file, "<link rel=\"SHORTCUT ICON\" href=\"") ||
       html_uriref (file, rootname, msico) ||
       html_raw (file, "\">\n")))
    return 1;
  if ((top && html_link (file, 0, spec->thumbdir, top, html)) ||
      (prev && html_link (file, "prev", spec->thumbdir, prev, html)) ||
      (next && html_link (file, "next", spec->thumbdir, next, html)) ||
      html_link_specs (file, html, thumb, spec) ||
      html_raw (file,
		"</head>\n\n"
		"<body>\n<h1>") ||
      html_parent_links (file, rootname, title, html, spec->thumbdir) ||
      html_esc (file, title, 0) ||
      html_raw (file, "</h1>\n"
		"<table><tr>\n"
		"<td>") ||
      html_child_links (file, tree, html, spec->thumbdir,
			strlen (tree->name)) ||
      html_raw (file, "</td>\n"
		"<td><p>") ||
      html_a_specs (file, html, thumb, spec) ||
      html_raw (file, "</p>\n"
		"<p class=\"img\">"))
    return 1;

  if (!(fn = malloc (fnlen + speclen + 1))) {
  memory:
    free (fn);
    fputs ("out of memory\n", stderr);
    return 1;
  }
  /* prepare the thumbnail file name */
  memcpy (fn, tree->name, fnlen - 1);
  memcpy (fn + fnlen, spec->thumbdir, speclen - 1);
  fn[fnlen - 1] = fn[fnlen + speclen - 1] = '/';

  /* write the descriptions */
  for (i = 0; i < tree->n_files; i++) {
    /** file modification time */
    char mtime[20];
    /** image dimensions */
    char dim[20];

    if (!info[i].height || !info[i].width)
      continue; /* skip invalid file */
    sprintf (dim, "%u\327%u", info[i].width, info[i].height);
    if (!strftime (mtime, sizeof mtime, "%Y-%m-%d %H:%M:%S",
		   localtime (&tree->files[i].mtime)))
      *mtime = 0;
    if (html_raw (file, "<a href=\"../") ||
	html_uri (file, tree->files[i].name) ||
	html_raw (file, "\"><img src=\"") ||
	html_uri (file, tree->files[i].name) ||
	html_raw (file, "\"\nalt=\"[") ||
	html_info (file, mtime, dim, comm[i]) ||
	html_raw (file, "]\"\ntitle=\"") ||
	html_info (file, mtime, dim, comm[i]) ||
	html_raw (file, "\"")) {
    error:
      free (fn);
      return 1;
    }
    else {
      /** thumbnail image information */
      struct imageinfo thumb_info;
      /** thumbnail file */
      FILE* f;
      /** thumbnail file name length */
      unsigned namelen = strlen (tree->files[i].name) + 1;
      char* s = realloc (fn, fnlen + speclen + namelen);
      if (!s)
	goto memory;
      fn = s;
      memcpy (fn + fnlen + speclen, tree->files[i].name, namelen);
      if (!(f = fopen (fn, "rb"))) {
	fputs (fn, stderr);
	perror (": open (reading)");
	goto error;
      }
      s = (char*) readinfo (&thumb_info, 0, f);
      fclose (f);
      if (s) {
	fputs (fn, stderr);
	fputs (": ", stderr), fputs (s, stderr);
	putc ('\n', stderr);
      }
      else
	fprintf (file, "\nwidth=\"%u\" height=\"%u\"",
		 thumb_info.width, thumb_info.height);
    }

    if (html_raw (file, "></a>\n"))
      goto error;
  }
  free (fn);
  return html_raw (file, "</p></td>\n"
		   "</tr></table>"
		   "</body></html>\n");
}
