/*
** Module   : TableHelper.java
** Abstract : base class for generating HTML pages with tables
**
** Copyright (c) 2009-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 GES 20090204 Base class for generating HTML pages with tables.
** 002 CA  20210902 Allow the content of 'pre' tags to be wrapped.
** 003 CA  20210906 The content for description and failure is wrapped only for web tests. 
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.html;

import java.io.*;
import com.goldencode.io.*;


/**
 * Base class for generating HTML pages with tables.
 */
public class TableHelper
implements HtmlConstants
{
   /** Olive color specification. */
   public static final String OLIVE = "#999900";
   
   /** Gold color specification. */
   public static final String GOLD = "#FFCC66";
   
   /** White color specification. */
   public static final String WHITE = "#FFFFFF";
   
   /** Normal text processing in fields. */
   public static final int NORMAL    = 0;
   
   /** Bold text processing in fields. */
   public static final int BOLD      = 1;
   
   /** Uses a preformatted element for text processing in fields. */
   public static final int PREFORMAT = 2;
   
   /** Uses a preformatted element for text processing in fields, wrapped. */
   public static final int PREFORMAT_WRAP = 3;
   
   /**
    * Drives the writing of the header of a report, includes the standard
    * top of the document and then adds a single centered header element
    * to the body (with the title).
    *
    * @param    out
    *           The output destination.
    * @param    title
    *           The preformatted title text.
    */      
   public void writeHeader(IndentingWriter out, String title)
   {  
      // generate the top of the document
      writeSimpleHeader(out, title);
      
      writeHeading(out, 2, title);
   }
      
   /**
    * Drives the writing of a header level 2 sub-title in a single
    * centered header element.
    *
    * @param    out
    *           The output destination.
    * @param    level
    *           Heading level.
    * @param    title
    *           The preformatted title text.
    */      
   public void writeHeading(IndentingWriter out, int level, String title)
   {
      writeCenterStart(out);
      write(out, HtmlHelper.heading(level, title));
      writeCenterEnd(out);
   }
      
   /**
    * Start a center element.
    *
    * @param    out
    *           The output destination.
    */      
   public void writeCenterStart(IndentingWriter out)
   {
      write(out, HtmlHelper.startCenter());
      out.increment();
   }
      
   /**
    * Start a center element.
    *
    * @param    out
    *           The output destination.
    */      
   public void writeCenterEnd(IndentingWriter out)
   {
      out.decrement();
      write(out, HtmlHelper.endCenter());
   }
      
   /**
    * Write a paragraph element and a following horizontal rule to the
    * given stream.
    *
    * @param    out
    *           The output destination.
    */
   public void writeSeparator(Writer out)
   {
      write(out, HtmlHelper.p());
      write(out, HtmlHelper.hr());
   }
   
   /**
    * Actual worker that writes the top of the header of a report, it does
    * not add any elements to the body although it sets the default
    * background color for the body.
    * <p>
    * This method implements HTML entity conversion to ensure that all
    * characters can be properly rendered.  The following subset are
    * converted:
    * <p>
    * &amp;
    * &quot;
    * &lt;
    * &gt;
    * &apos;
    *
    * @param    out
    *           The output destination.
    * @param    title
    *           The preformatted title text.
    */      
   public void writeSimpleHeader(IndentingWriter out, String title)
   {
      write(out, HtmlHelper.startHtml());
      out.increment();
      write(out, HtmlHelper.startHead());
      out.increment();
      write(out, HtmlHelper.getEncoding());
      write(out, HtmlHelper.title(title));
      write(out, HtmlHelper.writeWrapStyle());
      out.decrement();
      write(out, HtmlHelper.endHead());
      write(out, HtmlHelper.startBody(WHITE));
      out.increment();
   }
   
   /**
    * Write the common end of an HTML file.
    *
    * @param    out
    *           The output destination.
    */      
   public void writeSimpleFooter(IndentingWriter out)
   {
      // output the standard end of the file
      out.decrement();
      write(out, HtmlHelper.endBody());
      out.decrement();
      write(out, HtmlHelper.endHtml());
   }
   
   /**
    * Write the standard opening tags for a table.
    *
    * @param    out
    *           The output destination.
    */
   public void writeTableOpen(IndentingWriter out)
   {
      writeTableOpen(out, 100, 1);         
   }

   /**
    * Write the standard opening tags for a table.
    *
    * @param    out
    *           The output destination.
    * @param    width
    *           Percent of the page width the table should occupy.
    * @param    border
    *           The border thickness (0 for an invisible border).
    */
   public void writeTableOpen(IndentingWriter out,
                              int             width,
                              int             border)
   {         
      write(out, HtmlHelper.startTable(width, border));
      out.increment();
   }

   /**
    * Write the standard closing tags for a table.
    *
    * @param    out
    *           The output destination.
    */
   public void writeTableClose(IndentingWriter out)
   {
      out.decrement();         
      write(out, HtmlHelper.endTable());
      write(out, HtmlHelper.br());
   }

   /**
    * Write the opening row tag with optional background color support.
    * <p>
    * After opening the row, the indent level will be incremented.
    *
    * @param    out
    *           The output destination.
    * @param    bgcolor
    *           If <code>true</code>, the background of this row will not
    *           use the table or document background color.
    * @param    rgb
    *           A string starting with a '#' character and followed by 6
    *           hexidecimal digits specifying an RGB color.  If
    *           <code>null</code> this class' public constant GOLD will
    *           be used (which is RGB 255,204,102 or a light yellow).
    */
   public void writeRowStart(IndentingWriter out,
                             boolean         bgcolor,
                             String          rgb)
   {
      String contents = null;
      
      if (bgcolor)
      {
         contents = HtmlHelper.startRow(rgb == null ? GOLD : rgb);
      }
      else
      {
         contents = HtmlHelper.startRow();
      }
      
      write(out, contents);
      out.increment();
   }        
   
   /**
    * Write the closing row tag and decrement the indent level.
    *
    * @param    out
    *           The output destination.
    */
   public void writeRowEnd(IndentingWriter out)
   {
      out.decrement();
      write(out, HtmlHelper.endRow());
   }        
   
   /**
    * Write a single cell of text into the current row.  Alignment is
    * specified using one of the following:
    * <p>
    * <pre>
    * VALIGN_TOP
    * VALIGN_MIDDLE
    * VALIGN_BOTTOM
    * HALIGN_LEFT
    * HALIGN_CENTER
    * HALIGN_RIGHT
    * </pre>
    * <p>
    * This method forces HTML entity conversion to ensure that all
    * characters can be properly rendered.  The following subset are
    * converted at this time:
    * <p>
    * &amp;
    * &quot;
    * &lt;
    * &gt;
    * &apos;
    *
    * @param    out
    *           The output destination.
    * @param    text
    *           Contents of the cell.
    * @param    vAlign
    *           Vertical alignment of the cell.
    * @param    hAlign    
    *           Horizontal alignment of the cell.
    * @param    type
    *           Normal, Bold or Preformatted text if the type is set to
    *           <code>NORMAL, BOLD or PREFORMAT</code> respectively.
    */
   public void writeField(IndentingWriter out,
                          String          text, 
                          String          vAlign,
                          String          hAlign,
                          int             type)
   {
      writeField(out, text, vAlign, hAlign, type, false);
   }
   
   /**
    * Write a single cell of text into the current row.  Alignment is
    * specified using one of the following:
    * <p>
    * <pre>
    * VALIGN_TOP
    * VALIGN_MIDDLE
    * VALIGN_BOTTOM
    * HALIGN_LEFT
    * HALIGN_CENTER
    * HALIGN_RIGHT
    * </pre>
    * <p>
    * At the user's request, this method will implement HTML entity
    * conversion to ensure that all characters can be properly rendered.
    * The following subset are converted at this time:
    * <p>
    * &amp;
    * &quot;
    * &lt;
    * &gt;
    * &apos;
    *
    * @param    out
    *           The output destination.
    * @param    text
    *           Contents of the cell.
    * @param    vAlign
    *           Vertical alignment of the cell.
    * @param    hAlign    
    *           Horizontal alignment of the cell.
    * @param    type
    *           Normal, Bold or Preformatted text if the type is set to
    *           <code>NORMAL, BOLD or PREFORMAT</code> respectively.
    * @param    noFilter
    *           If <code>true</code> AND if the <code>type</code> is
    *           <code>NORMAL</code> or <code>PREFORMAT</code>, NO
    *           FILTERING of the text will be done.
    */
   public void writeField(IndentingWriter out,
                          String          text, 
                          String          vAlign,
                          String          hAlign,
                          int             type,
                          boolean         noFilter)
   {
      if (type == NORMAL)
      {
         // fast path
         write(out, HtmlHelper.field(hAlign, vAlign, text, !noFilter));
      }
      else
      {
         String start = HtmlHelper.startField(hAlign, vAlign);
         String end   = HtmlHelper.endField();
         
         if (type == BOLD)
         {
            write(out, start);
            out.increment();
            
            StringBuilder sb = new StringBuilder();
            sb.append(HtmlHelper.strong(text)).append("\n");
            
            write(out, sb.toString());
            
            out.decrement();
            write(out, end);
         }
         else if (type == PREFORMAT || type == PREFORMAT_WRAP)
         {
            int level = out.getIndent();
            
            write(out, start);
            out.setIndent(0);
            
            text = noFilter ? text : HtmlHelper.filter(text);
            write(out, type == PREFORMAT ? HtmlHelper.pre(text) : HtmlHelper.preWrap(text));
            
            out.setIndent(level);
            write(out, end);
         }
         else
         {
            String err = String.format("Unknown content type '%d'!", type);
            throw new RuntimeException(err);
         }
      }
   }
   
   /**
    * Creates a file-backed printable writer for storing report output.
    * As long as the filename is not a directory, it is deleted if it
    * exists and a new writer is created and returned. The path to the file
    * must already exist.
    *
    * @param    fname
    *           Input file name.
    *
    * @return   An open writer.
    */
   public IndentingWriter createStream(String fname)
   throws IllegalArgumentException
   {
      IndentingWriter out = null;
      
      // build a file
      File file = new File(fname);
      
      // if this filename exists as a directory, we can't save results
      if (file.isDirectory())
      {   
         throw new IllegalArgumentException(fname + " is a directory!");
      }
      
      // remove the target file if it exists
      if (file.exists())
      {
         file.delete();
      }
       
      try
      {
         FileOutputStream   fos = new FileOutputStream(file);
         OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
         BufferedWriter     bw  = new BufferedWriter(osw);
         out = new IndentingWriter(bw);
      }
      catch (Exception exc)
      {
         throw new IllegalArgumentException("Cannot create stream for " +
                                            fname + "(" + exc.toString() +
                                            ")!");
      }
      
      return out;             
   }
   
   /**
    * Handle output processing in a consistent manner by writing the
    * text to the given stream. Any failures will be transformed into
    * runtime exceptions.
    *
    * @param    out
    *           The output destination.
    * @param    text
    *           The content to write.
    *
    * @throws   RuntimeException
    *           On any failure.
    */
   public void write(Writer out, String text)
   {
      try
      {
         byte[] encoded = text.getBytes("UTF-8");
         String utf8 = new String(encoded, "UTF-8");
         out.write(utf8);
      }
      
      catch (IOException ioe)
      {
         throw new RuntimeException("I/O Failure on WRITE!", ioe);
      }
   }
   
   /**
    * Safely close the given writer.
    *
    * @param    out
    *           The output destination.
    */
   public void close(Writer out)
   {
      try
      {
         if (out != null)
            out.close();
      }
      
      catch (IOException ioe)
      {
         // ignore
      }
   }
   
   /**
    * Convert any newlines in the input string to HTML break tags.
    *
    * @param    input
    *           Data to check.
    *
    * @return   The data with HTML line breaks.
    */
   public String honorNewLine(String input)
   {
      return input.replaceAll("\n", "<br>");
   }
   
   /**
    * Return the input string if it is not <code>null</code> and not empty,
    * otherwise return a safe string that will display in a table as a blank
    * table cell.
    *
    * @param    input
    *           Data to check.
    *
    * @return   Safe version of the data.
    */
   public String makeNonEmpty(String input)
   {
      return (input == null || input.length() == 0) ? "&nbsp;<br>" : input;
   }
}
