/*
 * 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 uk.ac.vamsas.client.*;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Vector;

/**
 * Handler for the clientsFile within a vamsas session thread.
 * 
 * @author jim
 */
public class ClientsFile extends ListFile {
  private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
      .getLog(ClientsFile.class);

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

  public ClientsFile(File filelist) throws IOException {
    super(filelist);
  }

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

          ObjectInputStream is = new ObjectInputStream(fileLock
              .getBufferedInputStream(true));
          Object o;
          o = is.readObject();
          if (o != null) {
            try {
              clients = (ClientHandle[]) o;
            } catch (Exception e) {
              System.err.println("Garbage in the clientHandle list "
                  + this.sessionFile);
            }
          }
        }
        return clients;
      } catch (FileNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace(System.err);
      } catch (Exception e) {
        e.printStackTrace(System.err);
      }
    }
    return null;
  }

  /**
   * get the clientList from the file. May return null if lock failed!
   * 
   * @return clientList
   */
  public ClientHandle[] retrieveClientList() {
    if (lockFile()) {
      ClientHandle[] clients = retrieveClientHandles();
      unlockFile();
      return clients;
    }
    return null;
  }

  /**
   * get list from the locked ClientList.
   * 
   * @param extantlock
   * @return clientList or null if lock failed (or file was empty)
   */
  public ClientHandle[] retrieveClientList(Lock extantlock) {
    if (lockFile(extantlock)) {
      ClientHandle[] clients = retrieveClientHandles();
      unlockFile();
      return clients;
    }
    return null;
  }

  /**
   * adds clientHandle me to the clientList under an existing lock extantLock.
   * 
   * @param me
   * @param extantLock
   * @return client index in list or 0 if lock was invalid or addClient
   *         operation failed.
   */
  public int addClient(ClientHandle me, Lock extantLock) {
    return addClient(me, true, extantLock);
  }

  /**
   * adds clientHandle me to the clientList under an existing lock.
   * 
   * @param me
   *          - clientHandle
   * @param disambig
   *          - if true then add will fail if an identical clientHandle already
   *          exists
   * @param extantLock
   *          - existing lock
   * @return client index in list or 0 if addClient (or the lock) failed.
   */

  public int addClient(ClientHandle me, boolean disambig, Lock extantLock) {
    if (lockFile(extantLock)) {
      syncnum = addClient(me, disambig);
      unlockFile();
      return syncnum;
    }
    return 0;
  }

  /**
   * adds the ClientHandle to the list - if it is not unique, then the
   * ClientHandle object is modified to make it unique in the list. returns the
   * clientNumber for the client in the session.
   * 
   * @param me
   * @return
   */

  public int addClient(ClientHandle me) {
    syncnum = addClient(me, true);
    unlockFile();
    return syncnum;
  }

  /**
   * removes 'me' from the session ClientList without complaint if 'me' isn't in
   * the clientList already.
   * 
   * @param me
   *          client handle to be removed
   * @param clientlock
   *          existing lock passed from watcher.
   */
  public void removeClient(ClientHandle me, Lock clientlock) {
    int mynum = -1;
    if (lockFile(clientlock)) {
      ClientHandle[] clients = retrieveClientHandles();
      if (clients != null) {
        if ((syncnum <= 0 || syncnum > clients.length)
            || clients[syncnum - 1] != me) {
          for (int i = 0, j = clients.length; i < j; i++)
            if (clients[i].equals(me)) {
              mynum = i;
              break;
            }
        } else {
          mynum = syncnum - 1;
        }
        if (mynum > -1) {
          ClientHandle[] newlist = new ClientHandle[clients.length - 1];
          for (int k = 0, i = 0, j = clients.length; i < j; i++)
            if (i != mynum)
              newlist[k++] = clients[i];
          if (!putClientList(newlist))
            throw new Error("Failed to write new clientList!"); // failed to put
                                                                // the
                                                                // clientList to
                                                                // disk.
        }
      }
      unlockFile();
    } else {
      throw new Error("Couldn't get lock for "
          + ((sessionFile == null) ? "Unitialised sessionFile in ClientsFile"
              : sessionFile.getAbsolutePath()));
    }
  }

  /**
   * Adds a ClientHandle to the ClientList file - optionally disambiguating the
   * ClientHandle (modifes the URN). Note: Caller is left to release the lock on
   * the ClientList.
   * 
   * @param me
   * @param disambiguate
   *          - flag indicating if the URN for me should be disambiguated to
   *          differentiate between sessions.
   * @return index of clientHandle in new list, or -1-position of existing
   *         clientHandle (if disambiguate is true)
   */
  protected int addClient(ClientHandle me, boolean disambiguate) {
    int newclient = 0;
    int tries = 5;
    while (tries-- > 0 && !lockFile())
      try {
        Thread.sleep(1);
      } catch (Exception e) {
      }
    ;
    if (lockFile()) {
      ClientHandle[] clients = retrieveClientHandles();
      if (me.getClientUrn() == null) {
        // TODO: move this into ClientUrn as a standard form method.
        me.setClientUrn("vamsas://" + me.getClientName() + ":"
            + me.getVersion() + "/");
      }
      if (clients == null) {
        clients = new ClientHandle[1];
        clients[0] = me;
        newclient = 1;
      } else {
        int k = 0;
        for (int i = 0, j = clients.length; i < j; i++) {
          if (clients[i].equals(me)) {
            if (disambiguate) {
              while (clients[i].equals(me)) {
                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;
        ClientHandle[] newlist = new ClientHandle[clients.length + 1];
        for (i = 0, j = clients.length; i < j; i++)
          newlist[i] = clients[i];
        newlist[j] = me;
        clients = newlist;
        newclient = j + 1;
      }
      if (!putClientList(clients))
        return 0; // failed to put the clientList to disk.
    }
    return newclient;
  }

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

  /**
   * safely writes clients array to the file referred to by sessionFile.
   * 
   * @param clients
   * @return true if successful write. Throws Errors otherwise.
   */
  protected boolean putClientList(ClientHandle[] clients) {
    if (lockFile()) {
      File templist = null;
      if (backup) {
        templist = backupSessionFile();
      }
      if (!backup || (templist != null)) {
        int retries = 3;
        while (retries-- > 0) {
          try {
            ObjectOutputStream os = new ObjectOutputStream(fileLock
                .getBufferedOutputStream(true));
            log.debug("About to write " + clients.length
                + " clientHandles to output stream.");
            os.writeObject(clients);
            os.close();
            // All done - remove the backup.
            if (backup)
              templist.delete();
            templist = null;
            retries = -1;
          } catch (Exception e) {
            System.err.println("Serious - problems writing to sessionFile.");
            if (retries > 0 && templist != null) {
              System.err.println("Recovering from Backup in "
                  + templist.getAbsolutePath());
              templist.renameTo(fileLock.target);
            }
            e.printStackTrace(System.err);
          }
        }
        if (retries > -2) {
          System.err
              .println("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: "
          + ((sessionFile == null) ? "Unitialized ClientsFile"
              : " failed to get lock on " + sessionFile.getAbsolutePath()));
    }
    // successful!
    return true;
  }

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

      }
    }

  }
}
