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

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.dao.feedparser.FeedParser;
import net.sourceforge.rssowl.model.Channel;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.util.shop.XMLShop;

import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.JDOMParseException;
import org.jdom.input.SAXBuilder;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UTFDataFormatException;

/**
 * This class initializes the parser with a InputStream from the RSSUrl
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class NewsfeedFactory {

  /** The builded document from the URL */
  private Document document;

  /** Parser for the document */
  private FeedParser parser;

  /** Parsed Channel from the document */
  private Channel rssChannel;

  /** The URL to the RSS / RDF / Atom feed */
  String rssUrl;

  /**
   * Create a new parser for the given URL to the RSS file
   * 
   * @param rssOwlFavorite A favorite from the favoritesTree
   * @throws NewsfeedFactoryException If an error occurs
   */
  public NewsfeedFactory(Favorite rssOwlFavorite) throws NewsfeedFactoryException {
    this(rssOwlFavorite.getUrl(), false);
  }

  /**
   * Create a new parser for the given URL to the RSS file
   * 
   * @param rssUrl URL or Path to the RSS XML
   * @throws NewsfeedFactoryException If an error occurs
   */
  public NewsfeedFactory(String rssUrl) throws NewsfeedFactoryException {
    this(rssUrl, false);
  }

  /**
   * Create a new parser for the given URL to the RSS file
   * 
   * @param rssUrl URL or Path to the RSS XML
   * @param ignoreRSS ignoreRSS If TRUE its not intended to retrieve a RSS / RDF
   * XML InputStream
   * @throws NewsfeedFactoryException If an error occurs
   */
  public NewsfeedFactory(String rssUrl, boolean ignoreRSS) throws NewsfeedFactoryException {
    this.rssUrl = rssUrl;

    /** Init XML Document */
    initXmlDocument(false);

    /** User might have aborted loading already */
    if (document == null)
      return;

    /** User intends to retrieve a Newsfeed Channel */
    if (!ignoreRSS) {

      /** Init XML Parser with the XML document */
      initXMLParser();

      /** Get the parsed Channel from the document */
      parser.parse();
      rssChannel = parser.getChannel();

      /** Proceed if Channel is not null */
      if (rssChannel != null) {

        /** Set unread / read state in newsitems */
        rssChannel.updateReadStatusOnNews();

        /** Cleanup archive */
        GUI.rssOwlGui.getArchiveManager().getArchive().cleanUpItem(rssChannel.getLink(), rssChannel.getItems());
      }
    }
  }

  /**
   * Get the parsed document
   * 
   * @return Document The parsed XML document
   */
  public Document getDocument() {
    return document;
  }

  /**
   * Get the generated Channel
   * 
   * @return Generated Channel from the RSS XML
   */
  public Channel getRSSChannel() {
    return rssChannel;
  }

  /**
   * Initialize the RSSOwlXMLParser with the given Document
   * 
   * @throws NewsfeedFactoryException
   */
  private void initXMLParser() throws NewsfeedFactoryException {

    /** XML successfully retrieved and parsed */
    if (document != null) {

      /** Create parser */
      parser = new FeedParser(document, rssUrl);
    }
  }

  /**
   * Trys to open the RSS from the local path
   * 
   * @param builder The XML builder
   * @param forceDefaultEncoding If set to TRUE the encoding is not being
   * detected from the XMl document and the OS default encoding is used
   * @return The parsed Document
   * @throws NewsfeedFactoryException if an error occurs
   */
  private Document openRSSFromLocal(SAXBuilder builder, boolean forceDefaultEncoding) throws NewsfeedFactoryException {
    try {

      /** Auto-Detect encoding from the XML declaration */
      if (!forceDefaultEncoding)
        document = builder.build(new File(rssUrl));

      /** Use OS default encoding */
      else
        document = builder.build(new InputStreamReader(new FileInputStream(rssUrl)));
    }

    /** File is not a XML */
    catch (JDOMException e) {
      throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
    }

    /** UTF encoding exception */
    catch (UTFDataFormatException e) {

      /** Try parsing again with default encoding */
      if (!forceDefaultEncoding)
        return openRSSFromLocal(builder, true);

      /** Parsing not possible */
      throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
    }

    /** File not found */
    catch (IOException e) {
      throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_FILE_NOT_FOUND);
    }

    /** Parsing Error */
    catch (IllegalArgumentException e) {
      throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
    }
    return document;
  }

  /**
   * Init a new XML Parser from the RSS XML
   * 
   * @param forceDefaultEncoding If set to TRUE the encoding is not being
   * detected from the XMl document and the OS default encoding is used
   * @throws NewsfeedFactoryException
   */
  void initXmlDocument(boolean forceDefaultEncoding) throws NewsfeedFactoryException {

    /** Build Parser */
    SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser");
    builder.setFeature(XMLShop.ALLOW_JAVA_ENCODING, true);
    document = null;

    /** Ignore DTD */
    XMLShop.setDefaultEntityResolver(builder);

    /** User chose a local file */
    if (new File(rssUrl).exists()) {
      document = openRSSFromLocal(builder, false);
    }

    /** User chose a file in the internet */
    else {

      /** Create a new Connection Manager */
      ConnectionManager connectionManager = new ConnectionManager(rssUrl);
      try {

        /** Connect to the URL */
        connectionManager.connect();

        /** Retrieve InputStream */
        InputStream inS = connectionManager.getInputStream();

        /** User might have aborted loading already */
        if (inS == null)
          return;

        /** Parse XML from this InputStream. Auto-Detect encoding */
        if (!forceDefaultEncoding)
          document = builder.build(inS);

        /** Parse XML from this InputStream. Use default encoding */
        else
          document = builder.build(new InputStreamReader(inS));
      }

      /** Error parsing the document */
      catch (JDOMParseException e) {
        throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
      }

      /** Not a valid XML */
      catch (JDOMException e) {
        throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
      }

      /** UTF encoding exception */
      catch (UTFDataFormatException e) {

        /** Try parsing again with default encoding */
        if (!forceDefaultEncoding)
          initXmlDocument(true);

        /** Parsing not possible */
        else
          throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
      }

      /** Connection Error */
      catch (IOException e) {

        /** Explicitly set type to ERROR_AUTH_REQUIRED */
        if (e.getMessage() != null && e.getMessage().equals("401"))
          throw new NewsfeedFactoryException(rssUrl, null, connectionManager.getStatusLine(), NewsfeedFactoryException.ERROR_AUTH_REQUIRED);

        /** Throw exception with ERROR_FILE_NOT_FOUND type */
        throw new NewsfeedFactoryException(rssUrl, e.getMessage(), connectionManager.getStatusLine(), NewsfeedFactoryException.ERROR_FILE_NOT_FOUND);
      }

      /** Parsing Error */
      catch (IllegalArgumentException e) {
        throw new NewsfeedFactoryException(rssUrl, e.getMessage(), null, NewsfeedFactoryException.ERROR_INVALID_XML);
      }

      /** Close the connection in anyway */
      finally {
        connectionManager.closeConnection();
      }
    }
  }
}