/*
 * 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.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.OverlappingFileLockException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import uk.ac.vamsas.client.ClientHandle;
import uk.ac.vamsas.client.Events;
import uk.ac.vamsas.client.InvalidSessionUrnException;
import uk.ac.vamsas.client.UserHandle;

/**
 * Does all the IO operations for a SimpleClient instance accessing a
 * SimpleClient vamsas session.
 * 
 * Basically, it defines the various standard names for the files in the session
 * directory (that maps to the sessionUrn), provides constructors for the file
 * handlers and watchers of those file entities, and some higher level methods
 * to check and change the state flags for the session.
 * 
 * TODO: move the stuff below to the SimpleClientFactory documentation. much may
 * not be valid now : Vamsas client is intialised with a path to create live
 * session directories. This path may contain a vamsas.properties file that sets
 * additional parameters (otherwise client just uses the one on the classpath).
 * 
 * A vamsas session consists of : SessionDir - translates to urn of a live
 * session. Contains: Vamsas Document (as a jar), Session client list file, both
 * of which may be locked, and additional temporary versions of these files when
 * write operations are taking place.
 * 
 * Zip file entries - vamsasdocument.xml : core info one or more: -
 * <applicationname>.version.sessionnumber.raw (string given in
 * vamsasdocument.xml applicationData entry)
 * 
 * Lockfile - filename given in the vamsasdocument.xml. Should be checked for
 * validity by any client and rewritten if necessary. The lockfile can point to
 * the jar itself. Mode of operation. Initially - documentHandler either: -
 * creates a zip for a new session for the client - connect to an existing
 * session zip 1. reads session urn file 2. waits for lock 3. examines session -
 * decide whether to create new application data slice or connect to one stored
 * in session. 4. writes info into session file 5. releases lock and generates
 * local client events. 6. Creates Watcher thread to generate events.
 * 
 * During the session - Update watcher checks for file change -
 * 
 * Procedures for file based session message exchange - session document
 * modification flag
 * 
 */

public class VamsasSession {
  /**
   * indicator file for informing other processes that they should finalise
   * their vamsas datasets for storing into a vamsas archive.
   */
  public static final String CLOSEANDSAVE_FILE = "stored.log";

  /**
   * session file storing the last_stored_stat data
   */
  public static final String MODIFIEDDOC_FILE = "modified";

  private SimpleSessionManager sessionManager = null;

  /**
   * Count of cycles before considering the current client as the last one of
   * the session (if no other client registered as active )
   */
  private final int watchCycleCountBeforeLastClient = 1220;

  /**
   * time between checking
   */
  public int WATCH_SLEEP = 30;

  protected String clientFileDirectory = "clients";

  /**
   * called to clear update flag after a successful offline storage event
   */
  protected void clearUnsavedFlag() {
    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
        MODIFIEDDOC_FILE));
    if (!laststored.clearFlag())
      log.warn("Unsaved flag was not cleared for " + sessionDir);
  }

  /**
   * called to indicate session document has been modified.
   * 
   */
  protected void setUnsavedFlag() {
    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
        MODIFIEDDOC_FILE));
    if (!laststored.setFlag())
      log.warn("Couldn't set the Unsaved flag for " + sessionDir);
  }

  /**
   * 
   * @return true if session document has been modified since last offline
   *         storage event
   */
  protected boolean getUnsavedFlag() {
    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
        MODIFIEDDOC_FILE));
    return laststored.checkFlag();
  }

  /**
   * log file location
   */
  public static final String SESSION_LOG = "Log.txt";

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

  protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");

  /**
   * the appender that writes to the log file inside the session's directory.
   */
  private FileAppender slogAppender = null;

  /**
   * setup the sessionLog using Log4j.
   * 
   * @throws IOException
   */
  private void initLog() throws IOException {
    // TODO: fix session event logging
    // LATER: make dedicated appender format for session log.
    /*
     * Appender app = slog.getAppender("log4j.appender.SESSIONLOG"); //
     * slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir,
     * SESSION_LOG).getAbsolutePath())); // slog.addAppender(new
     * FileAppender(app.getLayout(), new File(sessionDir,
     * SESSION_LOG).getAbsolutePath())); for (Enumeration e =
     * slog.getAllAppenders() ; e.hasMoreElements() ;) {
     * System.out.println(e.nextElement()); }
     */

    if (slog != null) {
      File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
      slog.addAppender(slogAppender = new FileAppender(new PatternLayout(
          "%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(),
          true));
    } else {
      log.info("No appender for SessionLog");
    }
  }

  private void closeSessionLog() {
    if (slog != null) {
      if (slogAppender != null) {
        slog.removeAppender(slogAppender);
        slogAppender.close();
        slogAppender = null;
      }
    }
  }

  /**
   * the sessionDir is given as the session location for new clients.
   */
  protected File sessionDir;

  /**
   * holds the list of attached clients
   */
  ClientsFile clist;

  public static final String CLIENT_LIST = "Clients.obj";

  /**
   * holds the data
   */
  VamsasFile vamArchive;

  public static final String VAMSAS_OBJ = "VamDoc.jar";

  /**
   * sets up the vamsas session files and watchers in sessionDir1
   * 
   * @param sessionDir1
   */
  protected VamsasSession(File sessionDir1) throws IOException {
    this(sessionDir1, null);
  }
  private SimpleSessionHandle sessionHandle = null;
  /**
   * sets up the vamsas session files and watchers in sessionDir1
   * 
   * @param sessionDir1
   * @param extVamDoc
   *          null or an existing archive to initialise the session with
   * @throws any
   *           IOExceptions from creating session directory and files.
   * @throws error
   *           if both extVamDoc and sessionDir1 already exist (cannot import
   *           new data into session in this way)
   */
  protected VamsasSession(File sessionDir1, File extVamDoc) throws IOException {
    this(sessionDir1,extVamDoc,null);
  }
  /**
   * sets up the vamsas session files and watchers in sessionDir1
   * 
   * @param sessionDir1
   * @param extVamDoc
   *          null or an existing archive to initialise the session with
   * @param preferredName - optional string to use to generate a new session URI
   * @throws any
   *           IOExceptions from creating session directory and files.
   * @throws error
   *           if both extVamDoc and sessionDir1 already exist (cannot import
   *           new data into session in this way)
   *           MalformedURI if preferredName cannot be used to derive a URI of the form simpleclient::preferredName
   */
  public VamsasSession(File sessionDir1, File extVamDoc, String preferredName) throws IOException {
    // TODO: refactor to separate extVamDoc path from session URN - enables non-local URLs to be locally bound to sessions.
    if (sessionDir1 == null)
      throw new Error("Null directory for VamsasSession.");
    if (!sessionDir1.exists() && !sessionDir1.mkdir()) {
      throw new IOException("Failed to make VamsasSession directory in "
          + sessionDir1);
    }
    if (!sessionDir1.isDirectory() || !sessionDir1.canWrite()
        || !sessionDir1.canRead()) {
      throw new IOException("Cannot access '" + sessionDir1
          + "' as a read/writable Directory.");
    }
    boolean existingSession = checkSessionFiles(sessionDir1);
    if (existingSession) {
      if (extVamDoc != null) {
        throw new Error(
            "Client Initialisation Error: Cannot join an existing session directory with an  existing vamsas document to import.");
      } else {
        log.debug("Joining an existing session.");
      }
    }
    this.sessionDir = sessionDir1;
    // construct session URN
    SessionUrn sessUrn = new SessionUrn(sessionDir);
    if (extVamDoc!=null) {
      // Construct Session URN from the original vamsas document.
      sessUrn = new SessionUrn(extVamDoc);
    }
    if (preferredName!=null) {
      try {
            sessUrn = new SessionUrn(preferredName);
      } catch (InvalidSessionUrnException e) {
        throw new Error("Malformed URI : preferredName = "+preferredName,e);
      }
    }
    // create the session handle
    sessionHandle = new SimpleSessionHandle(sessUrn.getSessionUrn(),sessionDir);
    initSessionObjects();
    if (existingSession) {
      slog.debug("Initialising additional VamsasSession instance");
    } else {
      slog.debug("Founding client has joined VamsasSession instance");
    }

    log.debug("Attached to VamsasSession in " + sessionDir1);
    if (extVamDoc != null) {
      setVamsasDocument(extVamDoc);
    }
    slog.debug("Session directory created.");
    log.debug("Initialised VamsasSession in " + sessionDir1);
  }


  /**
   * tests presence of existing sessionfiles files in dir
   * 
   * @param dir
   * @return
   */
  private boolean checkSessionFiles(File dir) throws IOException {
    File c_file = new File(dir, CLIENT_LIST);
    File v_doc = new File(dir, VAMSAS_OBJ);
    if (c_file.exists() && v_doc.exists())
      return true;
    return false;
  }

  /**
   * create new empty files in dir
   * 
   */
  private void createSessionFiles() throws IOException {
    if (sessionDir == null)
      throw new IOException(
          "Invalid call to createSessionFiles() with null sessionDir");
    File c_file = new File(sessionDir, CLIENT_LIST);
    File v_doc = new File(sessionDir, VAMSAS_OBJ);
    if (!c_file.exists() && c_file.createNewFile())
      log.debug("Created new ClientFile " + c_file); // don't care if this
    // works or not
    if (!v_doc.exists()) {
      if (v_doc.createNewFile()) {
        log.debug("Created new Vamsas Session Document File " + v_doc);
      } else {
        log.warn("Didn't create Vamsas Session Document file in " + v_doc);
      }
    }
  }

  /**
   * construct SessionFile objects and watchers for each
   */
  private void initSessionObjects() throws IOException {
    createSessionFiles();
    if (clist != null || vamArchive != null)
      throw new IOException(
          "initSessionObjects called for initialised VamsasSession object.");
    clist = new ClientsFile(new File(sessionDir, CLIENT_LIST));
    vamArchive = new VamsasFile(new File(sessionDir, VAMSAS_OBJ));
    storedocfile = new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
    initLog();
  }

  /**
   * make a new watcher object for the clientFile
   * 
   * @return new ClientFile watcher instance
   */
  public FileWatcher getClientWatcher() {
    return new FileWatcher(clist.sessionFile);
  }

  /**
   * make a new watcher object for the vamsas Document
   * 
   * @return new ClientFile watcher instance
   */
  public FileWatcher getDocWatcher() {
    return new FileWatcher(vamArchive.sessionFile);
  }

  FileWatcher store_doc_file = null;

  public ClientsFile storedocfile = null;

  /**
   * make a new watcher object for the messages file
   * 
   * @return new watcher instance
   */
  public FileWatcher getStoreWatcher() {
    return new FileWatcher(new File(sessionDir, CLOSEANDSAVE_FILE));

  }

  /**
   * write to the StoreWatcher file to indicate that a storeDocumentRequest has
   * been made. The local client's storeWatcher FileWatcher object is updated so
   * the initial change is not registered.
   * 
   * @param client
   * @param user
   * @return
   */
  public void addStoreDocumentRequest(ClientHandle client, UserHandle user)
      throws IOException {
    // TODO: replace this with clientsFile mechanism
    SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
    while (!sfw.lockFile())
      log.debug("Trying to get lock for " + CLOSEANDSAVE_FILE);
    RandomAccessFile sfwfile = sfw.fileLock.getRaFile();
    sfwfile.setLength(0); // wipe out any old info.
    // TODO: rationalise what gets written to this file (ie do we want other
    // clients to read the id of the requestor?)
    sfwfile.writeUTF(client.getClientUrn() + ":" + user.getFullName() + "@"
        + user.getOrganization());
    sfw.unlockFile();
    if (store_doc_file != null)
      store_doc_file.setState();
    slog.info("FinalizeAppData request from " + user.getFullName() + " using "
        + client.getClientUrn() + "");
  }

  /**
   * create a new session with an existing vamsas Document - by copying it into
   * the session.
   * 
   * @param archive
   */
  public void setVamsasDocument(File archive) throws IOException {
    log.debug("Transferring vamsas data from " + archive + " to session:"
        + vamArchive.sessionFile);
    SessionFile xtantdoc = new SessionFile(archive);
    while (!vamArchive.lockFile())
      log.info("Trying to get lock for " + vamArchive.sessionFile);
    vamArchive.updateFrom(null, xtantdoc);
    xtantdoc.unlockFile();
    unlockVamsasDocument();
    // TODO: session archive provenance should be updated to reflect import from
    // external source
    log.debug("Transfer complete.");
  }

  /**
   * write session as a new vamsas Document (this will overwrite any existing
   * file without warning) TODO: test TODO: verify that lock should be released
   * for vamsas document.
   * 
   * @param destarchive
   */
  protected void writeVamsasDocument(File destarchive, Lock extlock)
      throws IOException {
    log.debug("Transferring vamsas data from " + vamArchive.sessionFile
        + " to session:" + destarchive);
    SessionFile newdoc = new SessionFile(destarchive);
    if (extlock == null && !vamArchive.lockFile())
      while (!vamArchive.lockFile())
        log.info("Trying to get lock for " + vamArchive.sessionFile);
    // TODO: LATER: decide if a provenance entry should be written in the
    // exported document recording the export from the session
    newdoc.updateFrom(null, vamArchive);
    // LATER: LATER: fix use of updateFrom for file systems where locks cannot
    // be made (because they don't have a lockManager, ie NFS/Unix, etc).
    vamArchive.unLock();
    newdoc.unlockFile();
    log.debug("Transfer complete.");
  }

  /**
   * extant archive IO handler
   */
  VamsasArchive _va = null;

  /**
   * Creates a VamsasArchive Vobject for accessing and updating document Note:
   * this will lock the Vamsas Document for exclusive access to the client.
   * 
   * @return session vamsas document
   * @throws IOException
   *           if locks fail or vamsas document read fails.
   */
  protected VamsasArchive getVamsasDocument() throws IOException {
    // check we haven't already done this once - probably should be done by
    // caller
    if (_va != null)
      return _va;
    // patiently wait for a lock on the document. (from
    // ArchiveClient.getUpdateable())
    long tries = 5000;
    while (vamArchive.getLock() == null && --tries > 0) {
      // Thread.sleep(1);
      log.debug("Trying to get a document lock for the " + tries + "'th time.");
    }
    if (tries == 0)
      throw new IOException("Failed to get lock for vamsas archive.");

    VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true,
        vamArchive);

    return va;
  }

  /**
   * Unlocks the vamsas archive session document after it has been closed.
   * 
   * @throws IOException
   */
  protected void unlockVamsasDocument() throws IOException {
    if (_va != null)
      _va.closeArchive();
    _va = null;
    if (vamArchive != null)
      vamArchive.unLock();

  }

  /**
   * create a uniquely named
   * uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in
   * the session Directory
   * 
   * @see java.io.File.createTempFile
   * @param pref
   *          Prefix for name
   * @param suff
   *          Suffix for name
   * @return SessionFile object configured for the new file (of length zero)
   * @throws IOException
   */
  protected SessionFile getTempSessionFile(String pref, String suff)
      throws IOException {
    File tfile = File.createTempFile(pref, suff, sessionDir);
    SessionFile tempFile = new SessionFile(tfile);
    return tempFile;
  }

  /**
   * add a IClient to the session
   * 
   * add the client to the client list file
   * 
   * @param client
   *          client to add to the session
   */
  protected void addClient(SimpleClient client) {
    if (client == null)
      slog.error("Try to add a null client to the session ");
    else {
      log.debug("Adding client " + client.getClientHandle().getClientUrn());
      getClientWatcherElement().haltWatch();
      clist.addClient(client.getClientHandle());

      log.debug("Added.");
      log.debug("Register Client as Active.");
      try {
        client.createActiveClientFile();
      } catch (IOException e) {
        log.debug("Error during  active client file creation.");
      }
      // tracks modification to the client list and readds client to the list
      getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
      getClientWatcherElement().enableWatch();

    }
  }

  /**
   * Handler for the client watcher.
   * 
   * If (the current client is not in the client list, it is added again;)
   */
  private class AddClientWatchCallBack implements WatcherCallBack {

    private SimpleClient client;

    /**
     * Inits the handler with the client to check in the list
     * 
     * @param client
     *          client to monitor in the client list
     */
    protected AddClientWatchCallBack(SimpleClient client) {
      this.client = client;
    }

    /**
     * If the client list is modified, checks if the current is still in the
     * list. otherwise, readds ti.
     * 
     * @return true to enable watcher, or false to disable it in future
     *         WatcherThread cycles.
     */
    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
      boolean isWatchEnable = watcher.isWatchEnabled();
      if (lock == null)// no update on the list
        return isWatchEnable;
      log.debug("change on the client list ");
      if (client != null) {

        // checks if the client is not already in the lists
        ClientHandle[] cl = clist.retrieveClientList(lock);// clist.retrieveClientList();
        boolean found = false;
        if (cl != null) {
          for (int chi = cl.length - 1; !found && chi > -1; chi--) {
            found = cl[chi].equals(this.client.getClientHandle());
          }

        }
        if (!found) {
          log.debug("client not in the list ");
          if (log.isDebugEnabled())
            log
                .debug("the client has not been found in the list. Adding it again :"
                    + cl);
          addClient(client);
        } else
          log.debug("client is in the list");

      }
      log.debug("isWatchEnable " + isWatchEnable);
      return isWatchEnable;
    }
  }

  /**
   * 
   * removes a client from the current session removes the client from the
   * session client list if the client is the last one from the session
   * (ClientList), the current session is removed from active session list.
   * 
   * The active should add them self to the client list. To insure to close the
   * session,when the current client is the lact active client, clears the list
   * of clients and when two cycles to insure there is no more active client,
   * that otherwise would have readd themself to the list
   * 
   * @param client
   *          client to remove
   */
  protected void removeClient(SimpleClient client)// IClient client)
  {
    if (client == null) {
      log.error("Null client passed to removeClient");
      return;
    }
    // ClientSessionFileWatcherElement cwe=getClientWatcherElement();
    // if (cwe!=null && cwe.isWatchEnabled()) {
    // cwe.haltWatch();
    // };
    // set handler to check is the the last active client of the session
    // Wait for several watchers cycle to see if the current client was the last
    // client active in the session.
    // if yes, close the session

    // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack
    // (client));
    // getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
    log.info("remove client from list");
    if (clistWatchElement != null) {
      clistWatchElement.haltWatch();
      clistWatchElement.watched.unlockFile();
    }
    // clist.clearList();
    // clist.unlockFile();
    log.info("list cleared");
    // if (cwe!=null) {
    // cwe.enableWatch();

    log.debug("Stopping EventGenerator..");
    client.evgen.stopWatching();
    // cwe.setHandler(null);
    // ask to the client to copy application data into the document
    client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client, null);
    boolean closeSession = isLastActiveClient(client);
    if (closeSession) {
      if (client.get_session().getUnsavedFlag()) {
        log.debug("Raising request-to-save event");
        client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
      }
      log.debug("Raising session shutdown event");
      client.evgen._raise(Events.SESSION_SHUTDOWN, null, client
          .getSessionHandle(), null);
      log.debug("All events raised for finalising session "
          + client.getSessionHandle().toString());
    }
    // cwe.haltWatch();
    client.evgen.stopWatching();
    try {
      log.debug("Attempting to release active client locks");
      client.releaseActiveClientFile();
    } catch (IOException e) {
      log.error("error during active file client release");
    }
    tidyUp();
    if (closeSession) {
      log.debug("Last active client: closing session");
      log.info("Closing session");
      closeSession(client.getSessionHandle());
    }
  }

  /**
   * close every file and stop.
   */
  private void tidyUp() {
    if (clist != null)
      clist.unlockFile();
    clist = null;
    storedocfile.unlockFile();
    storedocfile = null;
    closeSessionLog();
  }

  private boolean isLastActiveClient(SimpleClient client) {
    log.debug("Testing if current client is the last one.");
    log
        .debug("current client lockfile is '" + client.getClientlockFile()
            + "'");
    boolean noOtherActiveClient = true;
    // create, if need, subdirectory to contain client files
    File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);
    if (!clientlockFileDir.exists()) {
      log
          .error("Something wrong the active client file does not exits... should not happen");
      return false;
    }

    try {

      // no check every file in the directory and try to get lock on it.
      File[] clientFiles = clientlockFileDir.listFiles();
      if (clientFiles == null || clientFiles.length == 0) {// there is not file
        // on the directory.
        // the current
        // client should be
        // the last one.
        return true;
      }

      for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {
        File clientFile = clientFiles[i];
        log.debug("testing file for lock: " + clientFile.getAbsolutePath());
        if (client.getClientLock().isTargetLockFile(clientFile)) {
          log.debug("current client file found");
          continue;
        }
        if (clientFile != null && clientFile.exists()) {
          try {
            log.debug("Try to acquire a lock on the file");
            // Get a file channel for the file
            FileChannel channel = new RandomAccessFile(clientFile, "rw")
                .getChannel();

            // Use the file channel to create a lock on the file.
            // This method blocks until it can retrieve the lock.
            // java.nio.channels.FileLock activeClientFilelock = channel.lock();

            // Try acquiring the lock without blocking. This method returns
            // null or throws an exception if the file is already locked.
            try {
              java.nio.channels.FileLock activeClientFilelock = channel
                  .tryLock();

              // the lock has been acquired.
              // the file was not lock and so the corresponding application
              // seems to have die
              if (activeClientFilelock != null) {
                log
                    .debug("lock obtained : file must be from a crashed application");

                activeClientFilelock.release();
                log.debug("lock released");

                channel.close();
                log.debug("channel closed");

                // delete file
                clientFile.delete();
                log.debug("crashed application file deleted");

              } else {
                noOtherActiveClient = false;
                log.debug("lock not obtained : another application is active");
              }
            } catch (OverlappingFileLockException e) {
              // File is already locked in this thread or virtual machine
              // that the expected behaviour
              log.debug("lock not accessible ", e);
            }
          } catch (Exception e) {
            log.debug("error during lock testing ", e);
          }
        }
      }

    } catch (Exception e) {
      log.error("error during counting active clients");
    }
    return noOtherActiveClient;
  }

  /**
   * Handler for the client watcher. after a client have been removed
   * 
   * Checks if the client is not the last active one.
   * 
   * If (the current client is not in the client list readd it;)
   */
  private class RemoveClientWatchCallBack implements WatcherCallBack {

    private SimpleClient client;

    private boolean manualCheckOfClientCount = false;

    /**
     * Inits the handler with the client to check in the list
     * 
     * @param client
     *          client to monitor in the client list
     */
    protected RemoveClientWatchCallBack(SimpleClient client) {
      this.client = client;
    }

    /**
     * If the client list is modified, checks if the current is still in the
     * list. otherwise, readds ti.
     * 
     * @return true to enable watcher, or false to disable it in future
     *         WatcherThread cycles.
     */
    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
      // if lock is null, no client has been added since last, clear.
      // the client is then the last client
      if (client != null) {

        if (lock == null) {

          // checks if the client is not already in the lists
          // ClientHandle[] cl =
          // clist.retrieveClientList();//lock);//clist.retrieveClientList();

          boolean islastClient = true;
          if (manualCheckOfClientCount) {
            log.debug("manual checking of count of client");
            // checks if the client is not already in the lists
            ClientHandle[] cl = clist.retrieveClientList();// lock);//clist.retrieveClientList();
            if (cl == null || cl.length < 1)
            // {//no client has registered as active
            {
              islastClient = true;
              log.debug("list is empty");
            } else
              islastClient = false;
            log.debug("list is not empty");
          }
          // if(cl == null || cl.length<1 )
          // {//no client has registered as active
          if (islastClient) {
            // the client is the last one, so close current session
            log
                .info("FROMCLIENTLIST WATCHER: last client removed: closing session");
            closeSession(client);
          }
        } else {
          log.debug("not the last client found ");
          // ask to the client to cpoy application data into the document
          // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null,
          // client,null);

          // / }

        }
        log.debug("Stopping EventGenerator..");
        // TODO: ensure ClientsFile lock is really released!!
        // clist.unlockFile();
        client.evgen.stopWatching();
      }
      watcher.setHandler(null);// Do not check if the client is the last
      // client. watcher will shutdown anyway
      // watcher.haltWatch();
      // watcher.
      return false;
    }
  }

  /**
   * closes the current session, and send an event to the last client to close
   * the document
   * 
   * @param client
   *          the last client of the client
   */
  private void closeSession(SimpleClient client) {
    // close document
    client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
    log.debug("close document request done");
    closeSession(client.getSessionHandle());
  }

  /**
   * CLoses the current session
   * 
   * @param sessionHandle
   *          sessionHandle of the session to remove
   */
  private void closeSession(uk.ac.vamsas.client.SessionHandle sessionHandle) {
    //if (sessionHandle instanceof SimpleSessionHandle)
    //{
      getSessionManager().removeSession(sessionHandle);
          log.debug("Session removed");
    //} else {
    //  log.error("Cannot close a sessionHandle (URN="+sessionHandle.getSessionUrn()+") which is of type "+sessionHandle.getClass());
     // throw new Error("Tried to close a non-SimpleClient vamsas sessionHandle");
    //}
  }

  /**
   * @return the sessionManager
   */
  protected SimpleSessionManager getSessionManager() {
    return sessionManager;
  }

  /**
   * @param sessionManager
   *          the sessionManager to set
   */
  protected void setSessionManager(SimpleSessionManager sessionManager) {
    this.sessionManager = sessionManager;
  }

  public ClientsFile getStoreDocFile() {
    if (storedocfile == null) {

    }
    return storedocfile;
  }

  ClientSessionFileWatcherElement clistWatchElement = null;

  /**
   * get or create a watcher on clist.
   * 
   * @return the contents of clistWatchElement or initialise it
   */
  public ClientSessionFileWatcherElement getClientWatcherElement() {
    if (clistWatchElement == null) {
      clistWatchElement = new ClientSessionFileWatcherElement(clist, null);
    }
    return clistWatchElement;
  }

  public uk.ac.vamsas.client.simpleclient.SimpleSessionHandle getSessionUrn() {
    return sessionHandle;
  }
}
