/*   **********************************************************************  **
 **   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.controller.thread.SettingsManager;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.util.shop.BrowserShop;
import net.sourceforge.rssowl.util.shop.ProxyShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.XMLShop;

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

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UTFDataFormatException;
import java.util.Iterator;
import java.util.List;

/**
 * This class is used to import categories and favorites from external
 * ressources (for example OPML documents).
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class Importer {
  private String categoryTitle;
  private Document document;
  private String opmlFilePath;
  private Category targetCategory;

  /**
   * Instantiate a new Importer
   * 
   * @param opmlFilePath The path or URL to the OPML file
   * @param targetCategory The Category to import the newsfeeds into
   */
  public Importer(String opmlFilePath, Category targetCategory) {
    this(opmlFilePath, null, targetCategory);
  }

  /**
   * Instantiate a new Importer
   * 
   * @param opmlFilePath The path or URL to the OPML file
   * @param categoryTitle The Title of the root category of this import
   * @param targetCategory The Category to import the newsfeeds into
   */
  public Importer(String opmlFilePath, String categoryTitle, Category targetCategory) {
    this.opmlFilePath = opmlFilePath;
    this.categoryTitle = categoryTitle;
    this.targetCategory = targetCategory;
  }

  /**
   * Import all newsfeeds from the OPML document into the target category.
   * 
   * @param fromBlogroll If TRUE, the import is from a Blogroll
   * @throws FileNotFoundException If an error occurs
   * @throws JDOMException If an error occurs
   * @throws IOException If an error occurs
   */
  public void importNewsfeeds(boolean fromBlogroll) throws FileNotFoundException, JDOMException, IOException {

    /** Load document from disk or online */
    loadDocument(false);

    /** Root element */
    Element root = document.getRootElement();

    /** Check for OPML format */
    if (root == null || !root.getName().equalsIgnoreCase("opml"))
      throw new JDOMException(GUI.i18n.getTranslation("ERROR_NOT_A_OPML"));

    /** Get body */
    Element body = root.getChild("body");

    /** Body must not be null */
    if (body == null)
      throw new JDOMException(GUI.i18n.getTranslation("ERROR_NOT_A_OPML"));

    /** Check for favorits that have no category */
    boolean favWithoutCat = false;

    List elements = body.getChildren();
    Iterator elementsIt = elements.iterator();

    /** Foreach element */
    while (elementsIt.hasNext()) {
      Element element = (Element) elementsIt.next();

      /** This outline is a root-leveled Favorite */
      if (element.getChildren().size() == 0 && !isCategory(element)) {
        favWithoutCat = true;
        break;
      }
    }

    /** Import a Blogroll */
    if (StringShop.isset(categoryTitle) && fromBlogroll) {

      /** A valid target was already provided (Edit Mode) */
      if (targetCategory != Category.getRootCategory()) {

        /** Remove Blogroll from List */
        Category.getBlogrolls().remove(targetCategory.getPathToBlogroll());

        /** Clear the category to reload the content */
        targetCategory.removeAll();

        /** Update Path and Proxy usage */
        targetCategory.setPathToBlogroll(opmlFilePath);
        targetCategory.setUseProxy(ProxyShop.isUseProxy());

        /** The Title might have changed */
        targetCategory.getParent().editCategory(targetCategory.getName(), categoryTitle);

        /** Import Newsfeeds into this Category */
        importFromOPML(root.getChild("body"), targetCategory, fromBlogroll);
      }

      /** Create a new Category based on the given title (New Mode) */
      else {
        Category blogrollCategory = new Category(categoryTitle, targetCategory, true);
        blogrollCategory.setPathToBlogroll(opmlFilePath);
        blogrollCategory.setUseProxy(ProxyShop.isUseProxy());
        targetCategory.addCategory(blogrollCategory, true);

        /** Import Newsfeeds into this Category */
        importFromOPML(root.getChild("body"), blogrollCategory, fromBlogroll);
      }
    }

    /** Import an OPML (only Favorites) */
    else if (favWithoutCat) {
      Category newCategory = new Category(GUI.i18n.getTranslation("OPML_IMPORTED"), targetCategory, false);
      targetCategory.addCategory(newCategory, true);

      /** Recursivly import from the category */
      importFromOPML(root.getChild("body"), newCategory, fromBlogroll);
    }

    /** Import a Blogroll or an OPML (with categories) */
    else {
      importFromOPML(root.getChild("body"), targetCategory, fromBlogroll);
    }

    /** Request Save of Settings */
    SettingsManager.getInstance().requestSave();
  }

  /**
   * Recursivly import categorys / favorits from the OPML file
   * 
   * @param element Current outline element
   * @param rssOwlCategory Current rssOwlCategory
   * @param fromBlogroll If TRUE, the import is from a Blogroll
   */
  private void importFromOPML(Element element, Category rssOwlCategory, boolean fromBlogroll) {
    List outlines = element.getChildren();
    Iterator outIt = outlines.iterator();

    /** Foreach Outline Element */
    while (outIt.hasNext()) {
      Element outline = (Element) outIt.next();

      /** Try to get the URL (various capitalization) */
      String url = outline.getAttributeValue("xmlUrl");
      if (url == null)
        url = outline.getAttributeValue("xmlurl");
      if (url == null)
        url = outline.getAttributeValue("xmlURL");

      /** This outline is a category */
      if (url == null) {
        Category newCategory = new Category(GUI.i18n.getTranslation("OPML_IMPORTED"), rssOwlCategory, fromBlogroll);

        /** Set category name - try with title attribute */
        if (StringShop.isset(outline.getAttributeValue("title")))
          newCategory.setName(outline.getAttributeValue("title"));

        /** Set category name - try with text attribute */
        else if (StringShop.isset(outline.getAttributeValue("text")))
          newCategory.setName(outline.getAttributeValue("text"));

        /** Set Blogroll specific settings */
        if (fromBlogroll) {
          newCategory.setBlogroll(true);
          newCategory.setPathToBlogroll(opmlFilePath);
          newCategory.setUseProxy(rssOwlCategory.isUseProxy());
        }

        /** Add category */
        rssOwlCategory.addCategory(newCategory, true);

        /** Recursivly add childs */
        importFromOPML(outline, newCategory, fromBlogroll);
      }

      /** This outline is a favorite. Add it to the category */
      else {

        /** The URL must not be an empty String */
        if (StringShop.isset(url)) {

          /** Create a unique link */
          url = Category.createUniqueLink(url);

          /** Title of the favorite */
          String title = url;

          /** Set favorite name - try with text attribute */
          if (StringShop.isset(outline.getAttributeValue("text")))
            title = outline.getAttributeValue("text");

          /** Set favorite name - try with title attribute */
          else if (StringShop.isset(outline.getAttributeValue("title")))
            title = outline.getAttributeValue("title");

          /** Create a unique title */
          title = Category.createUniqueTitle(title);

          /** Create new favorite */
          Favorite favorite = new Favorite(url, title, rssOwlCategory);
          favorite.setCreationDate(System.currentTimeMillis());

          /** Extended information: Open on startup */
          if (StringShop.isset(outline.getAttributeValue("openOnStartup")))
            favorite.setOpenOnStartup(Boolean.valueOf(outline.getAttributeValue("openOnStartup")).booleanValue());

          /** Extended information: Re-Load on startup */
          if (StringShop.isset(outline.getAttributeValue("loadOnStartup")))
            favorite.setLoadOnStartup(Boolean.valueOf(outline.getAttributeValue("loadOnStartup")).booleanValue());

          /** Extended information: Use Proxy */
          if (StringShop.isset(outline.getAttributeValue("useproxy")))
            favorite.setUseProxy(Boolean.valueOf(outline.getAttributeValue("useproxy")).booleanValue());

          /** Extended information: Update Interval */
          if (StringShop.isset(outline.getAttributeValue("rssOwlUpdateInterval")))
            favorite.setUpdateInterval(Integer.parseInt(outline.getAttributeValue("rssOwlUpdateInterval")));

          /** Extended information: Homepage */
          if (StringShop.isset(outline.getAttributeValue("htmlUrl")))
            favorite.setHomepage(outline.getAttributeValue("htmlUrl"));

          /** Extended information: Language */
          if (StringShop.isset(outline.getAttributeValue("language")))
            favorite.setLanguage(outline.getAttributeValue("language"));

          /** Extended information: Description */
          if (StringShop.isset(outline.getAttributeValue("description")))
            favorite.setDescription(outline.getAttributeValue("description"));

          /** Proxy Settings in case of a Blogroll */
          if (fromBlogroll)
            favorite.setUseProxy(rssOwlCategory.isUseProxy());

          /** Add into Category */
          rssOwlCategory.addFavorite(favorite);
        }
      }
    }
  }

  /**
   * Check if the given Element is to be interpreted as a Category.
   * 
   * @param e The Element from the XML Document to check.
   * @return boolean TRUE if the Element is a Category.
   */
  private boolean isCategory(Element e) {
    return (e != null && e.getAttributeValue("xmlUrl") == null && e.getAttributeValue("xmlurl") == null && e.getAttributeValue("xmlURL") == null);
  }

  /**
   * Load the OPML XML document from the given path / URL.
   * 
   * @param forceDefaultEncoding If TRUE, use OS default encoding and not UTF-8
   * @throws IOException If an error occurs
   * @throws JDOMException If an error occurs
   * @throws FileNotFoundException If an error occurs
   */
  private void loadDocument(boolean forceDefaultEncoding) throws FileNotFoundException, JDOMException, IOException {

    /** Set up SAXBuilder */
    SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser");
    builder.setFeature(XMLShop.ALLOW_JAVA_ENCODING, true);
    XMLShop.setDefaultEntityResolver(builder);

    /** OPML file is located locally */
    if (new File(opmlFilePath).exists()) {

      /** Build document */
      document = builder.build(new BufferedInputStream(new FileInputStream(opmlFilePath)));
    }

    /** OPML file is located online */
    else {

      /** Create a new ConnectionManager */
      ConnectionManager connectionManager = new ConnectionManager(opmlFilePath);
      connectionManager.setUserAgent(BrowserShop.getOwlAgent());

      /** Connect */
      connectionManager.connect(targetCategory.isUseProxy());

      /** Try to parse the document */
      try {

        /** Auto-Detect encoding */
        if (!forceDefaultEncoding)
          document = builder.build(connectionManager.getInputStream());

        /** Use default encoding */
        else
          document = builder.build(new InputStreamReader(connectionManager.getInputStream()));
      }

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

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

        /** Parsing not possible */
        else
          throw e;
      }

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