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

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.util.GlobalSettings;
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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The ArchiveManager handles loading and saving of the archive on startup and
 * shutdown. Its the maincontroller of the whole archive and other objects from
 * RSSOwl access the archive through this class.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class ArchiveManager {
  private Archive archive;
  private SAXBuilder builder;
  ArchiveIndex index;

  /**
   * Instantiate a new ArchiveManager
   */
  public ArchiveManager() {
    builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser");
  }

  /**
   * Create a unique archive file to save an archive item
   * 
   * @return File The unique file
   * @throws IOException If an error occurs
   */
  public File createUniqueArchiveFile() throws IOException {
    return File.createTempFile("rss", ".xml", new File(GlobalSettings.ARCHIVE_DIR));
  }

  /**
   * @return Returns the archive.
   */
  public Archive getArchive() {
    return archive;
  }

  /**
   * Load the archive
   * 
   * @return Archive The archive
   */
  public Archive loadArchive() {
    Archive archive = new Archive(this);
    ArrayList files = new ArrayList();

    /** Foreach items in the archive */
    Set entrySet = index.getIndex().entrySet();
    for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) {
      Map.Entry entry = (Map.Entry) iterator.next();

      String feedurl = (String) entry.getKey();
      String filename = (String) entry.getValue();
      files.add(filename);

      /** Load the archive item if it exists as file */
      if (isArchiveFileExisting(filename)) {
        ArchiveItem item = loadArchiveItem(feedurl);
        archive.addItem(item);
      }
    }

    /** Add index to files */
    files.add("index.xml");

    File[] allFiles = new File(GlobalSettings.ARCHIVE_DIR).listFiles();

    /** Cleanup files that are not listed in the index */
    for (int a = 0; a < allFiles.length; a++)
      if (allFiles[a].isFile() && !files.contains(allFiles[a].getName()))
        allFiles[a].delete();

    return archive;
  }

  /**
   * Save all archive items
   */
  private void saveArchive() {
    ArrayList toDelete = new ArrayList();

    /** Synchronize on the Archive */
    Hashtable items = archive.getItems();
    synchronized (items) {
      Set entrySet = items.entrySet();

      /** For each Archive-Item */
      for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) {
        Map.Entry entry = (Map.Entry) iterator.next();
        String feedurl = (String) entry.getKey();
        ArchiveItem item = (ArchiveItem) entry.getValue();

        /** Synchronize on Entries */
        Hashtable entries = item.getEntries();
        synchronized (entries) {

          /** Save this item because it has entries */
          if (entries.size() > 0) {
            saveArchiveItem(item);
          }

          /** Delete this item because it has no entries */
          else {
            toDelete.add(feedurl);

            /** Delete archive item file */
            if (item.getFile().getName() != null && isArchiveFileExisting(item.getFile().getName()))
              deleteArchiveFile(item.getFile().getName());
          }
        }
      }
    }

    /** Delete from Index */
    synchronized (index) {
      for (int a = 0; a < toDelete.size(); a++)
        index.removeIndex(toDelete.get(a));
    }
  }

  /**
   * Sync all changes to the Archive with the File-System.
   */
  public synchronized void flush() {
    saveArchive();
    saveIndex();
  }

  /**
   * Startup process
   */
  public void startup() {
    index = loadIndex();
    archive = loadArchive();
  }

  /**
   * Delete a file from the archive
   * 
   * @param fileName The file to delete
   */
  private void deleteArchiveFile(String fileName) {
    new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + fileName).delete();
  }

  /**
   * Check if the given archive file is existing
   * 
   * @param fileName The name of the file
   * @return TRUE if the file exists in the archive
   */
  private boolean isArchiveFileExisting(String fileName) {
    return new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + fileName).exists();
  }

  /**
   * Check if the index file is exsiting
   * 
   * @return TRUE if the archive index file exists
   */
  private boolean isIndexExisting() {
    return new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + "index.xml").exists();
  }

  /**
   * Load an archive item from the archive
   * 
   * @param feedurl The URL of the feed
   * @return ArchiveItem The archive item from the archive
   */
  private ArchiveItem loadArchiveItem(String feedurl) {
    ArchiveItem archiveItem = new ArchiveItem(feedurl);

    /** Get the filename of the archive item */
    String itemFileName = index.getValue(feedurl);
    archiveItem.setFile(new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + itemFileName));

    try {
      Document document = builder.build(new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + itemFileName));
      Element root = document.getRootElement();
      List entries = root.getChildren("entry");
      Iterator entriesIt = entries.iterator();

      /** Load all archive entries from the archive item */
      while (entriesIt.hasNext()) {
        Element entry = (Element) entriesIt.next();
        Element newstitle = entry.getChild("newstitle");
        Element newslink = entry.getChild("newslink");
        Element newsdate = entry.getChild("newsdate");

        ArchiveEntry rssOwlArchiveEntry = new ArchiveEntry(newslink.getText(), newstitle.getText(), (newsdate != null) ? newsdate.getText() : "NULL");
        archiveItem.addEntry(rssOwlArchiveEntry);
      }
    } catch (JDOMException e) {
      GUI.logger.log("loadArchiveItem()", e);
    } catch (IOException e) {
      GUI.logger.log("loadArchiveItem()", e);
    } catch (IllegalArgumentException e) {
      GUI.logger.log("loadArchiveItem()", e);
    }

    /** Mark as no need of being saved on shutdown */
    archiveItem.setDirty(false);

    return archiveItem;
  }

  /**
   * Load the archive index
   * 
   * @return ArchiveIndex The archive index
   */
  private ArchiveIndex loadIndex() {
    ArchiveIndex index = new ArchiveIndex(this);

    /** If the index does not exist, return an empty one */
    if (!isIndexExisting())
      return index;

    try {

      /** Parse index.xml */
      Document document = builder.build(new File(GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + "index.xml"));

      Element root = document.getRootElement();
      List items = root.getChildren("item");
      Iterator itemsIt = items.iterator();

      /** Foreach item */
      while (itemsIt.hasNext()) {
        Element item = (Element) itemsIt.next();
        Element itemKey = item.getChild("feedurl");
        Element itemValue = item.getChild("filename");

        /** Add entry to index */
        index.addIndex(itemKey.getText(), itemValue.getText());
      }
    } catch (JDOMException e) {
      GUI.logger.log("loadIndex()", e);
    } catch (IOException e) {
      GUI.logger.log("loadIndex()", e);
    } catch (IllegalArgumentException e) {
      GUI.logger.log("loadIndex()", e);
    }
    return index;
  }

  /**
   * Save an archiveitem to a unique file
   * 
   * @param archiveItem An archive item that contains severyl archive entries
   */
  private void saveArchiveItem(ArchiveItem archiveItem) {

    /** Return in case this Item has not changed since loaded */
    if (!archiveItem.isDirty())
      return;

    /** Update Dirty Flag */
    archiveItem.setDirty(false);

    /** Create Document for the Item */
    Document document = new Document();
    Element root = new Element("item");
    document.setRootElement(root);

    /** Save each entry */
    Enumeration elements = archiveItem.getEntries().elements();
    while (elements.hasMoreElements()) {
      ArchiveEntry rssOwlArchiveEntry = (ArchiveEntry) elements.nextElement();
      Element entry = new Element("entry");
      root.addContent(entry);

      Element newstitle = new Element("newstitle");
      newstitle.setText(rssOwlArchiveEntry.getNewsTitle());
      entry.addContent(newstitle);

      Element newslink = new Element("newslink");
      newslink.setText(rssOwlArchiveEntry.getNewsLink());
      entry.addContent(newslink);

      Element newsdate = new Element("newsdate");
      newsdate.setText(rssOwlArchiveEntry.getNewsdate());
      entry.addContent(newsdate);
    }

    /** Write item */
    XMLShop.writeXML(document, archiveItem.getFile());
  }

  /**
   * Save the archive index
   */
  private void saveIndex() {

    /** First check if the Index is in need of being saved */
    if (!index.isDirty())
      return;

    /** Update Dirty Flag */
    index.setDirty(false);

    /** Root */
    Document document = new Document();
    Element root = new Element("index");
    document.setRootElement(root);

    /** Synchronize on the Index */
    Hashtable indexTable = index.getIndex();
    synchronized (indexTable) {
      Set entrySet = indexTable.entrySet();

      /** For each Index Entry */
      for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) {
        Map.Entry entry = (Map.Entry) iterator.next();
        String feedurl = (String) entry.getKey();
        String filename = (String) entry.getValue();

        /** Create new entry */
        Element item = new Element("item");
        root.addContent(item);

        /** Key of entry */
        Element entryKey = new Element("feedurl");
        entryKey.setText(feedurl);
        item.addContent(entryKey);

        /** Value of entry */
        Element entryValue = new Element("filename");
        entryValue.setText(filename);
        item.addContent(entryValue);
      }
    }

    /** Write Index */
    XMLShop.writeXML(document, GlobalSettings.ARCHIVE_DIR + GlobalSettings.PATH_SEPARATOR + "index.xml");
  }
}