/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.util.document;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.model.Channel;
import net.sourceforge.rssowl.model.Enclosure;
import net.sourceforge.rssowl.model.NewsItem;
import net.sourceforge.rssowl.util.DateParser;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.URLShop;

import org.jdom.Text;

import com.lowagie.text.Anchor;
import com.lowagie.text.Cell;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfDestination;
import com.lowagie.text.pdf.PdfOutline;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.rtf.RtfWriter;

import java.awt.Color;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;

/**
 * Class to generate PDF / RTF / HTML from a Newsfeed
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class DocumentGenerator {

  /** Constant to indicate a HTML formatted document */
  public static final int HTML_FORMAT = 1;

  /** Constant to indicate a PDF formatted document */
  public static final int PDF_FORMAT = 0;

  /** Constant to indicate a RTF formatted document */
  public static final int RTF_FORMAT = 2;

  /** Value to decide wether a Table fits a page or not */
  private static final int MAX_CHARS_PER_PAGE = 3000;

  /** Tokeniter to split Feed Title and News Title */
  private static final String TOKENIZER = " =;;= ";

  /** Document to write */
  private Document document;

  /** The config for the document containing Fonts */
  private DocumentConfig documentConfig;

  /** The File to write the Document into */
  private FileOutputStream fileOS;

  /** Chosen format (PDF, RTF or HTML) */
  private int format;

  /** The Channel to generate the PDF / RTF / HTML from */
  private Channel rssChannel;

  /** Store Chapters for each newsfeed that is generated to document */
  Hashtable chapters;

  /**
   * Instantiate a new DocumentGenerator
   * 
   * @param rssChannel The selected Channel
   * @param fileOS File to write document into
   * @param format PDF, RTF or HTML
   * @throws DocumentException If an error occurs
   */
  public DocumentGenerator(Channel rssChannel, FileOutputStream fileOS, int format) throws DocumentException {
    this.rssChannel = rssChannel;
    this.format = format;
    this.fileOS = fileOS;

    /** The HTML Format does not use iText */
    if (format != HTML_FORMAT) {
      chapters = new Hashtable();
      document = new Document();
      documentConfig = DocumentConfig.getInstance();
      initDocument(fileOS);
    }
  }

  /**
   * Get a suitable file format name for the given format.
   * 
   * @param format Any of the allowed formats PDF, RTF or HTML
   * @return String The format as file name
   */
  public static String formatToString(int format) {
    if (format == PDF_FORMAT)
      return "pdf";
    else if (format == RTF_FORMAT)
      return "rtf";
    else if (format == HTML_FORMAT)
      return "html";
    return "pdf";
  }

  /**
   * Write header and a single news to the document
   * 
   * @param newsItemOrder The sort order of the selected newsfeed
   * @throws DocumentException In case of an Error.
   * @throws IOException In case of an Error.
   */
  public void createDocument(ArrayList newsItemOrder) throws DocumentException, IOException {

    /** Use the HTMLExporter which is independant from iText for HTML */
    if (format == HTML_FORMAT)
      new HTMLExporter(rssChannel, newsItemOrder, fileOS).create();

    /** Use iText for Documents in PDF and RTF */
    else {
      DocumentException documentException = null;

      /** Meta Infos */
      writeMetaInfos();

      try {

        /** Footer */
        writeFooter();

        /** Open document to write */
        document.open();

        /** Write the news */
        writeNews(newsItemOrder);
      }

      /** An error has occured */
      catch (DocumentException e) {
        documentException = e;
      }

      /** Close the document */
      document.close();

      /** Throw the exception if one occured */
      if (documentException != null)
        throw documentException;
    }
  }

  /**
   * Init the document to PDF, HTML or RTF and with the given file
   * 
   * @param file The path to the file where to save the document after
   * generation
   * @throws DocumentException If an error occurs
   */
  private void initDocument(FileOutputStream file) throws DocumentException {

    /** Create new PDF document */
    if (format == PDF_FORMAT) {

      /** In order to create an Outline, we listen for Generic Tags */
      PdfWriter.getInstance(document, file).setPageEvent(new PdfPageEventHelper() {
        public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text) {

          /** Get Root Outline Object */
          PdfContentByte cb = writer.getDirectContent();
          PdfOutline root = cb.getRootOutline();

          /** Get Feed Title and News Title from generic Tag */
          String feedTitle = text.split(TOKENIZER)[0];
          String newsTitle = text.split(TOKENIZER)[1];

          /**
           * A new Feed was reached, create Chapter for it and let it be opened.
           * Place News Title as Sub-Chapter to that Chapter.
           */
          if (!chapters.containsKey(feedTitle)) {
            PdfOutline chapter = new PdfOutline(root, new PdfDestination(PdfDestination.FIT, rect.top(-20)), feedTitle, true);
            chapters.put(feedTitle, chapter);
            new PdfOutline(chapter, new PdfDestination(PdfDestination.FIT, rect.top(-20)), newsTitle);
          }

          /**
           * News Item from Feed that already has a chapter. Add the news to the
           * Chapter of the Feed
           */
          else {
            new PdfOutline((PdfOutline) chapters.get(feedTitle), new PdfDestination(PdfDestination.FIT, rect.top(-20)), newsTitle);
          }
        }
      });

    }

    /** Create a new RTF document */
    else if (format == RTF_FORMAT)
      RtfWriter.getInstance(document, file);
  }

  /**
   * Write footer into document
   */
  private void writeFooter() {
    Chunk footerChunk = new Chunk(DateParser.formatDate() + " - " + GUI.i18n.getTranslation("DOCUMENT_GENERATED_FROM") + " (http://www.rssowl.org)", documentConfig.getItalicFont());
    HeaderFooter footer = new HeaderFooter(new Phrase(footerChunk), false);
    footer.setAlignment(Element.ALIGN_CENTER);
    footer.setBorder(1);
    document.setFooter(footer);
  }

  /** Write some meta informations to the document */
  private void writeMetaInfos() {

    /** Title */
    if (StringShop.isset(rssChannel.getTitle()))
      document.addTitle(rssChannel.getTitle());

    /** Publisher */
    if (StringShop.isset(rssChannel.getManagingEditor()))
      document.addAuthor(rssChannel.getManagingEditor());
    else if (StringShop.isset(rssChannel.getPublisher()))
      document.addAuthor(rssChannel.getPublisher());
    else if (StringShop.isset(rssChannel.getCreator()))
      document.addAuthor(rssChannel.getCreator());

    /** Date */
    document.addCreationDate();

    /** Creator */
    document.addCreator(GUI.i18n.getTranslation("DOCUMENT_GENERATED_FROM") + " (http://www.rssowl.org)");
  }

  /**
   * Write all news to the document
   * 
   * @param newsItemsOrder The sort order of the newsitems
   * @throws DocumentException If an error occurs
   */
  private void writeNews(ArrayList newsItemsOrder) throws DocumentException {

    /** Channel News Items */
    Hashtable newsItems = rssChannel.getItems();

    /** Write all newsitems in their sort order into document */
    for (int a = 0; a < newsItemsOrder.size(); a++) {
      NewsItem rssNewsItem = (NewsItem) newsItems.get(newsItemsOrder.get(a));
      String newsTitle = GUI.i18n.getTranslation("NO_TITLE");

      if (StringShop.isset(rssNewsItem.getTitle()))
        newsTitle = rssNewsItem.getTitle();

      /** Empty Spacer between News Tables */
      document.add(new Paragraph());

      /** Write this newsitem */
      writeNews(rssNewsItem, newsTitle);
    }
  }

  /**
   * Write a newsitem to the document
   * 
   * @param item NewsItem to write
   * @param newsTitle Title of the newsitem
   * @throws DocumentException if an error occurs
   */
  private void writeNews(NewsItem item, String newsTitle) throws DocumentException {

    /** Create the Table to hold the news */
    Table table = new Table(1, 2);
    table.setBorder(0);
    table.setPadding(7);
    table.setWidth((format == RTF_FORMAT) ? 80 : 100);
    table.setBackgroundColor(new Color(246, 246, 246));
    table.setBorderColor(new Color(192, 192, 192));

    /** Cell for the News Title and Feed Information */
    Cell titleCell = new Cell();
    titleCell.setBorderColor(Color.LIGHT_GRAY);

    /** Feed Title */
    Paragraph title = new Paragraph();

    /** If Format is PDF, use Chunks */
    if (format == PDF_FORMAT) {
      Chunk titleChunk = new Chunk(newsTitle);

      /** Assign Newslink to Title if available */
      if (StringShop.isset(item.getLink())) {
        titleChunk.setFont(documentConfig.getLinkFont());
        titleChunk.setAnchor(item.getLink());
      }

      /** News Link not given */
      else {
        titleChunk.setFont(documentConfig.getBoldFont());
      }

      /** Set generic Tag to write Chapters and Sections */
      String feedTitle = (StringShop.isset(item.getNewsfeedTitle())) ? item.getNewsfeedTitle() : GUI.i18n.getTranslation("NO_TITLE");
      titleChunk.setGenericTag(feedTitle + TOKENIZER + newsTitle);
      title.add(titleChunk);
    }

    /** If Format is not PDF, use Anchors */
    else {

      /** Assign Newslink to Title if available */
      if (StringShop.isset(item.getLink())) {
        Anchor titleChunk = new Anchor(newsTitle, documentConfig.getLinkFont());
        titleChunk.setReference(item.getLink());
        title.add(titleChunk);
      }

      /** News Link not given */
      else {
        Anchor titleChunk = new Anchor(newsTitle, documentConfig.getBoldFont());
        title.add(titleChunk);
      }
    }

    /** Check if a publish Date is available */
    StringBuffer subTitleBuf = new StringBuffer();
    if (item.getPubDateParsed() != null)
      subTitleBuf.append(DateParser.formatDate(item.getPubDateParsed(), true, true));
    else if (item.getPubDate() != null)
      subTitleBuf.append(item.getPubDate());

    /** Check if author is available */
    if (StringShop.isset(item.getAuthor())) {
      subTitleBuf.append(subTitleBuf.length() > 0 ? " - " : "");
      subTitleBuf.append(item.getAuthor());
    }

    /** Sub Title container */
    String subTitleContentStr = subTitleBuf.toString();
    Paragraph subTitle = new Paragraph();
    Chunk subTitleChunk = new Chunk(subTitleContentStr, documentConfig.getSubTitleFont());
    subTitle.add(subTitleChunk);

    /** Add Title and Sub Title to Title Cell */
    titleCell.add(title);
    titleCell.add(subTitle);

    /** Add Title Cell to Table */
    table.addCell(titleCell);

    /** Cell containing News description */
    Cell descriptionCell = new Cell();
    descriptionCell.setBorderColor(Color.LIGHT_GRAY);

    /** NewsItem content containing description, comments, source... */
    StringBuffer description = new StringBuffer();

    /** Description */
    if (StringShop.isset(item.getDescription()))
      description.append(Text.normalizeString(item.getDescription()));

    /** Enclosures of the news */
    if (item.getEnclosures() != null && item.getEnclosures().size() > 0) {
      description.append("\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_ENCLOSURE") + ": ");

      /** Write all enclosures to document */
      for (int c = 0; c < item.getEnclosures().size(); c++) {
        Enclosure enclosure = (Enclosure) item.getEnclosures().get(c);
        description.append("\n" + enclosure.getUrl() + " (" + enclosure.getType() + " / " + enclosure.getLength(true) + ")");
      }
    }

    /** Source of the news */
    if (StringShop.isset(item.getSource()))
      description.append("\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_SOURCE") + ": " + item.getSource());

    /** Comments of the news */
    if (item.getComments() != null && item.getComments().size() > 0) {
      description.append("\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_COMMENTS") + ":\n");

      /** Write all comments to the document */
      for (int b = 0; b < item.getComments().size(); b++)
        if (item.getComments().get(b) != null && !item.getComments().get(b).equals(""))
          description.append("\n" + item.getComments().get(b));
    }

    /** In case description is empty */
    if (description.length() == 0)
      description.append(GUI.i18n.getTranslation("NEWS_NO_DESCRIPTION"));

    /** Description Container */
    Paragraph descriptionContainer = new Paragraph();

    /** Strip all Tags from the Text */
    Chunk descriptionChunk = new Chunk(StringShop.stripTags(description.toString()), documentConfig.getNormalFont());

    /** Add Chunk to Container */
    descriptionContainer.add(descriptionChunk);

    /** Add Description to Description Cell */
    descriptionCell.add(descriptionContainer);

    /** Check if the Newsfeed Title is available in case of an Aggregation */
    if (rssChannel.isAggregatedCat() && StringShop.isset(item.getNewsfeedTitle())) {
      String feedTitle = item.getNewsfeedTitle();
      Paragraph channelTitleContainer = new Paragraph();
      Chunk channelTitleChunk = new Chunk("\n" + feedTitle, documentConfig.getSmallItalicFont());

      /** Set Anchor in case given by the Newsitem */
      if (URLShop.looksLikeURL(item.getNewsfeedHomepage()))
        channelTitleChunk.setAnchor(item.getNewsfeedHomepage());

      channelTitleContainer.add(channelTitleChunk);

      /** Add Author to Description Cell */
      descriptionCell.add(channelTitleContainer);
    }

    /** Add Description Cell to Table */
    table.addCell(descriptionCell);

    /** Decide wether the table should fit the page */
    table.setTableFitsPage(descriptionChunk.content().length() < MAX_CHARS_PER_PAGE && format != RTF_FORMAT);

    /** Add Table to Document */
    document.add(table);
  }
}