/*   **********************************************************************  **
 **   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.model;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.thread.AmphetaRateThread;
import net.sourceforge.rssowl.util.DateParser;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.search.SearchDefinition;
import net.sourceforge.rssowl.util.shop.BrowserShop;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.RegExShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.URLShop;
import net.sourceforge.rssowl.util.shop.XMLShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.jdom.Text;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.Vector;

/**
 * Model for a NewsItem. A Channel may contain more than one item.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class NewsItem {

  /** Max. number of characters for the title if description is used as title */
  private static final int MAX_TITLE_LENGTH = 80;

  private String author;
  private String baseUri;
  private String category;
  private Vector comments;
  private String description;
  private Vector enclosures;
  private String guid;

  /** Vector holding all words that should be highlighted */
  private Vector highlightWords;

  private boolean isPartOfAggregation;

  /** TRUE if the user has read this news */
  private boolean isRead;

  private boolean isSearchCaseSensitive;

  private String link;

  /** Vector holding all links from the text */
  private Vector linkList;

  /** Homepage of the Newsfeed this Item is From */
  private String newsFeedHomepage;

  /** Title of the feed this news is from */
  private String newsfeedTitle;

  /** URL of the RSS from where this news is */
  private String newsfeedXmlUrl;

  /** Set by AmphetaRate for feed XML included in news */
  private String origurl;

  private String pubDate;
  private Date pubDateParsed;
  private String publisher;
  private String source;

  /** Attributes describing the news item */
  private String title;

  /** Flag to force an update when viewing */
  private boolean updateNewsView;

  /**
   * Instantiate a new NewsItem
   */
  public NewsItem() {
    this(false);
  }

  /**
   * Instantiate a new NewsItem
   * 
   * @param isPartOfAggregation If TRUE, this NewsItem is showing inside an
   * Category Aggregation.
   */
  public NewsItem(boolean isPartOfAggregation) {
    isRead = false;
    updateNewsView = false;
    this.isPartOfAggregation = isPartOfAggregation;

    if (!GlobalSettings.useBrowserForNewsText)
      linkList = new Vector();
  }

  /**
   * Add a comment to the NewsItem.
   * 
   * @param comment The comment as String.
   */
  public void addComment(String comment) {
    if (comments == null)
      comments = new Vector();

    comments.add(comment);

    /** If the comment is an URL */
    if (linkList != null && !GlobalSettings.useBrowserForNewsText && URLShop.looksLikeURL(comment))
      linkList.add(comment);
  }

  /**
   * Clear the Vector that contains the highlight words for a search.
   */
  public void clearHighlightWords() {
    if (highlightWords != null)
      highlightWords.clear();
  }

  /**
   * Clone settings from this newsitem into the given one
   * 
   * @param newsItem The newsitem to clone the settings into
   */
  public void clone(NewsItem newsItem) {
    newsItem.setAuthor(author);
    newsItem.setCategory(category);
    newsItem.setDescription(description);
    newsItem.setGuid(guid);
    newsItem.setRead(isRead);
    newsItem.setLink(link);
    newsItem.setNewsfeedTitle(newsfeedTitle);
    newsItem.setNewsfeedXmlUrl(newsfeedXmlUrl);
    newsItem.setNewsfeedHomepage(newsFeedHomepage);
    newsItem.setOrigurl(origurl);
    newsItem.setPubDate(pubDate, false);
    newsItem.setPubDateParsed(pubDateParsed);
    newsItem.setPublisher(publisher);
    newsItem.setSource(source);
    newsItem.setTitle(title);

    /** Clone Comments */
    if (comments != null)
      for (int a = 0; a < comments.size(); a++)
        newsItem.addComment((String) comments.get(a));

    /** Clone Enclosures */
    if (enclosures != null)
      for (int a = 0; a < enclosures.size(); a++)
        newsItem.insertEnclosure((Enclosure) enclosures.get(a));
  }

  /**
   * Get the author
   * 
   * @return String Author
   */
  public String getAuthor() {
    return author;
  }

  /**
   * Get the category
   * 
   * @return String Category
   */
  public String getCategory() {
    return category;
  }

  /**
   * Get Vector holding all comments
   * 
   * @return Vector comments
   */
  public Vector getComments() {
    return comments;
  }

  /**
   * Get the description
   * 
   * @return String description
   */
  public String getDescription() {
    return description;
  }

  /**
   * Get a Vector holding all enclosures
   * 
   * @return Vector Enclosures
   */
  public Vector getEnclosures() {
    return enclosures;
  }

  /**
   * Get the GUID
   * 
   * @return String guid
   */
  public String getGuid() {
    return guid;
  }

  /**
   * Get highlighted words
   * 
   * @return Vector Highlighted Words
   */
  public Vector getHighlightWords() {
    return highlightWords;
  }

  /**
   * Get the link
   * 
   * @return String link
   */
  public String getLink() {
    return link;
  }

  /**
   * Get the Link List
   * 
   * @return Vector containing Links
   */
  public Vector getLinkList() {
    return linkList;
  }

  /**
   * @return String The Homepage URL of the Newsfeed this item is in or NULL.
   */
  public String getNewsfeedHomepage() {
    return newsFeedHomepage;
  }

  /**
   * Get the news feed title
   * 
   * @return String The Title of the newsfeed where this item is in
   */
  public String getNewsfeedTitle() {
    return newsfeedTitle;
  }

  /**
   * Get the newsfeed XML URL
   * 
   * @return String newsfeed XML URL
   */
  public String getNewsfeedXmlUrl() {
    return newsfeedXmlUrl;
  }

  /**
   * Get the feed XML url from the newstext
   * 
   * @return Returns the origurl.
   */
  public String getOrigurl() {
    return origurl;
  }

  /**
   * Get the publish date
   * 
   * @return String PubDate
   */
  public String getPubDate() {
    return pubDate;
  }

  /**
   * Get the parsed publish Date
   * 
   * @return Date The publish Date object
   */
  public Date getPubDateParsed() {
    return pubDateParsed;
  }

  /**
   * Get the publisher
   * 
   * @return String Publisher
   */
  public String getPublisher() {
    return publisher;
  }

  /**
   * Check if this newsitem was marked to require an update in the view.
   * 
   * @return boolean TRUE if this news requires an update in the view
   */
  public boolean getRequiresViewUpdate() {
    return updateNewsView;
  }

  /**
   * Get the URL of the XML feed this news item is in. If the newsitem was
   * aggregated from a category, the URL is not correct. In this method, the
   * correct URL is returned, in any way.
   * 
   * @return String The URL of the XML feed this news item is in
   */
  public String getSafeXMLFeedUrl() {

    /** URL of the XML from where this news is */
    String feedurl = getNewsfeedXmlUrl();

    /** Feed URL is unknown */
    if (feedurl == null)
      return null;

    /** If this is a aggregated category, use the feed title to get the XML URL */
    if (isPartOfAggregation)
      feedurl = getNewsfeedTitle();

    /** Feed Title is unknown */
    if (feedurl == null)
      return null;

    /** The URL could be the title of a favorite */
    String str = Category.getLinkForTitle(feedurl);
    if (str != null)
      feedurl = str;

    return feedurl;
  }

  /**
   * Get the source
   * 
   * @return String Source
   */
  public String getSource() {
    return source;
  }

  /**
   * Get the title
   * 
   * @return String title
   */
  public String getTitle() {
    return title;
  }

  /**
   * Add an enclosure to the news item and parse for links
   * 
   * @param rssEnclosure
   */
  public void insertEnclosure(Enclosure rssEnclosure) {
    if (enclosures == null)
      enclosures = new Vector();

    enclosures.add(rssEnclosure);

    /** Parse for links and add to linkList */
    if (!GlobalSettings.useBrowserForNewsText && rssEnclosure.getUrl() != null)
      parseForLinks(rssEnclosure.getUrl());
  }

  /**
   * Insert a word to highlight
   * 
   * @param word The word to highlight
   */
  public void insertHighlightWord(String word) {
    if (highlightWords == null)
      highlightWords = new Vector();

    if (StringShop.isset(word) && !highlightWords.contains(word))
      highlightWords.add(word);

    /** Mark this NewsItem as in need for an update */
    if (!getRequiresViewUpdate())
      setRequiresViewUpdate(true);
  }

  /**
   * Check wether this NewsItem is part of an Category Aggregation.
   * 
   * @return TRUE if this NewsItem is part of an Category Aggregation.
   */
  public boolean isPartOfAggregation() {
    return isPartOfAggregation;
  }

  /**
   * Get state of isRead
   * 
   * @return boolean TRUE if read
   */
  public boolean isRead() {
    return isRead;
  }

  /**
   * Note: Field only used when a search was run on this item's Channel.
   * 
   * @return boolean TRUE if the search was Case Sensitive.
   */
  public boolean isSearchCaseSensitive() {
    return isSearchCaseSensitive;
  }

  /**
   * Lazy check if the given NewsItem is equal to the called one
   * 
   * @param newsItem The newsitem to check for equalness
   * @return boolean TRUE in case both should be equal
   */
  public boolean sameAs(NewsItem newsItem) {

    /** Check for NULL */
    if (newsItem == null)
      return false;

    /** Check Title */
    if (getTitle() != null && !getTitle().equals(newsItem.getTitle()))
      return false;
    else if (getTitle() == null && newsItem.getTitle() != null)
      return false;

    /** Check Link */
    if (getLink() != null && !getLink().equals(newsItem.getLink()))
      return false;
    else if (getLink() == null && newsItem.getLink() != null)
      return false;

    /** Check Feed URL */
    if (getSafeXMLFeedUrl() != null && !getSafeXMLFeedUrl().equals(newsItem.getSafeXMLFeedUrl()))
      return false;
    else if (getSafeXMLFeedUrl() == null && newsItem.getSafeXMLFeedUrl() != null)
      return false;

    /** Check Description */
    if (getDescription() != null && !getDescription().equals(newsItem.getDescription()))
      return false;
    else if (getDescription() == null && newsItem.getDescription() != null)
      return false;

    /** NewsItems should be equal then */
    return true;
  }

  /**
   * Set the author
   * 
   * @param author
   */
  public void setAuthor(String author) {
    this.author = author;
  }

  /**
   * A Base URI may be set if required. It is used from the toHTML-Method.
   * 
   * @param baseUri The baseUri to set.
   */
  public void setBaseUri(String baseUri) {
    this.baseUri = baseUri;
  }

  /**
   * Set the category
   * 
   * @param category
   */
  public void setCategory(String category) {
    this.category = category;
  }

  /**
   * Set the description and parse for links
   * 
   * @param description
   */
  public void setDescription(String description) {
    this.description = description;

    /** Parse for links and add to linkList */
    if (!GlobalSettings.useBrowserForNewsText)
      parseForLinks(description);
  }

  /**
   * Set the GUID
   * 
   * @param guid
   */
  public void setGuid(String guid) {
    this.guid = guid;
  }

  /**
   * Set the link
   * 
   * @param link
   */
  public void setLink(String link) {
    this.link = link;
  }

  /**
   * @param newsFeedHomepage The Homepage URL of the Newsfeed this item is in or
   * NULL.
   */
  public void setNewsfeedHomepage(String newsFeedHomepage) {
    this.newsFeedHomepage = newsFeedHomepage;
  }

  /**
   * Set the Title of the newsfeed where this item is in
   * 
   * @param newsfeedTitle
   */
  public void setNewsfeedTitle(String newsfeedTitle) {
    this.newsfeedTitle = newsfeedTitle;
  }

  /**
   * Set the newsfeed XML URL
   * 
   * @param newsfeedXML
   */
  public void setNewsfeedXmlUrl(String newsfeedXML) {
    this.newsfeedXmlUrl = newsfeedXML;
  }

  /**
   * Set the feed XML url of the newstext
   * 
   * @param origurl The origurl to set.
   */
  public void setOrigurl(String origurl) {
    this.origurl = origurl;
  }

  /**
   * Set the publish date
   * 
   * @param pubDate
   * @param parsePubDate
   */
  public void setPubDate(String pubDate, boolean parsePubDate) {
    this.pubDate = pubDate;

    /** Parse the publish date */
    if (parsePubDate)
      pubDateParsed = DateParser.getDate(pubDate);
  }

  /**
   * Set the parsed Publish Date
   * 
   * @param pubDateParsed The parsed publish Date
   */
  public void setPubDateParsed(Date pubDateParsed) {
    this.pubDateParsed = pubDateParsed;
  }

  /**
   * Set the publisher
   * 
   * @param publisher
   */
  public void setPublisher(String publisher) {
    this.publisher = publisher;
  }

  /**
   * Set state of isRead
   * 
   * @param isRead
   */
  public void setRead(boolean isRead) {
    this.isRead = isRead;
  }

  /**
   * Set wether this newsitem requires an update in the view.
   * 
   * @param updateNewsView TRUE marks this news as required of an update
   */
  public void setRequiresViewUpdate(boolean updateNewsView) {
    this.updateNewsView = updateNewsView;
  }

  /**
   * Note: Field only used when a search was run on this item's Channel.
   * 
   * @param isSearchCaseSensitive TRUE if the search was Case Sensitive.
   */
  public void setSearchCaseSensitive(boolean isSearchCaseSensitive) {
    this.isSearchCaseSensitive = isSearchCaseSensitive;
  }

  /**
   * Set the source and parse for links
   * 
   * @param source The source information
   */
  public void setSource(String source) {
    this.source = source;

    /** If Source is an URL */
    if (linkList != null && !GlobalSettings.useBrowserForNewsText && URLShop.looksLikeURL(source))
      linkList.add(source);

    /** Parse for links and add to linkList */
    else if (!GlobalSettings.useBrowserForNewsText)
      parseForLinks(source);
  }

  /**
   * Set the title to this Newsitem. RSSOwl will try to decode ISO Entites from
   * the Title, and replace all HTML. Do not trim the title to a certain length.
   * 
   * @param title The title of this newsitem
   */
  public void setTitle(String title) {
    setTitle(title, false);
  }

  /**
   * Set the title to this Newsitem. If set so, trim the title to a certain
   * length. HTML is removed and the String normalized to single whitespaces.
   * 
   * @param desiredTitle The title of this newsitem
   * @param trim If TRUE, trim the title to MAX_TITLE_LENGTH
   */
  public void setTitle(String desiredTitle, boolean trim) {
    setTitle(desiredTitle, trim, true);
  }

  /**
   * Set the title to this Newsitem. If set so, trim the title to a certain
   * length. HTML is removed and the String normalized to single whitespaces in
   * dependance of the <code>stripTags</code> flag.
   * 
   * @param desiredTitle The title of this newsitem
   * @param trim If TRUE, trim the title to MAX_TITLE_LENGTH
   * @param stripTags If TRUE, strip HTML Tags
   */
  public void setTitle(String desiredTitle, boolean trim, boolean stripTags) {
    String tmpTitle = desiredTitle;

    /** Remove HTML Tags from the Title */
    if (stripTags)
      desiredTitle = StringShop.stripTags(desiredTitle);

    /** In case the title is now an empty String */
    if (StringShop.isWhiteSpaceOrEmpty(desiredTitle)) {
      desiredTitle = tmpTitle;

      /**
       * Use a custom trimmin rule in this case. The title is a String existing
       * of HTML which can't be stripped, so it must be surrounded by Tags.
       * Search for the last closing Tag beginning with MAX_TITLE_LENGTH and try
       * to trim the title to that position.
       */
      trim = false;
      int lastClosingTagPosition = desiredTitle.lastIndexOf('>', MAX_TITLE_LENGTH);
      if (lastClosingTagPosition > 0)
        desiredTitle = desiredTitle.substring(0, lastClosingTagPosition + 1);
    }

    /** Trim title to MAX_TITLE_LENGTH if set so */
    if (trim)
      desiredTitle = StringShop.hrTrim(desiredTitle, MAX_TITLE_LENGTH);

    /** Normalize Title */
    desiredTitle = Text.normalizeString(desiredTitle);

    /** Apply Title */
    this.title = desiredTitle;
  }

  /**
   * Create the AmphetaRate URL for this newsitem. The URL contains informations
   * about the news.
   * 
   * @return String The URL to rate this news
   */
  public String toAmphetaRateURL() {

    /** Init vars */
    String itemTitle = "";
    String itemDescription = "";
    String itemLink = "";
    String guid = "";
    String xmlUrl;

    /** URL of the XML from where this news is */
    xmlUrl = getNewsfeedXmlUrl();

    /** If this is a aggregated category, use the feed title to get the XML Url */
    if (isPartOfAggregation)
      xmlUrl = getNewsfeedTitle();

    /** The URL could be the title of a favorite */
    if (Category.getLinkForTitle(xmlUrl) != null)
      xmlUrl = Category.getLinkForTitle(xmlUrl);

    /** URL Encode the value */
    xmlUrl = URLShop.urlEncode(xmlUrl);

    /** Title of the news */
    if (getTitle() != null)
      itemTitle = URLShop.urlEncode(getTitle()).replaceAll("\\+", "%20");

    /** Description of the news */
    if (getDescription() != null)
      itemDescription = Text.normalizeString(getDescription());

    /** Guid of the news if available */
    if (getGuid() != null)
      guid = getGuid();

    /** Only submit the first 100 chars of the description */
    if (itemDescription.length() > 100)
      itemDescription = itemDescription.substring(0, 100);

    /** URL Encode value */
    itemDescription = URLShop.urlEncode(itemDescription).replaceAll("\\+", "%20");

    /** Try the Guid if Link is null */
    if (getLink() == null && guid != null && RegExShop.isValidURL(guid))
      itemLink = URLShop.urlEncode(guid);

    /** Use the getLink() method */
    else if (getLink() != null)
      itemLink = URLShop.urlEncode(getLink());

    /** Check if enough informations are given */
    if (itemTitle.equals("") && itemDescription.equals(""))
      return null;
    else if (xmlUrl.equals(""))
      return null;

    /** Create the url to rate */
    String url = "http://amphetarate.sourceforge.net/dinka-add-rating.php?title=" + itemTitle + "&xmlurl=" + xmlUrl + "&desc=" + itemDescription + "&link=" + itemLink;

    /** Old User */
    if (AmphetaRateThread.isOldUser())
      url += "&user=" + GlobalSettings.amphetaRateUserID;

    /** New User */
    else
      url += "&alias=" + GlobalSettings.amphetaRateUsername + "&password=" + GlobalSettings.amphetaRatePassword;

    /** Append Guid if available */
    if (StringShop.isset(guid))
      url += "&guid=" + URLShop.urlEncode(guid);

    return url;
  }

  /**
   * Create HTML from some of the informations of the news item. The HTML is
   * HTML 4.01 transitional.
   * 
   * @return String The HTML as String
   */
  public String toHTML() {

    /** Setup HTML Font */
    FontData[] fontData = FontShop.textFont.getFontData();
    String fontFamily = fontData[0].getName();
    String fontSize = String.valueOf(fontData[0].getHeight());
    String fontUnit = GlobalSettings.isMac() ? "px" : "pt";
    String margin = GlobalSettings.isMac() ? "" : "margin:8px; ";
    boolean isBold = (fontData[0].getStyle() & SWT.BOLD) != 0;
    boolean isItalic = (fontData[0].getStyle() & SWT.ITALIC) != 0;

    /** Create HTML Head and Body */
    StringBuffer html = new StringBuffer();
    html.append(XMLShop.DOCTYPE_HTML_TRANSITIONAL);

    /** Mark of the Web (Windows only) */
    if (GlobalSettings.isWindows())
      html.append("\n").append(BrowserShop.IE_MOTW).append("\n");

    html.append("<html><head><title>");
    html.append(getTitle());
    html.append("</title>");

    /** BASE URI */
    String base = StringShop.isset(baseUri) ? baseUri : getSafeXMLFeedUrl();
    if (StringShop.isset(base))
      html.append("<base href=\"").append(base).append("\">");

    /** Body */
    html.append("</head><body style='");
    html.append(margin);
    html.append("overflow:auto; font-family:");
    html.append(fontFamily);

    if (isBold)
      html.append("; font-weight:bold");

    if (isItalic)
      html.append("; font-style:italic");

    html.append("; font-size:");
    html.append(fontSize);
    html.append(fontUnit);
    html.append(";'>");

    /** Description */
    if (StringShop.isset(getDescription()))
      html.append(getDescription());
    else
      html.append(GUI.i18n.getTranslation("NEWS_NO_DESCRIPTION"));

    /** Enclosures */
    if (enclosures != null && enclosures.size() > 0) {
      html.append("<br /><br /><b>");
      html.append(GUI.i18n.getTranslation("NEWS_ITEM_INFO_ENCLOSURE"));
      html.append(":</b><br />");

      /** Foreach Enclosure */
      for (int a = 0; a < enclosures.size(); a++) {
        Enclosure enclosure = (Enclosure) enclosures.get(a);
        String url = enclosure.getUrl();

        /** Create Anchor */
        String anchor;
        try {
          URL enclosureUrl = new URL(url);
          anchor = StringShop.createAnchor(url, URLShop.getFile(enclosureUrl));
        } catch (MalformedURLException e) {
          anchor = url;
        }

        /** Append to Buffer */
        html.append("<br />");
        html.append(anchor);
        html.append(" (");
        html.append(enclosure.getType());
        html.append(" / ");
        html.append(enclosure.getLength(true));
        html.append(")");
      }
    }

    /** Source of the news */
    if (getSource() != null) {
      html.append("<br /><br /><b>");
      html.append(GUI.i18n.getTranslation("NEWS_ITEM_INFO_SOURCE"));
      html.append(":</b>  ");
      html.append(URLShop.looksLikeURL(getSource()) ? StringShop.createAnchor(getSource()) : getSource());
    }

    /** Comments to this news */
    if (comments != null && comments.size() > 0) {
      html.append("<br /><br /><b>");
      html.append(GUI.i18n.getTranslation("NEWS_ITEM_INFO_COMMENTS"));
      html.append(":</b><br />");

      for (int a = 0; a < comments.size(); a++) {
        String comment = (String) comments.get(a);

        /** Show comment as anchor if it is a valid URL */
        if (StringShop.isset(comment))
          html.append("<br />").append((URLShop.looksLikeURL(comment) ? StringShop.createAnchor(comment) : comment));
      }
    }

    /** Close HTML document */
    html.append("</body></html>");

    /** Create the resulting HTML */
    String resultingHTML = new String(html);

    /** Highlight Search results if necessary */
    if (highlightWords != null) {
      for (int a = 0; a < highlightWords.size(); a++) {
        String curWord = (String) highlightWords.get(a);
        StringBuffer curWordHihglighted = new StringBuffer();
        curWordHihglighted.append("<span style='background-color:rgb(").append(PaintShop.syntaxHighlightColor.getRed());
        curWordHihglighted.append(", ").append(PaintShop.syntaxHighlightColor.getGreen()).append(", ");
        curWordHihglighted.append(PaintShop.syntaxHighlightColor.getBlue()).append(");'>").append(curWord).append("</span>");

        /**
         * Only highlight in case the current word is not inside a tag or is a
         * tag itself. This is necessary to avoid invalidly nested HTML and
         * errors.
         */
        if (!RegExShop.isInsideTags(resultingHTML, curWord))
          resultingHTML = StringShop.replaceAll(resultingHTML, curWord, curWordHihglighted.toString());
      }
    }

    return resultingHTML;
  }

  /**
   * Create a new "mailto:" URL from the newsitem
   * 
   * @return String URL-encoded "mailto:..." link
   */
  public String toNewsTip() {

    /** Mailto URL */
    String mailtoUrl = "mailto:";

    /** Mail Info */
    String subject = GlobalSettings.mailSubject;
    String body = GlobalSettings.mailBody;

    /** RSSOwl Ad */
    String rssOwlAd = "\n\n-----\nGet RSSOwl - The Opensource Newsreader - http://www.rssowl.org";

    /** Newsitem informations */
    String url = getLink();
    String title = getTitle();
    String description = getDescription();

    /** Use Guid if Link is null */
    if (url == null)
      url = getGuid();

    /** Place title into wildcard [TITLE] */
    body = StringShop.replaceAll(body, "[TITLE]", title);
    subject = StringShop.replaceAll(subject, "[TITLE]", title);

    /** Place url into wildcard [LINK] */
    if (StringShop.isset(url)) {
      body = StringShop.replaceAll(body, "[LINK]", url);
      subject = StringShop.replaceAll(subject, "[LINK]", url);
    }

    /** URL is not given */
    else {
      body = body.replaceAll("\\[LINK\\]", GUI.i18n.getTranslation("LABEL_EMPTY_LINK"));
      subject = subject.replaceAll("\\[LINK\\]", GUI.i18n.getTranslation("LABEL_EMPTY_LINK"));
    }

    /** Place description into wildcard [DESCRIPTION] */
    if (StringShop.isset(description)) {
      body = StringShop.replaceAll(body, "[DESCRIPTION]", description);
      subject = StringShop.replaceAll(subject, "[DESCRIPTION]", description);
    }

    /** Description is not given */
    else {
      body = body.replaceAll("\\[DESCRIPTION\\]", "");
      subject = subject.replaceAll("\\[DESCRIPTION\\]", "");
    }

    /** Author */
    subject = (StringShop.isset(getAuthor())) ? StringShop.replaceAll(subject, "[AUTHOR]", getAuthor()) : subject.replaceAll("\\[AUTHOR\\]", "");
    body = (StringShop.isset(getAuthor())) ? StringShop.replaceAll(body, "[AUTHOR]", getAuthor()) : body.replaceAll("\\[AUTHOR\\]", "");

    /** Category */
    subject = (StringShop.isset(getCategory())) ? StringShop.replaceAll(subject, "[CATEGORY]", getCategory()) : subject.replaceAll("\\[CATEGORY\\]", "");
    body = (StringShop.isset(getCategory())) ? StringShop.replaceAll(body, "[CATEGORY]", getCategory()) : body.replaceAll("\\[CATEGORY\\]", "");

    /** PubDate */
    subject = (StringShop.isset(getPubDate())) ? StringShop.replaceAll(subject, "[PUBDATE]", getPubDate()) : subject.replaceAll("\\[PUBDATE\\]", "");
    body = (StringShop.isset(getPubDate())) ? StringShop.replaceAll(body, "[PUBDATE]", getPubDate()) : body.replaceAll("\\[PUBDATE\\]", "");

    /** Publisher */
    subject = (StringShop.isset(getPublisher())) ? StringShop.replaceAll(subject, "[PUBLISHER]", getPublisher()) : subject.replaceAll("\\[PUBLISHER\\]", "");
    body = (StringShop.isset(getPublisher())) ? StringShop.replaceAll(body, "[PUBLISHER]", getPublisher()) : body.replaceAll("\\[PUBLISHER\\]", "");

    /** Source */
    subject = (StringShop.isset(getSource())) ? StringShop.replaceAll(subject, "[SOURCE]", getSource()) : subject.replaceAll("\\[SOURCE\\]", "");
    body = (StringShop.isset(getSource())) ? StringShop.replaceAll(body, "[SOURCE]", getSource()) : body.replaceAll("\\[SOURCE\\]", "");

    /** Place RSSOwl Ad to the end */
    body += rssOwlAd;

    /** Create mailto: */
    mailtoUrl += "?body=" + URLShop.mailToUrllEncode(body) + "&subject=" + URLShop.mailToUrllEncode(subject);

    return mailtoUrl;
  }

  /**
   * Create a String of the informations in the newsitem with simple formatting
   * to send to a printer.
   * 
   * @return String Printable newsitem text
   */
  public String toPrintable() {
    StringBuffer printableText = new StringBuffer("");

    /** Title */
    if (getTitle() != null)
      printableText.append(getTitle() + "\n");

    /** Link */
    if (getLink() != null)
      printableText.append(getLink() + "\n");

    /** Divide from description */
    printableText.append("\n");

    /** Description */
    if (getDescription() != null)
      printableText.append(Text.normalizeString(getDescription()));

    /** Enclosures */
    if (enclosures != null && enclosures.size() > 0) {
      String enclosureTitle = "\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_ENCLOSURE") + ":\n";
      printableText.append(enclosureTitle);

      for (int a = 0; a < enclosures.size(); a++) {
        Enclosure enclosure = (Enclosure) enclosures.get(a);
        printableText.append("\n" + enclosure.getUrl() + " (" + enclosure.getType() + " / " + enclosure.getLength(true) + ")");
      }
    }

    /** Source of the news */
    if (getSource() != null) {
      String source = "\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_SOURCE") + ":  " + getSource();
      printableText.append(source);

    }

    /** Comments to this news */
    if (comments != null && comments.size() > 0) {
      printableText.append("\n\n" + GUI.i18n.getTranslation("NEWS_ITEM_INFO_COMMENTS") + ":\n");

      for (int a = 0; a < comments.size(); a++)
        if (comments.get(a) != null && !comments.get(a).equals(""))
          printableText.append("\n" + comments.get(a));
    }

    return new String(printableText);
  }

  /**
   * Create a String from the newsitem (not complete, only used by the search).
   * 
   * @param searchDefinition The SearchDefinition including the Scope.
   * @return String Representation of the newsitem.
   */
  public String toString(SearchDefinition searchDefinition) {
    StringBuffer sb = new StringBuffer();
    int scope = searchDefinition.getScope();

    /** Search in Title */
    if (scope == SearchDefinition.SCOPE_TITLE || scope == SearchDefinition.SCOPE_NEWS) {

      /** Append Title */
      if (StringShop.isset(getTitle()))
        sb.append(getTitle());

      /** Append Spacer */
      if (scope == SearchDefinition.SCOPE_NEWS && StringShop.isset(getTitle()))
        sb.append("\n");
    }

    /** Search in Description, Comments and Source */
    if (scope == SearchDefinition.SCOPE_DESCRIPTION || scope == SearchDefinition.SCOPE_NEWS) {

      /** Append Description */
      if (StringShop.isset(getDescription()))
        sb.append(getDescription());

      /** Append Comments */
      if (comments != null)
        for (int a = 0; a < comments.size(); a++)
          sb.append("\n").append(comments.get(a));

      /** Append Source */
      if (StringShop.isset(getSource()))
        sb.append("\n").append(getSource());

      /** Append Spacer */
      if (scope == SearchDefinition.SCOPE_NEWS && StringShop.isset(getDescription()))
        sb.append("\n");
    }

    /** Search in Publish Date */
    if (scope == SearchDefinition.SCOPE_PUBDATE) {
      if (getPubDateParsed() != null)
        sb.append(DateParser.formatDate(getPubDateParsed(), true));
      else if (StringShop.isset(getPubDate()))
        sb.append(getPubDate());
    }

    /** Search in Category */
    if (scope == SearchDefinition.SCOPE_CATEGORY) {
      if (StringShop.isset(getCategory()))
        sb.append(getCategory());
    }

    /** Search in Author */
    if (scope == SearchDefinition.SCOPE_AUTHOR) {
      if (StringShop.isset(getAuthor()))
        sb.append(getAuthor());
    }

    /** Search in Enclosures */
    if (scope == SearchDefinition.SCOPE_ENCLOSURE || scope == SearchDefinition.SCOPE_NEWS) {

      /** Foreach Enclsoure */
      if (enclosures != null) {
        for (int a = 0; a < enclosures.size(); a++) {
          Enclosure enclosure = (Enclosure) enclosures.get(a);

          /** Append Link */
          sb.append("\n").append(enclosure.getUrl());

          /** Append Type */
          if (StringShop.isset(enclosure.getType()))
            sb.append(" ").append(enclosure.getType());
        }

        /** Add Spacer */
        if (scope == SearchDefinition.SCOPE_NEWS && enclosures.size() > 0)
          sb.append("\n");
      }
    }

    /** Search in Newsfeed Title */
    if (scope == SearchDefinition.SCOPE_NEWSFEED) {
      if (StringShop.isset(getNewsfeedTitle()))
        sb.append(getNewsfeedTitle());
    }

    /** Strip Tags out of the result */
    return StringShop.stripTags(sb.toString());
  }

  /**
   * Parse the text for links
   * 
   * @param text Current Text
   */
  private void parseForLinks(String text) {
    if (StringShop.isset(text) && linkList != null)
      RegExShop.extractLinksFromText(text, linkList);
  }
}