/*   **********************************************************************  **
 **   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.AmphetaRateThread;
import net.sourceforge.rssowl.model.Category;
import net.sourceforge.rssowl.model.Favorite;
import net.sourceforge.rssowl.util.CryptoManager;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.HotkeyShop;
import net.sourceforge.rssowl.util.shop.ProxyShop;
import net.sourceforge.rssowl.util.shop.SimpleFileShop;
import net.sourceforge.rssowl.util.shop.StringShop;
import net.sourceforge.rssowl.util.shop.XMLShop;

import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

/**
 * The SettingsLoader class is responsible to load all settings and favorites
 * into RSSOwl.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class SettingsLoader {

  /** The root element of the settings document */
  private Element root;

  /** Main Controller */
  private GUI rssOwlGui;

  /**
   * Instantiate a new SettingsFactory
   * 
   * @param rssOwlGui The Main Controller
   */
  public SettingsLoader(GUI rssOwlGui) {
    this(rssOwlGui, null);
  }

  /**
   * Instantiate a new SettingsFactory with a Document
   * 
   * @param rssOwlGui The Main Controller
   * @param document The configuration XML
   */
  public SettingsLoader(GUI rssOwlGui, Document document) {
    this.rssOwlGui = rssOwlGui;

    /** Try to build a document from the "user.xml" */
    if (document == null)
      document = buildSettingsDocument();

    /** Init root element */
    root = document.getRootElement();
  }

  /**
   * Get the state of a value from the user.xml
   * 
   * @param element The Name of the Element
   * @param init Initial value
   * @return boolean TRUE or FALSE
   */
  public boolean getBoolean(String element, boolean init) {
    Element child = root.getChild(element);

    if (child != null)
      return Boolean.valueOf(child.getAttributeValue("value")).booleanValue();

    return init;
  }

  /**
   * Load a value from the XML settings document
   * 
   * @param element The element's name
   * @param attribute The attribute's name
   * @return String The value of the attribute or the init String
   */
  public String getValue(String element, String attribute) {
    return getValue(element, attribute, "", true);
  }

  /**
   * Load a value from the XML settings document
   * 
   * @param element The element's name
   * @param attribute The attribute's name
   * @param init The initial value if attribute is not found
   * @return String The value of the attribute or the init String
   */
  public String getValue(String element, String attribute, String init) {
    return getValue(element, attribute, init, true);
  }

  /**
   * Load a value from the XML settings document
   * 
   * @param element The element's name
   * @param attribute The attribute's name
   * @param init The initial value if attribute is not found
   * @param trim If TRUE the text is trimmed
   * @return String The value of the attribute or the init String
   */
  public String getValue(String element, String attribute, String init, boolean trim) {
    Element child = root.getChild(element);
    String value = init;

    /** Get the value */
    if (child != null) {
      if (attribute != null)
        value = child.getAttributeValue(attribute);
      else
        value = child.getText();
    }

    /** Check value and set to init if is NULL */
    if (value == null)
      value = init;

    /** Trim value if needed */
    if (trim)
      return value.trim();

    return value;
  }

  /**
   * Load the color
   * 
   * @param element The element's name
   * @param init Init value if Color is null
   * @return RGB The color
   */
  public RGB loadColor(String element, RGB init) {
    Element child = root.getChild(element);

    if (child != null) {
      int red = Integer.parseInt(child.getAttributeValue("red"));
      int green = Integer.parseInt(child.getAttributeValue("green"));
      int blue = Integer.parseInt(child.getAttributeValue("blue"));

      return new RGB(red, green, blue);
    }
    return init;
  }

  /**
   * Load user favorites and categorys in the RSSOwlFavTree.
   */
  public void loadFavorites() {
    Element favorites = root.getChild("favorites");

    if (favorites != null)
      loadFavorites(Category.getRootCategory(), favorites);
  }

  /**
   * Load a font from the settings
   * 
   * @param font The Font to load
   * @param fontName The name of the font
   * @param isHotLoad TRUE if the settings are imported by the user from the
   * running program
   * @return Font loaded from the settings
   */
  public Font loadFont(Font font, String fontName, boolean isHotLoad) {
    Element textFontElement = root.getChild(fontName);

    /** The Font is existing in Settings File */
    if (textFontElement != null) {

      /** Create new Font */
      Font newFont = FontShop.createFont(textFontElement.getAttributeValue("name"), Integer.parseInt(textFontElement.getAttributeValue("height")), Integer.parseInt(textFontElement.getAttributeValue("style")));

      /** Check that creation was successfull */
      if (FontShop.isset(newFont)) {

        /** Dispose old font */
        if (!isHotLoad && FontShop.isset(font))
          font.dispose();

        /** Return new font */
        return newFont;
      }
    }

    /** Problems loading the Font, keep the default one */
    return font;
  }

  /**
   * Load the hotkeys
   */
  public void loadHotKeys() {
    Element hotkeys = root.getChild("hotkeys");
    if (hotkeys != null) {

      /** For each hotkey */
      List types = hotkeys.getChildren();
      for (int a = 0; a < types.size(); a++) {
        Element type = (Element) types.get(a);
        HotkeyShop.setHotkey(type.getName(), type.getAttributeValue("keyName"), type.getAttributeValue("keyInt"));
      }
    }
  }

  /**
   * Load the proxy settings
   */
  public void loadProxySettings() {

    /** Init default proxy settings */
    String proxySet = "false";
    String proxyHost = "";
    String proxyPort = "";
    String proxyUser = "";
    String proxyPassword = "";
    String proxyDomain = "";

    /** Retrieve proxy element */
    Element proxy = root.getChild("proxy");

    /** Proxy is given */
    if (proxy != null) {
      proxySet = proxy.getChild("proxySet").getText();
      proxyHost = proxy.getChild("proxyHost").getText();
      proxyPort = proxy.getChild("proxyPort").getText();
      proxyUser = CryptoManager.getInstance().loadProxyUser();
      proxyPassword = CryptoManager.getInstance().loadProxyPassword();
      proxyDomain = CryptoManager.getInstance().loadProxyDomain();
    }

    /** Update proxy settings hashtable */
    ProxyShop.setUseProxy(proxySet);
    ProxyShop.setHost(proxyHost);
    ProxyShop.setPort(proxyPort);
    ProxyShop.setUsername(proxyUser);
    ProxyShop.setPassword(proxyPassword);
    ProxyShop.setDomain(proxyDomain);
  }

  /**
   * Load all ratings that have not yet been submitted
   */
  public void loadRatings() {
    List ratings = root.getChildren("rating");
    if (ratings != null) {
      Iterator ratingsIt = ratings.iterator();

      /** For each rating */
      while (ratingsIt.hasNext()) {
        Element rating = (Element) ratingsIt.next();
        AmphetaRateThread.getRatings().put(rating.getAttributeValue("key"), rating.getText());
      }
    }
  }

  /**
   * Load the feeds that RSSOwl shall reopen after application start.
   */
  public void loadReopenFeeds() {
    Element reopenFeeds = root.getChild("reopenFeedsList");

    if (reopenFeeds != null) {
      List feeds = reopenFeeds.getChildren("feed");

      /** Foreach feed */
      for (int a = 0; a < feeds.size(); a++) {
        Element feed = (Element) feeds.get(a);
        if (feed.getAttributeValue("url") != null) {

          /** Directly add the feed to the RSS queue loader */
          rssOwlGui.getRSSOwlFeedQueueLoader().addFeed(feed.getAttributeValue("url"));

          /** Only load one Feed from List in case no TabFolder is used */
          if (GlobalSettings.displaySingleTab)
            return;
        }
      }
    }
  }

  /**
   * Load weights of the SashForms
   */
  public void loadSashWeights() {
    List sashWeightList = root.getChildren("sashweight");
    if (sashWeightList != null) {
      Iterator it = sashWeightList.iterator();

      /** For each sash weight */
      while (it.hasNext()) {
        Element sashWeight = (Element) it.next();
        String name = sashWeight.getAttributeValue("name");
        String[] weights = sashWeight.getAttributeValue("weight").split(",");

        /** Content Sash */
        if ("holdContentSash".equals(name)) {
          GlobalSettings.contentSashWeights = new int[weights.length];

          for (int a = 0; a < weights.length; a++)
            GlobalSettings.contentSashWeights[a] = Integer.parseInt(weights[a]);
        }

        /** News Sash */
        else if ("holdNewsSash".equals(name)) {
          GlobalSettings.newsSashWeights = new int[weights.length];

          for (int a = 0; a < weights.length; a++)
            GlobalSettings.newsSashWeights[a] = Integer.parseInt(weights[a]);
        }
      }
    }
  }

  /**
   * Load the bounds of the shell
   */
  public void loadShellBounds() {
    Element shellBoundsElem = root.getChild("shell");

    /** Shell Bounds are saved */
    if (shellBoundsElem != null) {

      /** Load the bounds */
      if (shellBoundsElem.getAttribute("height") != null) {
        Rectangle bounds = new Rectangle(0, 0, 0, 0);
        bounds.height = Integer.parseInt(shellBoundsElem.getAttributeValue("height"));
        bounds.width = Integer.parseInt(shellBoundsElem.getAttributeValue("width"));
        bounds.x = Integer.parseInt(shellBoundsElem.getAttributeValue("x"));
        bounds.y = Integer.parseInt(shellBoundsElem.getAttributeValue("y"));
        GlobalSettings.shellBounds = bounds;
      }

      /**
       * Load maximized state<br>
       * Bug on Mac: getMaximized() is not working properly. Therefor never
       * maximize the application window on Mac, unless RSSOwl is starting for
       * the first time.
       */
      if (shellBoundsElem.getAttribute("maximized") != null && !GlobalSettings.isMac())
        GlobalSettings.isShellMaximized = Boolean.valueOf(shellBoundsElem.getAttributeValue("maximized")).booleanValue();
    }

    /** By default maximize Shell if Shell Bounds are not saved */
    else {
      GlobalSettings.isShellMaximized = true;
    }
  }

  /**
   * Load the sort Order to apply to News
   * 
   * @return ArrayList An ArrayList containing the sort order
   */
  public ArrayList loadSortOrder() {
    ArrayList sortOrderVector = new ArrayList();

    /** Sort Order is available in the settings */
    if (root.getChild("sortOrder") != null) {
      Element sortOrder = root.getChild("sortOrder");
      List sortOrderItems = sortOrder.getChildren();
      for (int a = 0; a < sortOrderItems.size(); a++) {
        Element sortItem = (Element) sortOrderItems.get(a);
        int level = Integer.parseInt(sortItem.getAttributeValue("level"));
        sortOrderVector.add(level, sortItem.getName());
      }
    }

    /** Sort Order ist not available, use default sort Order */
    else {
      for (int a = 0; a < GlobalSettings.defaultSortOrder.length; a++)
        sortOrderVector.add(GlobalSettings.defaultSortOrder[a]);
    }

    return sortOrderVector;
  }

  /**
   * Build the document holding the settings of RSSOwl
   * 
   * @return Document The XML document holding the settings
   */
  private Document buildSettingsDocument() {
    SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser");
    XMLShop.setDefaultEntityResolver(builder);
    File settings = new File(GlobalSettings.RSSOWL_SETTINGS_FILE);
    File backup = new File(GlobalSettings.RSSOWL_SETTINGS_BACKUP_FILE);

    /** Set the encoding to use. If not supported use UTF-8 by default */
    String encoding = XMLShop.isEncodingSupported(GlobalSettings.charEncoding) ? GlobalSettings.charEncoding : "UTF-8";

    /** 1.) Try to load user.xml from profile directory */
    try {
      if (settings.exists()) {
        Document doc = builder.build(settings);

        /** Backup the settings file */
        SimpleFileShop.copy(settings, backup);

        return doc;
      }
    } catch (JDOMException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IOException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IllegalArgumentException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    }

    /** 2.) Try to load user.bak (Backup) from profile directory */
    try {
      if (backup.exists())
        return builder.build(backup);
    } catch (JDOMException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IOException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IllegalArgumentException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    }

    /** 3.) If any attempt fails, load default user.xml from JAR */
    try {
      return builder.build(getClass().getResourceAsStream("/usr/user.xml"), encoding);
    } catch (JDOMException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IOException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    } catch (IllegalArgumentException e) {
      GUI.logger.log("buildSettingsDocument()", e);
    }

    return null;
  }

  /**
   * Load a category from the Element
   * 
   * @param rssOwlCategory The parent category
   * @param catOrFav The element to load the category from
   * @return Favorite A new category loaded from the element
   */
  private Category loadCategory(Category rssOwlCategory, Element catOrFav) {

    /** Name of the category */
    String catName = catOrFav.getAttributeValue("name");

    /** Create a new category */
    Category rssOwlSubCat = new Category(catName, rssOwlCategory, false);

    /** Retrieve Expanded State */
    String expandedState = catOrFav.getAttributeValue("isExpanded");

    /** Set Expanded State */
    if (StringShop.isset(expandedState))
      rssOwlSubCat.setExpanded(Boolean.valueOf(expandedState).booleanValue());

    return rssOwlSubCat;
  }

  /**
   * Load a favorite from the Element
   * 
   * @param rssOwlCategory The parent category
   * @param catOrFav The element to load the favorite from
   * @return Favorite A new favorite loaded from the element
   */
  private Favorite loadFavorite(Category rssOwlCategory, Element catOrFav) {

    /** Init default favorite values */
    String url = "";
    String title = "";
    int updateInterval = 0;
    boolean useProxy = false;
    boolean errorLoading = false;
    boolean loadOnStartup = false;
    boolean openOnStartup = false;

    /** Temp String */
    String tmp;

    /** Url of the favorite */
    if (catOrFav.getText() != null)
      url = catOrFav.getText();

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

    /** Title of the favorite */
    if (catOrFav.getAttributeValue("title") != null)
      title = catOrFav.getAttributeValue("title");

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

    /** Use proxy state of the favorite */
    tmp = catOrFav.getAttributeValue("useproxy");
    if (StringShop.isset(tmp))
      useProxy = Boolean.valueOf(tmp).booleanValue();

    /** Error loading state of the favorite */
    tmp = catOrFav.getAttributeValue("errorLoading");
    if (StringShop.isset(tmp))
      errorLoading = Boolean.valueOf(tmp).booleanValue();

    /** Load on startup state of the favorite */
    tmp = catOrFav.getAttributeValue("loadOnStartup");
    if (StringShop.isset(tmp))
      loadOnStartup = Boolean.valueOf(tmp).booleanValue();

    /** Open on startup state of the favorite */
    tmp = catOrFav.getAttributeValue("openOnStartup");
    if (StringShop.isset(tmp)) {
      openOnStartup = Boolean.valueOf(tmp).booleanValue();

      /** Tell the queue loader about this feed if loadOnStartup is TRUE */
      if (openOnStartup)
        rssOwlGui.getRSSOwlFeedQueueLoader().addFeed(url);
    }

    /** Update interval of the favorite */
    tmp = catOrFav.getAttributeValue("updateInterval");
    if (StringShop.isset(tmp))
      updateInterval = Integer.parseInt(tmp);

    /** Create a new Favorite with the given informations */
    Favorite rssOwlFavorite = new Favorite(url, title, rssOwlCategory);
    rssOwlFavorite.setUseProxy(useProxy);
    rssOwlFavorite.setErrorLoading(errorLoading);
    rssOwlFavorite.setLoadOnStartup(loadOnStartup);
    rssOwlFavorite.setOpenOnStartup(openOnStartup);
    rssOwlFavorite.setUpdateInterval(updateInterval);

    /** Meta Infos: Unread News count */
    tmp = catOrFav.getAttributeValue("new");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setUnreadNewsCount(Integer.parseInt(tmp));

    /** Meta Infos: Description */
    tmp = catOrFav.getAttributeValue("description");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setDescription(tmp);

    /** Meta Infos: Homepage */
    tmp = catOrFav.getAttributeValue("homepage");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setHomepage(tmp);

    /** Meta Infos: Language */
    tmp = catOrFav.getAttributeValue("language");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setLanguage(tmp);

    /** Meta Infos: Creation date */
    tmp = catOrFav.getAttributeValue("creationDate");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setCreationDate(Long.parseLong(tmp));
    else
      rssOwlFavorite.setCreationDate(new Date().getTime());

    /** Meta Infos: Last visit date */
    tmp = catOrFav.getAttributeValue("lastVisitDate");
    if (StringShop.isset(tmp))
      rssOwlFavorite.setLastVisitDate(Long.parseLong(tmp));

    return rssOwlFavorite;
  }

  /**
   * Load categories and favorites from the XML. This method is called
   * recursivly to support nested categorys.
   * 
   * @param rssOwlCategory Current working category
   * @param element Current working element
   */
  private void loadFavorites(Category rssOwlCategory, Element element) {
    List catOrFavs = element.getChildren();
    Iterator catOrFavsIt = catOrFavs.iterator();

    /** Foreach Element */
    while (catOrFavsIt.hasNext()) {
      Element catOrFav = (Element) catOrFavsIt.next();

      /** The element is a category */
      if (catOrFav.getName().equals("category")) {
        Category rssOwlSubCat = loadCategory(rssOwlCategory, catOrFav);
        rssOwlCategory.addCategory(rssOwlSubCat);

        /** The category is a blogroll */
        if (catOrFav.getAttribute("url") != null) {
          rssOwlSubCat.setBlogroll(true);
          rssOwlSubCat.setUseProxy(Boolean.valueOf(catOrFav.getAttributeValue("useproxy")).booleanValue());
          rssOwlSubCat.setPathToBlogroll(catOrFav.getAttributeValue("url"));

          /** Mark this Blogroll as required to synchronize */
          rssOwlSubCat.setUnSynchronized(true);

          /** Add a placeholder for the loading process as favorite */
          Favorite favorite = new Favorite(rssOwlSubCat.getPathToBlogroll(), GUI.i18n.getTranslation("LOAD_FEED") + " (" + rssOwlSubCat.getPathToBlogroll() + ")", rssOwlSubCat);
          favorite.setSynchronizer(true);
          rssOwlSubCat.addFavorite(favorite);
        }

        /** This is a normal category */
        else {
          loadFavorites(rssOwlSubCat, catOrFav);
        }
      }

      /** The element is a favorite */
      else if (catOrFav.getName().equals("link")) {
        rssOwlCategory.addFavorite(loadFavorite(rssOwlCategory, catOrFav));
      }
    }
  }
}