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

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;

import uk.ac.vamsas.client.AppDataOutputStream;
import uk.ac.vamsas.client.ClientHandle;
import uk.ac.vamsas.client.IVorbaIdFactory;
import uk.ac.vamsas.client.SessionHandle;
import uk.ac.vamsas.client.UserHandle;
import uk.ac.vamsas.client.Vobject;
import uk.ac.vamsas.client.VorbaId;
import uk.ac.vamsas.client.simpleclient.FileWatcher;
import uk.ac.vamsas.client.simpleclient.IdFactory;
import uk.ac.vamsas.client.simpleclient.SessionFile;
import uk.ac.vamsas.client.simpleclient.SimpleDocBinding;
import uk.ac.vamsas.client.simpleclient.SimpleDocument;
import uk.ac.vamsas.client.simpleclient.VamsasArchive;
import uk.ac.vamsas.client.simpleclient.VamsasArchiveReader;
import uk.ac.vamsas.client.simpleclient.VamsasFile;
import uk.ac.vamsas.objects.core.AppData;
import uk.ac.vamsas.objects.core.ApplicationData;
import uk.ac.vamsas.objects.core.User;
import uk.ac.vamsas.objects.core.VamsasDocument;
import uk.ac.vamsas.objects.utils.AppDataReference;
import uk.ac.vamsas.objects.utils.DocumentStuff;
import uk.ac.vamsas.objects.utils.ProvenanceStuff;
import uk.ac.vamsas.objects.utils.SeqSet;
import uk.ac.vamsas.objects.utils.document.VersionEntries;
import uk.ac.vamsas.test.objects.Core;

/**
 * @author jimp test the VamsasFile routines for watching, reading and updating
 *         a vamsas document jar file. simple document access base class.
 */
public class ArchiveClient extends IdFactory {

  private Log log = LogFactory.getLog(ArchiveClient.class);

  // protected UserHandle user=null;
  // protected ClientHandle me = new ClientHandle("ArchiveClient","0.01");
  VamsasFile vsess;

  /**
   * @param user
   * @param vsess
   */
  public ArchiveClient(UserHandle user, VamsasFile vsess) {
    super(new SessionHandle("vamsasfile://" + vsess.getVamsasFile()),
        new ClientHandle("ArchiveClient", "0.01"), user);
    this.vsess = vsess;
    valid();
  }

  private void _openVsess(File vsess) {
    try {
      this.vsess = new VamsasFile(vsess);
    } catch (Exception e) {
      log.error("Couldn't open session for file " + vsess, e);
      this.vsess = null;
    }
  }

  public ArchiveClient(String username, String organization, File vsess) {
    super(new SessionHandle("vamsasfile://" + vsess), new ClientHandle(
        "ArchiveClient", "0.01"), new UserHandle(username, organization));
    _openVsess(vsess);
    valid();
  }

  public ArchiveClient(String username, String organization, String clientName,
      String clientVersion, File vsess) {
    super(new SessionHandle("vamsasfile://" + vsess), new ClientHandle(
        clientName, clientVersion), new UserHandle(username, organization));
    _openVsess(vsess);
    valid();
  }

  public void valid() {
    if (vsess == null)
      throw new Error("ArchiveClient instance is invalid!.");
  }

  /**
   * set this to false if watch loop should end immediately
   */
  protected boolean watchForChange = true;

  public static int WATCH_SLEEP = 300;

  /**
   * watch the document file for updates.
   * 
   * @param time
   *          - length of time to watch for.
   * @return read only IO interface for session document.
   */
  public ClientDoc watch(long time) {
    valid();
    vsess.unLock(); // doh.
    FileWatcher watcher = new FileWatcher(vsess.getVamsasFile());
    // watcher.setState();
    watchForChange = true;
    long endtime = System.currentTimeMillis() + time;
    try {
      uk.ac.vamsas.client.simpleclient.Lock doclock;
      do {
        doclock = watcher.getChangedState();
        if (doclock == null)
          Thread.sleep(WATCH_SLEEP);
      } while (watchForChange && doclock == null
          && (time == 0 || endtime > System.currentTimeMillis())); // tuning.
      if (doclock == null)
        return null;
      else {
        return getUpdateable(vsess.getLock(doclock));
        /*
         * VamsasArchiveReader varc = new
         * VamsasArchiveReader(vsess.getVamsasFile()); return
         * _getReadonly(varc);
         */
      }
    } catch (Exception e) {
      log.error("Whilst watching file " + vsess.getVamsasFile(), e);
    }
    return null;
  }

  // from ClientDocument.getClientAppdata
  private AppData[] getAppData(VamsasDocument doc) {
    // TODO: codefest - not yet tested or merged in to SimpleClient
    if (doc == null) {
      log.debug("extractAppData called for null document object");
      return null;
    }
    AppData appsGlobal = null, usersData = null;
    Vector apldataset = AppDataReference.getUserandApplicationsData(doc, this
        .getUserHandle(), this.getClientHandle());
    if (apldataset != null) {
      if (apldataset.size() > 0) {
        AppData clientdat = (AppData) apldataset.get(0);
        if (clientdat instanceof ApplicationData) {
          appsGlobal = (ApplicationData) clientdat;
          if (apldataset.size() > 1) {
            clientdat = (AppData) apldataset.get(1);
            if (clientdat instanceof User) {
              usersData = (User) clientdat;
            }
            if (apldataset.size() > 2)
              log.info("Ignoring additional (" + (apldataset.size() - 2)
                  + ") AppDatas returned by document appdata query.");
          }
        } else {
          log.warn("Unexpected entry in AppDataReference query: id="
              + clientdat.getVorbaId() + " type="
              + clientdat.getClass().getName());
        }
        apldataset.removeAllElements(); // destroy references.
      }
    }
    return new AppData[] { appsGlobal, usersData };
  }

  protected ClientDoc _getReadonly(VamsasArchiveReader vreader)
      throws IOException, ValidationException, MarshalException {
    valid();
    if (vreader != null) {
      SimpleDocBinding docb = new SimpleDocBinding();
      docb.setVorba(this);
      VamsasDocument d;
      d = docb.getVamsasDocument(vreader);

      if (d != null) {
        ClientDoc creader = new ClientDoc(d, null, vreader, getClientHandle()
            .getClientUrn(), getProvenanceUser(), getVorbaIdHash());
        return creader;
      }
    }
    return null;
  }

  /**
   * from SimpleClient
   * 
   * @return user field for a provenance entry
   */
  protected String getProvenanceUser() {
    return new String(getUserHandle().getFullName() + " ["
        + getClientHandle().getClientUrn() + "]");
  }

  public ClientDoc getUpdateable() {
    return getUpdateable(null);
  }

  public ClientDoc getUpdateable(uk.ac.vamsas.client.simpleclient.Lock lock) {
    getVorbaIdHash().clear();
    valid();
    try {
      // patiently wait for a lock on the document.
      long tries = 5000;
      while (lock == null
          && ((lock = vsess.getLock()) == null || !lock.isLocked())
          && --tries > 0) {
        // Thread.sleep(1);
        log.debug("Trying to get a document lock for the " + tries
            + "'th time.");
      }
      VamsasArchive varc = new VamsasArchive(vsess, true, false); // read
                                                                  // archive,
                                                                  // write as
                                                                  // vamsasDocument,
                                                                  // don't erase
                                                                  // original
                                                                  // contents.
      varc.setVorba(this);
      VamsasDocument d = varc.getVamsasDocument(getProvenanceUser(),
          "Created new document.", VersionEntries.latestVersion()); // VAMSAS:
                                                                    // provenance
                                                                    // user and
                                                                    // client
                                                                    // combined

      if (d == null) {
        log
            .warn("Backing out from opening a VamsasArchive writable IO session");
        varc.cancelArchive();
        return null;
      }
      ClientDoc cdoc = new ClientDoc(d, varc, varc.getOriginalArchiveReader(),
          getClientHandle().getClientUrn(), getProvenanceUser(),
          getVorbaIdHash());
      return cdoc;
      // do appHandle?
    } catch (Exception e) {
      log.error("Failed to get Updateable version of " + vsess.getVamsasFile(),
          e);
    }
    return null;
  }

  /**
   * trust client to not do anything stupid to the document roots which will now
   * be written to the archive.
   * 
   * @param cdoc
   * @return true if write was a success.
   */
  public boolean doUpdate(ClientDoc cdoc) {
    valid();
    if (cdoc == null) {
      log
          .warn("Invalid ClientDoc passed to uk.ac.vamsas.test.simpleclient.doUpdate()");
      return false;
    }
    if (cdoc.iohandler == null) {
      log
          .warn("Read only ClientDoc object passed to uk.ac.vamsas.test.simpleclient.doUpdate()");
      return false;
    }
    if (cdoc.iohandler.getVorba() != this) {
      log
          .error("Mismatch between ClientDoc instances and ArchiveClient instances!");
      return false;
    }
    try {
      // do any appDatas first.
      if (cdoc.iohandler.transferRemainingAppDatas())
        log.debug("Remaining appdatas were transfered.");
      cdoc.updateDocumentRoots();
      cdoc.iohandler.putVamsasDocument(cdoc.doc);
      cdoc.iohandler.closeArchive();
      this.extantids.clear();// we forget our ids after we close the document.
      cdoc.iohandler = null;
      cdoc = null;
      vsess.unLock();
    } catch (Exception e) {
      log.warn("While updating archive in " + vsess.getVamsasFile(), e);
      return false;
    }
    return true;
  }

  /**
   * @param args
   */
  public static void usage() {
    throw new Error("Usage: Username Organization VamsasFile [command,args]*");
  }

  public static void main(String[] args) {
    // really simple.
    if (args.length < 3)
      usage();

    ArchiveClient client = new ArchiveClient(args[0], args[1],
        new File(args[2]));
    ClientDoc cdoc = null;
    // sanity test.
    try {
      cdoc = client.getUpdateable();
      // ArchiveReports.reportDocument(cdoc.doc, cdoc.getReader(), true,
      // System.out);
      System.out.println("Report Roots :");
      ArchiveReports.rootReport(cdoc.getVamsasRoots(), true, System.out);
      cdoc.addVamsasRoot(Core.getDemoVamsas());
      System.out.println("Doing update.");
      client.doUpdate(cdoc);
      cdoc.closeDoc();
      cdoc = null;
      int u = 5;
      while (--u > 0) {
        System.out.println("Watch for more... (" + u + " left)");
        ClientDoc ucdoc = client.watch(0000);
        if (ucdoc != null) {
          System.out.println("****\nUpdate detected at " + new Date());
          ArchiveReports.reportDocument(ucdoc.doc, ucdoc.getReader(), true,
              System.out);
          ucdoc.closeDoc();
          ucdoc = null;
        } else {
          System.out.println("!!!! Null document update detected at "
              + new Date());
        }
      }
    } catch (Exception e) {
      client.log.error("Broken!", e);
    }
    System.out.println("Finished at " + new Date());
  }

  public uk.ac.vamsas.client.Vobject getObject(VorbaId id) {
    Hashtable idhash = this.getVorbaIdHash();
    if (idhash != null && idhash.containsKey(id))
      return (Vobject) idhash.get(id);
    return null;
  }
}
