// -*- C++ -*-
/* Copyright (C) 2002-2018  Free Software Foundation, Inc.
 *
 *  Gaius Mulley (gaius@glam.ac.uk) wrote html-table.cpp
 *
 *  html-table.h
 *
 *  provides the methods necessary to handle indentation and tab
 *  positions using html tables.
 */

/*
This file is part of groff.

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

groff 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.  If not, see <http://www.gnu.org/licenses/>. */

#include "driver.h"
#include "stringclass.h"
#include "cset.h"
#include "html-table.h"
#include "ctype.h"
#include "html.h"
#include "html-text.h"

#if !defined(TRUE)
#   define TRUE  (1==1)
#endif
#if !defined(FALSE)
#   define FALSE (1==0)
#endif

extern html_dialect dialect;


tabs::tabs ()
  : tab(NULL)
{
}

tabs::~tabs ()
{
  delete_list();
}

/*
 *  delete_list - frees the tab list and sets tab to NULL.
 */

void tabs::delete_list (void)
{
  tab_position *p = tab;
  tab_position *q;

  while (p != NULL) {
    q = p;
    p = p->next;
    delete q;
  }
  tab = NULL;
}

void tabs::clear (void)
{
  delete_list();
}

/*
 *  compatible - returns TRUE if the tab stops in, s, do
 *               not conflict with the current tab stops.
 *               The new tab stops are _not_ placed into
 *               this class.
 */

int tabs::compatible (const char *s)
{
  char align;
  int  total=0;
  tab_position *last = tab;

  if (last == NULL)
    return FALSE;  // no tab stops defined

  // move over tag name
  while ((*s != (char)0) && !isspace(*s))
    s++;

  while (*s != (char)0 && last != NULL) {
    // move over white space
    while ((*s != (char)0) && isspace(*s))
      s++;
    // collect alignment
    align = *s;
    // move over alignment
    s++;
    // move over white space
    while ((*s != (char)0) && isspace(*s))
      s++;
    // collect tab position
    total = atoi(s);
    // move over tab position
    while ((*s != (char)0) && !isspace(*s))
      s++;
    if (last->alignment != align || last->position != total)
      return FALSE;

    last = last->next;
  }
  return TRUE;
}

/*
 *  init - scans the string, s, and initializes the tab stops.
 */

void tabs::init (const char *s)
{
  char align;
  int  total=0;
  tab_position *last = NULL;

  clear(); // remove any tab stops

  // move over tag name
  while ((*s != (char)0) && !isspace(*s))
    s++;

  while (*s != (char)0) {
    // move over white space
    while ((*s != (char)0) && isspace(*s))
      s++;
    // collect alignment
    align = *s;
    // move over alignment
    s++;
    // move over white space
    while ((*s != (char)0) && isspace(*s))
      s++;
    // collect tab position
    total = atoi(s);
    // move over tab position
    while ((*s != (char)0) && !isspace(*s))
      s++;
    if (last == NULL) {
      tab = new tab_position;
      last = tab;
    } else {
      last->next = new tab_position;
      last = last->next;
    }
    last->alignment = align;
    last->position = total;
    last->next = NULL;    
  }
}

/*
 *  check_init - define tab stops using, s, providing none already exist.
 */

void tabs::check_init (const char *s)
{
  if (tab == NULL)
    init(s);
}

/*
 *  find_tab - returns the tab number corresponding to the position, pos.
 */

int tabs::find_tab (int pos)
{
  tab_position *p;
  int i=0;

  for (p = tab; p != NULL; p = p->next) {
    i++;
    if (p->position == pos)
      return i;
  }
  return 0;
}

/*
 *  get_tab_pos - returns the, nth, tab position
 */

int tabs::get_tab_pos (int n)
{
  tab_position *p;

  n--;
  for (p = tab; (p != NULL) && (n>0); p = p->next) {
    n--;
    if (n == 0)
      return p->position;
  }
  return 0;
}

char tabs::get_tab_align (int n)
{
  tab_position *p;

  n--;
  for (p = tab; (p != NULL) && (n>0); p = p->next) {
    n--;
    if (n == 0)
      return p->alignment;
  }
  return 'L';
}

/*
 *  dump_tab - display tab positions
 */

void tabs::dump_tabs (void)
{
  int i=1;
  tab_position *p;

  for (p = tab; p != NULL; p = p->next) {
    printf("tab %d is %d\n", i, p->position);
    i++;
  }
}

/*
 *  html_table - methods
 */

html_table::html_table (simple_output *op, int linelen)
  : out(op), columns(NULL), linelength(linelen), last_col(NULL), start_space(FALSE)
{
  tab_stops = new tabs();
}

html_table::~html_table ()
{
  cols *c;
  if (tab_stops != NULL)
    delete tab_stops;
  
  c = columns;
  while (columns != NULL) {
    columns = columns->next;
    delete c;
    c = columns;
  }
}

/*
 *  remove_cols - remove a list of columns as defined by, c.
 */

void html_table::remove_cols (cols *c)
{
  cols *p;

  while (c != NULL) {
    p = c;
    c = c->next;
    delete p;
  }
}

/*
 *  set_linelength - sets the line length value in this table.
 *                   It also adds an extra blank column to the
 *                   table should linelen exceed the last column.
 */

void html_table::set_linelength (int linelen)
{
  cols *p = NULL;
  cols *c;
  linelength = linelen;

  for (c = columns; c != NULL; c = c->next) {
    if (c->right > linelength) {
      c->right = linelength;
      remove_cols(c->next);
      c->next = NULL;
      return;
    }
    p = c;
  }
  if (p != NULL && p->right > 0 && linelength > p->right)
    add_column(p->no+1, p->right, linelength, 'L');
}

/*
 *  get_effective_linelength -
 */

int html_table::get_effective_linelength (void)
{
  if (columns != NULL)
    return linelength - columns->left;
  else
    return linelength;
}

/*
 *  add_indent - adds the indent to a table.
 */

void html_table::add_indent (int indent)
{
  if (columns != NULL && columns->left > indent)
    add_column(0, indent, columns->left, 'L');
}

/*
 *  emit_table_header - emits the html header for this table.
 */

void html_table::emit_table_header (int space)
{
  if (columns == NULL)
    return;

  // dump_table();

  last_col = NULL;
  if (linelength > 0) {
    out->nl();
    out->nl();

    out->put_string("<table width=\"100%\"")
      .put_string(" border=\"0\" rules=\"none\" frame=\"void\"\n")
      .put_string("       cellspacing=\"0\" cellpadding=\"0\"");
    out->put_string(">")
      .nl();
    if (dialect == xhtml)
      emit_colspan();
    out->put_string("<tr valign=\"top\" align=\"left\"");
    if (space) {
      out->put_string(" style=\"margin-top: ");
      out->put_string(STYLE_VERTICAL_SPACE);
      out->put_string("\"");
    }
    out->put_string(">").nl();
  }
}

/*
 *  get_right - returns the right most position of this column.
 */

int html_table::get_right (cols *c)
{
  if (c != NULL && c->right > 0)
    return c->right;
  if (c->next != NULL)
    return c->left;
  return linelength;
}

/*
 *  set_space - assigns start_space. Used to determine the
 *              vertical alignment when generating the next table row.
 */

void html_table::set_space (int space)
{
  start_space = space;
}

/*
 *  emit_colspan - emits a series of colspan entries defining the
 *                 table columns.
 */

void html_table::emit_colspan (void)
{
  cols *b = columns;
  cols *c = columns;
  int   width = 0;

  out->put_string("<colgroup>");
  while (c != NULL) {
    if (b != NULL && b != c && is_gap(b))
      /*
       *   blank column for gap
       */
      out->put_string("<col width=\"")
	.put_number(is_gap(b))
	.put_string("%\" class=\"center\"></col>")
	.nl();
    
    width = (get_right(c)*100 + get_effective_linelength()/2)
	      / get_effective_linelength()
             - (c->left*100 + get_effective_linelength()/2)
		/get_effective_linelength();
    switch (c->alignment) {
    case 'C':
      out->put_string("<col width=\"")
	  .put_number(width)
	  .put_string("%\" class=\"center\"></col>")
	  .nl();
      break;
    case 'R':
      out->put_string("<col width=\"")
	  .put_number(width)
	  .put_string("%\" class=\"right\"></col>")
	  .nl();
      break;
    default:
      out->put_string("<col width=\"")
	  .put_number(width)
	  .put_string("%\"></col>")
	  .nl();
    }
    b = c;
    c = c->next;
  }
  out->put_string("</colgroup>").nl();
}

/*
 *  emit_td - writes out a <td> tag with a corresponding width
 *            if the dialect is html4.
 */

void html_table::emit_td (int percentage, const char *s)
{
  if (percentage) {
    if (dialect == html4) {
      out->put_string("<td width=\"")
	.put_number(percentage)
	.put_string("%\"");
      if (s != NULL)
	out->put_string(s);
      out->nl();
    }
    else {
      out->put_string("<td");
      if (s != NULL)
	out->put_string(s);
      out->nl();
    }
  }
}

/*
 *  emit_col - moves onto column, n.
 */

void html_table::emit_col (int n)
{
  cols *c = columns;
  cols *b = columns;
  int   width = 0;

  // must be a different row
  if (last_col != NULL && n <= last_col->no)
    emit_new_row();

  while (c != NULL && c->no < n)
    c = c->next;

  // can we find column, n?
  if (c != NULL && c->no == n) {
    // shutdown previous column
    if (last_col != NULL)
      out->put_string("</td>").nl();

    // find previous column
    if (last_col == NULL)
      b = columns;
    else
      b = last_col;
    
    // have we a gap?
    if (last_col != NULL) {
      emit_td(is_gap(b), "></td>");
      b = b->next;
    }

    // move across to column n
    while (b != c) {
      // we compute the difference after converting positions
      // to avoid rounding errors
      width = (get_right(b)*100 + get_effective_linelength()/2)
		/ get_effective_linelength()
	      - (b->left*100 + get_effective_linelength()/2)
		  /get_effective_linelength();
      emit_td(width, "></td>");
      // have we a gap?
      emit_td(is_gap(b), "></td>");
      b = b->next;
    }
    width = (get_right(b)*100 + get_effective_linelength()/2)
	      / get_effective_linelength()
	    - (b->left*100 + get_effective_linelength()/2)
		/get_effective_linelength();
    switch (b->alignment) {
    case 'C':
      emit_td(width, " align=center>");
      break;
    case 'R':
      emit_td(width, " align=right>");
      break;
    default:
      emit_td(width);
    }
    // remember column, b
    last_col = b;
  }
}

/*
 *  finish_row -
 */

void html_table::finish_row (void)
{
  int n = 0;
  cols *c;

  if (last_col != NULL) {
    for (c = last_col->next; c != NULL; c = c->next)
      n = c->no;
    
    if (n > 0)
      emit_col(n);
#if 1
    if (last_col != NULL) {
      out->put_string("</td>");
      last_col = NULL;
    }
#endif
    out->put_string("</tr>").nl();
  }
}

/*
 *  emit_new_row - move to the next row.
 */

void html_table::emit_new_row (void)
{
  finish_row();

  out->put_string("<tr valign=\"top\" align=\"left\"");
  if (start_space) {
    out->put_string(" style=\"margin-top: ");
    out->put_string(STYLE_VERTICAL_SPACE);
    out->put_string("\"");
  }
  out->put_string(">").nl();
  start_space = FALSE;
  last_col = NULL;
}

void html_table::emit_finish_table (void)
{
  finish_row();
  out->put_string("</table>");
}

/*
 *  add_column - adds a column. It returns FALSE if hstart..hend
 *               crosses into a different columns.
 */

int html_table::add_column (int coln, int hstart, int hend, char align)
{
  cols *c = get_column(coln);

  if (c == NULL)
    return insert_column(coln, hstart, hend, align);
  else
    return modify_column(c, hstart, hend, align);
}

/*
 *  get_column - returns the column, coln.
 */

cols *html_table::get_column (int coln)
{
  cols *c = columns;

  while (c != NULL && coln != c->no)
    c = c->next;

  if (c != NULL && coln == c->no)
    return c;
  else
    return NULL;
}

/*
 *  insert_column - inserts a column, coln.
 *                  It returns TRUE if it does not bump into
 *                  another column.
 */

int html_table::insert_column (int coln, int hstart, int hend, char align)
{
  cols *c = columns;
  cols *l = columns;
  cols *n = NULL;

  while (c != NULL && c->no < coln) {
    l = c;
    c = c->next;
  }
  if (l != NULL && l->no>coln && hend > l->left)
    return FALSE;	// new column bumps into previous one

  l = NULL;
  c = columns;
  while (c != NULL && c->no < coln) {
    l = c;
    c = c->next;
  }

  if ((l != NULL) && (hstart < l->right))
    return FALSE;	// new column bumps into previous one
  
  if ((l != NULL) && (l->next != NULL) &&
      (l->next->left < hend))
    return FALSE;  // new column bumps into next one

  n = new cols;
  if (l == NULL) {
    n->next = columns;
    columns = n;
  } else {
    n->next = l->next;
    l->next = n;
  }
  n->left = hstart;
  n->right = hend;
  n->no = coln;
  n->alignment = align;
  return TRUE;
}

/*
 *  modify_column - given a column, c, modify the width to
 *                  contain hstart..hend.
 *                  It returns TRUE if it does not clash with
 *                  the next or previous column.
 */

int html_table::modify_column (cols *c, int hstart, int hend, char align)
{
  cols *l = columns;

  while (l != NULL && l->next != c)
    l = l->next;

  if ((l != NULL) && (hstart < l->right))
    return FALSE;	// new column bumps into previous one
  
  if ((c->next != NULL) && (c->next->left < hend))
    return FALSE;  // new column bumps into next one

  if (c->left > hstart)
    c->left = hstart;

  if (c->right < hend)
    c->right = hend;

  c->alignment = align;

  return TRUE;
}

/*
 *  find_tab_column - finds the column number for position, pos.
 *                    It searches through the list tab stops.
 */

int html_table::find_tab_column (int pos)
{
  // remember the first column is reserved for untabbed glyphs
  return tab_stops->find_tab(pos)+1;
}

/*
 *  find_column - find the column number for position, pos.
 *                It searches through the list of columns.
 */

int html_table::find_column (int pos)
{
  int   p=0;
  cols *c;

  for (c = columns; c != NULL; c = c->next) {
    if (c->left > pos)
      return p;
    p = c->no;
  }
  return p;
}

/*
 *  no_columns - returns the number of table columns (rather than tabs)
 */

int html_table::no_columns (void)
{
  int n=0;
  cols *c;

  for (c = columns; c != NULL; c = c->next)
    n++;
  return n;
}

/*
 *  is_gap - returns the gap between column, c, and the next column.
 */

int html_table::is_gap (cols *c)
{
  if (c == NULL || c->right <= 0 || c->next == NULL)
    return 0;
  else
    // we compute the difference after converting positions
    // to avoid rounding errors
    return (c->next->left*100 + get_effective_linelength()/2)
	     / get_effective_linelength()
	   - (c->right*100 + get_effective_linelength()/2)
	       / get_effective_linelength();
}

/*
 *  no_gaps - returns the number of table gaps between the columns
 */

int html_table::no_gaps (void)
{
  int n=0;
  cols *c;

  for (c = columns; c != NULL; c = c->next)
    if (is_gap(c))
      n++;
  return n;
}

/*
 *  get_tab_pos - returns the, nth, tab position
 */

int html_table::get_tab_pos (int n)
{
  return tab_stops->get_tab_pos(n);
}

char html_table::get_tab_align (int n)
{
  return tab_stops->get_tab_align(n);
}


void html_table::dump_table (void)
{
  if (columns != NULL) {
    cols *c;
    for (c = columns; c != NULL; c = c->next) {
      printf("column %d  %d..%d  %c\n", c->no, c->left, c->right, c->alignment);
    }
  } else
    tab_stops->dump_tabs();
}

/*
 *  html_indent - creates an indent with indentation, ind, given
 *                a line length of linelength.
 */

html_indent::html_indent (simple_output *op, int ind, int pageoffset, int linelength)
{
  table = new html_table(op, linelength);

  table->add_column(1, ind+pageoffset, linelength, 'L');
  table->add_indent(pageoffset);
  in = ind;
  pg = pageoffset;
  ll = linelength;
}

html_indent::~html_indent (void)
{
  end();
  delete table;
}

void html_indent::begin (int space)
{
  if (in + pg == 0) {
    if (space) {
      table->out->put_string(" style=\"margin-top: ");
      table->out->put_string(STYLE_VERTICAL_SPACE);
      table->out->put_string("\"");
    }
  }
  else {
    //
    // we use exactly the same mechanism for calculating
    // indentation as html_table::emit_col
    //
    table->out->put_string(" style=\"margin-left:")
      .put_number(((in + pg) * 100 + ll/2) / ll -
		  (ll/2)/ll)
      .put_string("%;");

    if (space) {
      table->out->put_string(" margin-top: ");
      table->out->put_string(STYLE_VERTICAL_SPACE);
    }
    table->out->put_string("\"");
  }
}

void html_indent::end (void)
{
}

/*
 *  get_reg - collects the registers as supplied during initialization.
 */

void html_indent::get_reg (int *ind, int *pageoffset, int *linelength)
{
  *ind = in;
  *pageoffset = pg;
  *linelength = ll;
}
