/*
 * This file is part of the Vamsas Client version 0.2. 
 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, 
 *  Andrew Waterhouse and Dominik Lindner.
 * 
 * Earlier versions have also been incorporated into Jalview version 2.4 
 * since 2008, and TOPALi version 2 since 2007.
 * 
 * The Vamsas Client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * The Vamsas Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
 */
package uk.ac.vamsas.client.simpleclient;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.apache.tools.zip.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.vamsas.client.AppDataInputStream;
import uk.ac.vamsas.objects.utils.document.VersionEntries;

/**
 * Basic methods for accessing an existing Vamsas Archive, and Jar entry names
 * for creating new vamsas archives.
 * 
 * @author jimp
 * 
 */
public class VamsasArchiveReader {
  private static final int JARFILE_OPEN_RETRIES = 50;

  private static final int JARFILE_OPEN_RETRYWAIT = 1;

  private static Log log = LogFactory.getLog(VamsasArchiveReader.class);

  ZipFile jfile = null;

  String jfileName = "randomAccessFile";

  boolean stream = false; // true if we are seeking on the stream.

  RandomAccessFile rfile;

  // ZipInputStream jstream=null;
  Hashtable strmentries = null;

  private void streamInit() {
    // throw new Error("VamsasArchiveReader(Stream) Not implemented!");
    if (!stream) {
      log.debug("Skipping init for Jar Stream input.");
      return;
    } else {
      throw new Error(
          "Implementation error - we don't do streams - only files or RA files");
    }
    /*
     * strmentries = new Hashtable();
     * log.debug("Jar Stream input Initialisation"); try { rfile.seek(0); // no
     * buffering - we need to be able to move around the random access stream.
     * jstream = new ZipInputStream(new FileInputStream(rfile.getFD())); // no
     * manifest (probably) if (jstream.available()==0)
     * log.warn("Can't read from JarInputStream (Locked stream!)"); ZipEntry
     * entry=null; long pos=0; do { if ((entry=jstream.getNextEntry())!=null) {
     * if (strmentries.containsKey(entry.getName())) {
     * log.info("Only recording last of duplicate entries '"
     * +entry.getName()+"'"); } strmentries.put(entry.getName(), new
     * Long(pos++)); jstream.closeEntry(); } } while (entry!=null); } catch
     * (Exception e) { log.warn("Exceptions during init!",e); jstream=null; }
     */
  }

  public VamsasArchiveReader(File vamsasfile) {
    jfile = null;
    int retries = JARFILE_OPEN_RETRIES;
    Exception ex = null;
    if (vamsasfile.exists()) {
      while (jfile == null && --retries > 0) {
        try {
          jfile = new ZipFile(vamsasfile);
          jfileName = vamsasfile.toString();
        } catch (Exception e) {
          ex = e;
          jfile = null;
          try {
            Thread.sleep(JARFILE_OPEN_RETRYWAIT);
          } catch (Exception w) {
          }
          ;
        }
      }
      if (jfile == null && ex != null) {
        log.debug("non-serious? Exceptions when opening JarFile at "
            + vamsasfile, ex);
      }
    }
  }

  /**
   * in an ideal world - this constructor will create a reader object for the
   * locked file's random access stream.
   * 
   * @param vamsaslock
   */
  public VamsasArchiveReader(Lock vamsaslock) {
    jfile = null;
    if (vamsaslock == null || !vamsaslock.isLocked())
      throw new Error(
          "IMPLEMENTATION ERROR: Cannot create a VamsasArchiveReader without a valid lock.");
    // throw new
    // Error("VamsasArchiveReading from locked IO stream not yet implemented.");
    try {
      rfile = vamsaslock.getRaFile();
      jfile = new ZipFile(rfile);
      if (vamsaslock.target != null)
        jfileName = vamsaslock.target.toString();
    } catch (Exception e) {
      rfile = null;
      jfile = null;
      log.warn(
          "Unexpected IO Exception when accessing locked vamsas archive stream "
              + vamsaslock.target, e);
    }
    /*
     * stream = true; streamInit(); if (jstream==null) throw new
     * Error("Failed to open archive from Locked random access stream.");
     */
  }

  /**
   * the vamsas document version(s) handled by this Reader
   */
  final public static String DOCUMENT_VERSION = VersionEntries.BETA_VERSION;

  /**
   * name of the jarEntry containing a well formatted vamsas XML Document
   */

  final public static String VAMSASDOC = "vamsasDocument.xml";

  /**
   * name of the jarEntry containing a root VAMSAS element, and containing a
   * random sequence of VAMSAS DataSet elements
   */

  final public static String VAMSASXML = "vamsas.xml";

  /**
   * seeks jstream to the given entry name and reads it.
   * 
   * @param entryname
   * @return private JarEntry seekEntry(String entryname) { if (jstream==null)
   *         return null; if (!strmentries.containsKey(entryname)) return null;
   *         Long entrypos = (Long) strmentries.get(entryname); if
   *         (entrypos==null) { log.error("Null entry position for "+entryname);
   *         return null; } try { jstream=null; rfile.seek(0); jstream = new
   *         ZipInputStream(new FileInputStream(rfile.getFD())); ZipEntry entry
   *         = null; long epos = entrypos.longValue(); do { entry =
   *         jstream.getNextEntry(); } while (entry!=null && --epos>=0); //
   *         rfile.seek(entrypos.longValue()); // make a Jar entry from a zip
   *         entry. return new JarEntry(entry); } catch (Exception e) {
   *         log.warn("Whilst seeking for "+entryname, e); } return null; }
   */
  /**
   * 
   * @return JarEntry for VamsasArchiveReader.VAMSASDOC
   */
  protected ZipEntry getVamsasDocumentEntry() {
    return getJarEntry(VAMSASDOC);
  }

  /**
   * 
   * @return JarEntry for VamsasArchiveReader.VAMSASXML
   */
  protected ZipEntry getVamsasXmlEntry() {
    return getJarEntry(VAMSASXML);
  }

  /**
   * Test for valid vamsas document archive
   * 
   * @return true if getVamsasDocumentStream will return a stream likely to
   *         contain valid XML
   */
  public boolean isValid() {
    // TODO: check if VAMSASDOC is well formed (follows
    // www.vamsas.ac.uk/schemas/vamsasDocument.xsd) and all appData references
    // are resolvable - preferably as jar entries
    if (jfile != null) // || jstream!=null)
      return (getVamsasDocumentEntry() != null);
    return false;
  }

  protected ZipEntry getAppdataEntry(String AppdataRef) {
    ZipEntry entry;
    if (jfile == null || !isValid()
        || (entry = getJarEntry(AppdataRef)) == null)
      return null;

    return entry;
  }

  public InputStream getAppdataStream(String AppdataRef) {
    ZipEntry entry = getAppdataEntry(AppdataRef);
    try {
      if (entry != null)
        return getInputStream(entry);
    } catch (IOException e) {
      log.error("Failed when opening AppdataStream for " + AppdataRef, e);
    }
    return null;
  }

  /**
   * get the VamsasDocument input stream, if it exists.
   * 
   * @return null or valid input stream
   */
  public InputStream getVamsasDocumentStream() {
    InputStream vdoc;
    if (jfile == null || !isValid())
      return null;
    try {
      vdoc = getInputStream(getVamsasDocumentEntry());
    } catch (IOException e) {
      log.error("Whilst geting document stream", e);
      vdoc = null;
    }
    return vdoc;
  }

  /**
   * get the VamsasXML input stream, if it exists. Note: Deprecated beyond our
   * prealpha testing.
   * 
   * @return null or valid input stream.
   */

  public InputStream getVamsasXmlStream() {
    // log.warn("Deprecated call");
    ZipEntry xmle = getVamsasXmlEntry();
    InputStream vdoc;
    if (xmle == null)
      return null;
    try {
      vdoc = getInputStream(xmle);
    } catch (IOException e) {
      log.error("Whilst getting VamsasXmlStream", e);
      vdoc = null;
    }
    return vdoc;
  }

  /**
   * silently close the jar file.
   * 
   */
  public void close() {
    if (jfile != null) {
      try {
        jfile.close();
        rfile = null;
      } catch (Exception e) {
        log.error("Whilst closing JarFile " + jfileName, e);
      }
    }
  }

  /**
   * returns all entries not matching the filespec of a vamsas xml entry
   * 
   * @return array of entries.
   */
  public Vector getExtraEntries() {
    if (jfile == null || !isValid())
      return null;
    Vector e = new Vector();
    /*
     * if (jstream!=null) { Enumeration entries = strmentries.keys(); if
     * (entries!=null && entries.hasMoreElements()) { do { JarEntry el =
     * (JarEntry) entries.nextElement(); if (!el.getName().equals(VAMSASDOC) &&
     * !el.getName().equals(VAMSASXML)) e.add(new String(el.getName())); //
     * avoid references } while (entries.hasMoreElements()); } } else
     */
    Enumeration entries = jfile.getEntries();
    if (entries != null && entries.hasMoreElements()) {
      do {
        ZipEntry el = (ZipEntry) entries.nextElement();
        if (!el.getName().equals(VAMSASDOC) && !el.getName().equals(VAMSASXML))
          e.add(new String(el.getName())); // avoid references
      } while (entries.hasMoreElements());
    }
    return e;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry)
   */
  private InputStream getInputStream(ZipEntry ze) throws IOException {
    if (jfile != null)
      return jfile.getInputStream(ze);
    return null;
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.util.jar.JarFile#getJarEntry(java.lang.String)
   */
  private ZipEntry getJarEntry(String name) {
    if (jfile != null)
      return jfile.getEntry(name);
    return null;
  }
}
