/*
 * 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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.vamsas.client.SessionHandle;
import uk.ac.vamsas.client.simpleclient.SimpleSessionHandle;

/**
 * @author <a href="mailto:pierre@ebi.ac.uk">Pierre MARGUERITE</a>
 * 
 * 
 */
public class SessionsFile extends ListFile {

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

  /**
   * when set true - get FileNotFoundExceptions on WinXP when writing to locked
   * stream after the backup has been made (via the backupFile method)
   */
  boolean backup = false;

  /**
   * number of my session in list - passed back when a session is added to list,
   * and used (if valid) for quickly looking up presence of session handle in
   * the list.
   */
  private int syncnum = 1;

  /**
   * @param file
   */
  public SessionsFile(File file) throws java.io.IOException {
    super(file);
  }

  /**
   * internal method for getting sessionsList - ensures a lock has been made but
   * does not release it.
   * 
   * @return list of clients
   */
  private SimpleSessionHandle[] retrieveSessionHandles() {
    if (lockFile()) {
      try {
        SimpleSessionHandle[] sessions = null;
        if (this.fileLock.length() > 0) {

          ObjectInputStream is = new ObjectInputStream(this.fileLock
              .getBufferedInputStream(true));
          Object o;
          o = is.readObject();
          if (o != null) {
            try {
              if (o instanceof SimpleSessionHandle[]) {
                sessions = (SimpleSessionHandle[]) o;
              } else {
                // legacy support or incorrect path.
                if (o instanceof SessionHandle[]) {
                  log.info("Importing VAMSAS 0.1 clientHandle list.");
                  SessionHandle[] oldsess = (SessionHandle[]) o;
                  File sessdir;
                  sessions = new SimpleSessionHandle[oldsess.length];
                  for (int s=0;s<oldsess.length;s++)
                  {
                    try {
                      sessdir = new SessionUrn(oldsess[s].getSessionUrn()).asFile();
                      // try to locate it.
                      sessions[s] = new SimpleSessionHandle(oldsess[s].getSessionUrn(), sessdir);
                    } catch (Exception e)
                    {
                      // give up and report an error.
                      sessions = null;
                      throw new Exception("Legacy SessionFile problem. Couldn't locate "+oldsess[s]+" as a file.",e);
                    }
                  }
                }
              }
            } catch (Exception e) {
              log.error("Garbage in the clientHandle list " + this.sessionFile,
                  e);
            }
          }
          // is.close();
        }
        return sessions;
      } catch (FileNotFoundException e) {
        // e.printStackTrace(System.err);
        log.error(e);
      } catch (Exception e) {
        log.error(e);
        // e.printStackTrace(System.err);
      }
    }
    return null;
  }

  /**
   * get the SessionsList from the file. May return null if lock failed!
   * 
   * @return sessionsList
   */
  public SimpleSessionHandle[] retrieveSessionsList() {
    if (lockFile()) {
      SimpleSessionHandle[] clients = retrieveSessionHandles();
      unlockFile();
      return clients;
    }
    return null;
  }

  /**
   * get list from the locked SessionsList.
   * 
   * @param extantlock
   * @return sessionList or null if lock failed (or file was empty)
   */
  public SimpleSessionHandle[] retrieveSessionsList(Lock extantlock) {
    if (lockFile(extantlock)) {
      SimpleSessionHandle[] sessions = retrieveSessionHandles();
      unlockFile();
      return sessions;
    }
    return null;
  }

  /**
   * adds SessionHandle me to the sessionList under an existing lock extantLock.
   * 
   * @param newSession
   * @param extantLock
   * @return session index in list or 0 if lock was invalid or addSession
   *         operation failed.
   */
  public int addSession(SimpleSessionHandle newSession, Lock extantLock) {
    return addSession(newSession, true, extantLock);
  }

  /**
   * adds SessionsHandle me to the sessionsList under an existing lock.
   * 
   * @param newSession
   *          - sessionsHandle
   * @param disambig
   *          - if true then add will fail if an identical sessionHandle already
   *          exists
   * @param extantLock
   *          - existing lock
   * @return client index in list or 0 if addSession (or the lock) failed.
   */

  public int addSession(SimpleSessionHandle newSession, boolean disambig,
      Lock extantLock) {
    if (lockFile(extantLock)) {
      this.syncnum = addSession(newSession, disambig);
      unlockFile();
      return this.syncnum;
    }
    return 0;
  }

  /**
   * removes the current session from the SessionsList without complaint if the
   * session isn't in the sessionsList already.
   * 
   * @param session
   *          session handle to be removed
   * @param sessionlock
   *          existing lock passed from watcher.
   */
  public void removeSession(SessionHandle session, Lock sessionlock) {
    int mynum = -1;
    if (lockFile(sessionlock)) {

      SimpleSessionHandle[] sessions = retrieveSessionHandles();
      if (sessions != null) {
        if ((this.syncnum <= 0 || this.syncnum > sessions.length)
            || !session.equals(sessions[this.syncnum - 1])) {
          for (int i = 0, j = sessions.length; i < j; i++) {
            if (sessions[i].equals(session)) {
              mynum = i;
              break;
            }
          }
        } else {
          mynum = this.syncnum - 1;
        }

        if (mynum > -1) {
          SimpleSessionHandle[] newlist = new SimpleSessionHandle[sessions.length - 1];
          for (int k = 0, i = 0, j = sessions.length; i < j; i++)
            if (i != mynum)
              newlist[k++] = sessions[i];
          if (!putSessionsList(newlist))
            throw new Error("Failed to write new sessionsList!"); // failed to
                                                                  // put the
                                                                  // sessionList
                                                                  // to disk.
        }
      }
      unlockFile();
    } else {
      throw new Error(
          "Couldn't get lock for "
              + ((this.sessionFile == null) ? "Unitialised sessionFile in SessionsFile"
                  : this.sessionFile.getAbsolutePath()));
    }
  }

  /**
   * Adds a SessionHandle to the SessionList file - optionally disambiguating
   * the SessionHandle (modifes the URN). Note: Caller is left to release the
   * lock on the SessionList.
   * 
   * @param session
   * @param disambiguate
   *          - flag indicating if the URN for me should be disambiguated to
   *          differentiate between sessions.
   * @return index of sessionHandle in new list, or -1-position of existing
   *         sessionHandle (if disambiguate is true)
   */
  protected int addSession(SimpleSessionHandle session, boolean disambiguate) {
    int newsession = 0;
    int tries = 5;
    while (tries-- > 0 && !lockFile())
      try {
        Thread.sleep(1);
      } catch (Exception e) {
      }
    ;
    if (lockFile()) {
      SimpleSessionHandle[] sessions = retrieveSessionHandles();

      if (sessions == null) {
        sessions = new SimpleSessionHandle[1];
        sessions[0] = session;
        newsession = 1;
      } else {
        int k = 0;
        for (int i = 0, j = sessions.length; i < j; i++) {
          if (sessions[i].equals(session)) {
            if (disambiguate) {
              while (sessions[i].equals(session)) {
                // me.setClientUrn(me.getClientUrn() + k++); // TODO: make a
                // better
                // disambiguation of
                // urn.
              }
            } else {
              // will not write the ambiguous clientHandle to disk, just return
              // its index.
              return -1 - i;
            }
          }
        }
        int i, j;
        SimpleSessionHandle[] newlist = new SimpleSessionHandle[sessions.length + 1];
        for (i = 0, j = sessions.length; i < j; i++)
          newlist[i] = sessions[i];
        newlist[j] = session;
        sessions = newlist;
        newsession = j + 1;
      }
      if (!putSessionsList(sessions))
        return 0; // failed to put the clientList to disk.
    }
    return newsession;
  }

  /**
   * safely writes sessions array to the file referred to by sessionFile.
   * 
   * @param clients
   * @return true if successful write. Throws Errors otherwise.
   */
  protected boolean putSessionsList(SimpleSessionHandle[] clients) {
    if (lockFile()) {
      File templist = null;
      if (!this.backup || (templist = backupSessionFile()) != null) {
        int retries = 3;
        while (retries-- > 0) {
          try {
            ObjectOutputStream os = new ObjectOutputStream(this.fileLock
                .getBufferedOutputStream(true));
            log.debug("About to write " + clients.length
                + " sessionHandles to output stream.");
            os.writeObject(clients);
            // os.flush();
            os.close();
            // All done - remove the backup.
            if (this.backup)
              templist.delete();
            templist = null;
            retries = -1;
          } catch (Exception e) {
            // System.err
            // .println("Serious - problems writing to sessionFile.");
            log.error("Serious - problems writing to sessionFile.", e);
            if (retries > 0 && templist != null) {
              // System.err.println("Recovering from Backup in "
              // + templist.getAbsolutePath());
              log.error("Recovering from Backup in "
                  + templist.getAbsolutePath());
              templist.renameTo(this.fileLock.target);
            }
            // e.printStackTrace(System.err);
            log.error(e);
          }
        }
        if (retries > -2) {
          // System.err
          // .println("Serious - problems writing to sessionFile. Giving Up.");
          log.error("Serious - problems writing to sessionFile. Giving Up.");
          return false;
        }
      } else {
        throw new Error(
            "Couldn't create backup of the clientList before writing to it!");
      }
    } else {
      throw new Error("Could not lock the clientList: "
          + ((this.sessionFile == null) ? "Unitialized ClientsFile"
              : " failed to get lock on " + this.sessionFile.getAbsolutePath()));
    }
    // successful!
    return true;
  }

  public void clearList() {
    if (lockFile()) {
      try {
        FileOutputStream fout = this.fileLock.getFileOutputStream(true);
        fout.flush();
        fout.close();
      } catch (Exception e) {
        throw new Error("Problems trying to clear clientlist!", e);

      }
    }

  }
}
