/*
 * 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.net.MalformedURLException;

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

import uk.ac.vamsas.client.ClientHandle;
import uk.ac.vamsas.client.IClient;
import uk.ac.vamsas.client.IClientFactory;
import uk.ac.vamsas.client.InvalidSessionDocumentException;
import uk.ac.vamsas.client.InvalidSessionUrnException;
import uk.ac.vamsas.client.NoDefaultSessionException;
import uk.ac.vamsas.client.SessionHandle;
import uk.ac.vamsas.client.UserHandle;

/**
 * 
 * creates a session arena in the user home directory under .vamsas. Each
 * session has its own subdirectory.
 */
public class SimpleClientFactory implements IClientFactory {

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

  private File sessionArena = null;

  private String vamsasSubdirectoryName = ".vamsas";

  private SimpleSessionManager sessionManager = null;

  private static final String SESSION_LIST = "sessions.obj";

  // private String[] currentlyAvailableDessions = null;

  /**
   * default constructor - called by CreateClientFactory only.
   * 
   * Inits the sessionarena to the directory .vamsas of the user home directory.
   * 
   */
  public SimpleClientFactory() throws IOException {
    // sessionArena

    // retrieves user home directory
    String userHomeDirectory = System.getProperty("user.home");
    if (userHomeDirectory == null || userHomeDirectory.length() < 1) {
      new IOException("Unable to detect user home directory");
    }
    String sessionArenaPath = userHomeDirectory.concat(File.separator
        .concat(this.vamsasSubdirectoryName));

    this.initSessionArena(sessionArenaPath);
    // this.initFactoryObjects();
  }

  /**
   * Create a client factory that works with sessions at the given path.
   * 
   * @param path
   *          path to directory called session arena, where will be created
   *          session directories and session files.
   */
  public SimpleClientFactory(String path) throws IOException {
    this.initSessionArena(path);
  }

  /**
   * Inits sessionArena to a given path. checks if path is valid.
   * 
   * @param path
   *          path to a directory to use
   * @throws IOException
   *           if the path is incorrect
   */
  private void initSessionArena(String path) throws IOException {
    // Check path is valid and read/writeable.
    File arenaFile = new File(path);
    if (!arenaFile.exists()) {
      if (!arenaFile.mkdirs()) {
        this.sessionArena = null;
        throw (new IOException("Unable to create a directory called " + path));
      }
    }
    if (arenaFile.exists() && arenaFile.isDirectory() && arenaFile.canRead()
        && arenaFile.canWrite()) {
      this.sessionArena = arenaFile;
    } else {
      this.sessionArena = null;
      throw (new IOException("Cannot read and write to a directory called "
          + path));
    }
  }

  /**
   * @see uk.ac.vamsas.client.IClientFactory#getIClient(uk.ac.vamsas.client.ClientHandle)
   * 
   *      Creates a IClient object, using default UserHandle with system
   *      variables:"user.name" or "USERNAME")), "host.name" or "HOSTNAME"
   */
  public IClient getIClient(ClientHandle applicationHandle)
      throws NoDefaultSessionException {
    // create a new session
    // register new ClientHandle in session
    // create SimpleClient instance
    return this.getIClient(applicationHandle, (UserHandle) null);
  }

  /**
   * the URN should be something like simpleclient:FILEPATH URL encoded
   * 
   * @see uk.ac.vamsas.client.IClientFactory#getIClient(uk.ac.vamsas.client.ClientHandle,
   *      java.lang.String)
   */
  public IClient getIClient(ClientHandle applicationHandle, String sessionUrn) {
    // locate session from Urn
    // check that clientHandle is unique (with default user) - if not update the
    // clientHandle urn to make it unique.
    // wait for lock and attach to session
    // create SimpleClient instance
    log.debug("Trying to create session with URN " + sessionUrn);
    return this.getIClient(applicationHandle, null, sessionUrn);

  }

  /**
   * try to locate the sessionUrn within the simpleclient session arena
   * @param sessionUrn
   * @return
   * @throws InvalidSessionUrnException
   */
  private File convertSessionUrnToFile(String sessionUrn)
      throws InvalidSessionUrnException {
    if (sessionUrn == null) {
      log.debug("Incorrect URN: can not open session.");
      throw new InvalidSessionUrnException("SessionUrn was null");
    }

    SessionUrn urn = new SessionUrn(sessionUrn);
    SimpleSessionHandle[] sh = null;
    try {
      sh = getSessionManager().getSessionFor(urn);
    } catch (IOException e)
    {
      log.warn("Ignored IO Exception when trying to access sessionlist.",e);
    }
    File sesfile = null;
    if (sh!=null)
    {
      if (sh.length==1)
      {
        sesfile = new File(sh[0].getPhysLoc());
        sh[0] = null;
      } else {
        log.error("Raising exception for multiple session files corresponding to single URN (was : "+sessionUrn+")");
        throw new InvalidSessionUrnException("IMPLEMENTATION ERROR: Multiple session files available for URN ("+sessionUrn+")");
      }
    }
    return sesfile;

  }

  /**
   * @see uk.ac.vamsas.client.IClientFactory#getIClient(uk.ac.vamsas.client.ClientHandle,
   *      uk.ac.vamsas.client.UserHandle, java.lang.String)
   */
  public IClient getIClient(ClientHandle applicationHandle, UserHandle userId,
      String sessionUrn) {
    // locate session from Urn
    // check Uniqueness of user + ClientHandle in the session. Update
    // clientHandle urn accordingly.
    // wait for lock, attach to session
    // create client instance
    IClient client = null;

    // TODO: implement 'opening stored session' opening mechanism
    // 1. existing session document URL is vdoc://... ?
    // 2. check for sessionUrn being of this form.
    // 3. if it is - locate the file and pass to new VamsasSession

    try {
      File sessionDirectory = convertSessionUrnToFile(sessionUrn);
      // create session
      log
          .debug("found session directory "
              + sessionDirectory.getAbsolutePath());
      VamsasSession vamsasSession = new VamsasSession(sessionDirectory);

      /*
       * if (userId == null) { //create a default userHandle //with current OS
       * user and hostname userId = new
       * UserHandle(System.getProperty("user.name",
       * System.getProperty("USERNAME","Joe Doe")),
       * System.getProperty("host.name",System.getProperty("HOSTNAME",
       * "Unknown") ));// clientName, clientVersion, sessionPath); }
       * 
       * 
       * //create simple client client = new SimpleClient(userId,
       * applicationHandle, vamsasSession);
       */
      client = this.initClient(sessionDirectory, userId, applicationHandle,
          null,null);
    } catch (MalformedURLException e) {
      log.error("error while creating new IClient: incorrect session urn", e);
      client = null;
    } catch (InvalidSessionDocumentException e) {
      log
          .error("error while creating new IClient: invalid session document",
              e);
      client = null;
    } catch (InvalidSessionUrnException e) {
      log.error("error while creating new IClient: incorrect session urn", e);
      client = null;
    } catch (IOException e) {
      log.error("error while creating new IClient: file access error", e);
      client = null;
    }
    return client;
  }

  /**
   * initialise the vamsas session state and create a SimpleClient object to
   * connect to it
   * 
   * @param sessdir
   *          newly created or existing session directory
   * @param userId
   * @param clientHandle
   * @param vamsasDocument
   *          null or a document to pass to SimpleCLient to write into the
   *          sessdir
   * @param preferredName - name to use instead of vamsasDocument path when creating new session URN 
   * @return the client
   * @throws IOException
   *           if there are problems in session or client creation or if the
   *           session already has a vamsasDocument
   * @throws InvalidSessionUrnException
   *           for a malformed sessdir
   */
  private IClient initClient(File sessdir, UserHandle userId,
      ClientHandle clientHandle, File vamsasDocument, String preferredName) throws IOException,
      InvalidSessionUrnException, InvalidSessionDocumentException {
    IClient client = null;
    // create session
    VamsasSession vamsasSession = null;
    if (vamsasDocument == null) {
      vamsasSession = new VamsasSession(sessdir);
    } else {
      vamsasSession = new VamsasSession(sessdir, vamsasDocument,preferredName);
    }
    getSessionManager().addSession(vamsasSession.getSessionUrn());
    if (userId == null) {
      // create a default userHandle
      // userId = new UserHandle(System.getProperty("user.name",
      // System.getProperty("USERNAME","Joe Doe")),
      // System.getProperty("host.name",System.getProperty("HOSTNAME",
      // "Unknown") ));// clientName, clientVersion, sessionPath);
      userId = new UserHandle(null, null);
    }

    // FullName and organisation should not be null (otherwise UserHandle equals
    // method raises an java.lang.NullPointerException )
    // use current OS user and hostname, if null
    if (userId.getFullName() == null) {
      userId.setFullName(System.getProperty("user.name", System.getProperty(
          "USERNAME", "Joe Doe")));
    }

    if (userId.getOrganization() == null) {
      userId.setOrganization(System.getProperty("host.name", System
          .getProperty("HOSTNAME", "Unknown")));
    }

    if (clientHandle == null)
      clientHandle = new ClientHandle("SimpleVamsasClientApp", "0.1");
    else {
      if (clientHandle.getClientName() == null) {
        clientHandle.setClientName("SimpleVamsasClientApp");
      }
      if (clientHandle.getVersion() == null) {
        clientHandle.setVersion("0.1");
      }
    }

    // create simple client
    client = new SimpleClient(userId, clientHandle, vamsasSession);
    vamsasSession.addClient((SimpleClient) client);
    vamsasSession.setSessionManager(this.getSessionManager());
    return client;
  }

  /**
   * @see uk.ac.vamsas.client.IClientFactory#getIClient(uk.ac.vamsas.client.ClientHandle,
   *      uk.ac.vamsas.client.UserHandle)
   */
  public IClient getIClient(ClientHandle applicationHandle, UserHandle userId)
      throws NoDefaultSessionException {
    // create new session
    // register SimpleClient and UserHandles in session
    // create client instance
    IClient client = null;
    if (this.sessionArena == null)
      throw new Error(
          "Improperly initialised SimpleClientFactory object - null sessionArena.");

    ClientHandle clientHandle = applicationHandle;
    // create default clientHandle with "SimpleVamsasClientApp","0.1",
    if (clientHandle == null)
      clientHandle = new ClientHandle("SimpleVamsasClientApp", "0.1");
    else {
      if (clientHandle.getClientName() == null) {
        clientHandle.setClientName("SimpleVamsasClientApp");
      }

      if (clientHandle.getVersion() == null) {
        clientHandle.setVersion("0.1");
      }
    }
    // check if any available session(s)
    String[] availableSessions = this.getCurrentSessions();
    if (availableSessions != null) {// there are available sessions
      if (availableSessions.length > 1) {// more than one session if
                                         // available... can not choose

        // represents list of session as String
        StringBuffer sessionURNs = new StringBuffer("");
        for (int i = 0; i < availableSessions.length; i++) {
          sessionURNs.append(availableSessions[i] + " ");
        }
        throw new NoDefaultSessionException(
            "Several sessions available, please pick one: " + sessionURNs);
      }

      // check if only one session available. if yes, open it
      if (availableSessions.length == 1) {
        // only one session available, open it.
        return this.getIClient(clientHandle, availableSessions[0]);
      } else {
        log.debug("No active session found");
      }
    }
    // no session available - create a new one
    try {
      client = clientInNewSession(userId, clientHandle, null,null);
    } catch (Exception e) {
      throw new Error(
          "IMPLEMENTATION ERROR: unexpected exception when creating a new session to connect to.",
          e);
    }
    return client;
  }

  /**
   * create a new session directory and possibly import an existing document
   * into it
   * 
   * @param userId
   * @param clientHandle
   * @param vamsasDocument
   *          null or a document file to copy into the new session
   *          @param preferredName - name to be used as base for the new session URN
   * @return null or a valid IClient instance
   */
  private IClient clientInNewSession(UserHandle userId,
      ClientHandle clientHandle, File vamsasDocument, String preferredName)
      throws InvalidSessionDocumentException, InvalidSessionUrnException {

    IClient client = null;
    try {
      // try and make a friendly session name
      String sesspref = "";
      if (vamsasDocument != null) {
        if (preferredName!=null)
        {
          sesspref = preferredName.replaceAll(
              "([^-A-Za-z0-9]|\\.vdj)", "");
        } else {
          sesspref = vamsasDocument.getName().replaceAll(
              "([^-A-Za-z0-9]|\\.vdj)", "");
        }
      }
      sesspref += (new java.util.Date()).toString().replaceAll("[^-A-Za-z0-9]",
          "_");
      // create sessionDirectory
      File sessdir = new File(sessionArena, sesspref + ".simpleclient");
      if (sessdir.exists()) {
        // make a unique session name
        sessdir = File.createTempFile(sesspref, ".simpleclient", sessionArena);
      } else {
        if (!sessdir.createNewFile()) {
          throw new Error(
              "VAMSAS Implementation error : sesspref friendly session name is invalid on this platform - please tell the authors!");
        }
      }
      log.debug("Creating new session  directory");
      if (!(sessdir.delete() && sessdir.mkdir()))
        throw new IOException("Could not make session directory " + sessdir);
      client = initClient(sessdir, userId, clientHandle, vamsasDocument, preferredName);
    } catch (IOException e) {
      log.error("error while creating new IClient", e);
    } catch (InvalidSessionUrnException e) {
      log.error(
          "Unable to create new IClient. The new session urn is malformed.", e);
    }

    return client;
  }

  /**
   * @see uk.ac.vamsas.client.IClientFactory#getCurrentSessions()
   */
  public String[] getCurrentSessions() {
    String[] sessions = null;
    try {
      sessions = this.getSessionManager().getCurrentSessions();
    } catch (IOException e) {
      log.error("Unable to get available sessions", e);
      sessions = null;
    }
    return sessions;
  }

  /**
   * @return the sessionFile
   */
  private SimpleSessionManager getSessionManager() throws IOException {
    if (this.sessionManager == null) {
      this.sessionManager = new SimpleSessionManager(new File(
          this.sessionArena, SESSION_LIST));
    }
    return this.sessionManager;
  }

  public IClient getNewSessionIClient(ClientHandle applicationHandle) {
    try {
      return clientInNewSession(null, applicationHandle, null,null);
    } catch (Exception e) {
      log.error("Failed to create new session for app with default user.", e);
    }
    return null;
  }

  public IClient getNewSessionIClient(ClientHandle applicationHandle,
      UserHandle userId) {
    try {
      return clientInNewSession(userId, applicationHandle, null,null);
    } catch (Exception e) {
      log.error("Failed to create new session for app and user.", e);
    }
    return null;
  }

  private void checkImportedDocument(File vamsasDocument)
      throws InvalidSessionDocumentException {
    if (!vamsasDocument.exists()) {
      throw new InvalidSessionDocumentException("File " + vamsasDocument
          + " does not exist");
    }
    if (!vamsasDocument.canRead()) {
      throw new InvalidSessionDocumentException("File " + vamsasDocument
          + " does not exist");
    }
  }

  public IClient openAsNewSessionIClient(ClientHandle applicationHandle,
      File vamsasDocument) throws InvalidSessionDocumentException {
    checkImportedDocument(vamsasDocument);
    try {
      return clientInNewSession(null, applicationHandle, vamsasDocument,null);
    } catch (InvalidSessionUrnException e) {
      throw new InvalidSessionDocumentException("Unexpected exception", e);
    }
  }

  public IClient openAsNewSessionIClient(ClientHandle applicationHandle,
      UserHandle userId, File vamsasDocument)
      throws InvalidSessionDocumentException {
    checkImportedDocument(vamsasDocument);
    try {
      return clientInNewSession(userId, applicationHandle, vamsasDocument,null);
    } catch (InvalidSessionUrnException e) {
      throw new InvalidSessionDocumentException("Unexpected exception", e);
    }
  }

  public IClient openAsNewSessionIClient(ClientHandle applicationHandle,
      File vamsasDocument, String sessionName)
      throws InvalidSessionDocumentException {
    checkImportedDocument(vamsasDocument);
    try {
      return clientInNewSession(null, applicationHandle, vamsasDocument, sessionName);
    } catch (InvalidSessionUrnException e) {
      throw new InvalidSessionDocumentException("Unexpected exception", e);
    }
  }

  public IClient openAsNewSessionIClient(ClientHandle applicationHandle,
      UserHandle userId, File vamsasDocument, String sessionName)
      throws InvalidSessionDocumentException {
    checkImportedDocument(vamsasDocument);
    try {
      return clientInNewSession(userId, applicationHandle, vamsasDocument, sessionName);
    } catch (InvalidSessionUrnException e) {
      throw new InvalidSessionDocumentException("Unexpected exception", e);
    }
  }

}
