/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 */

package org.opends.quicksetup;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import org.opends.admin.ads.ServerDescriptor;
import org.opends.admin.ads.SuffixDescriptor;
import org.opends.admin.ads.util.ConnectionUtils;
import org.opends.quicksetup.installer.AuthenticationData;
import org.opends.quicksetup.installer.DataReplicationOptions;
import org.opends.quicksetup.installer.NewSuffixOptions;
import org.opends.quicksetup.installer.SuffixesToReplicateOptions;
import org.opends.quicksetup.util.Utils;

/**
 * This class is used to provide a data model for the different parameters
 * that the user can provide in the installation wizard.
 *
 * @see org.opends.quicksetup.DataOptions
 *
 */
public class UserData
{
  private String serverLocation;

  private String hostName;

  private int serverPort;

  private int adminConnectorPort;

  private String directoryManagerDn;

  private String directoryManagerPwd;

  private String globalAdministratorUID;

  private String globalAdministratorPassword;

  private SecurityOptions securityOptions;
  private int serverJMXPort = -1;

  private boolean startServer;

  private boolean stopServer;

  private boolean enableWindowsService;

  private NewSuffixOptions newSuffixOptions;

  private DataReplicationOptions replicationOptions;

  private boolean createAdministrator;

  private SuffixesToReplicateOptions suffixesToReplicateOptions;

  private Map<ServerDescriptor, AuthenticationData> remoteWithNoReplicationPort;

  private boolean quiet;

  private boolean verbose;

  private boolean interactive;

  private boolean forceOnError;

  private String configurationFile;

  private String configurationClass;

  private Map<String, JavaArguments> hmJavaArguments;
  private Map<String, JavaArguments> hmDefaultJavaArguments;

  private static String defaultHostName;

  private int connectTimeout = ConnectionUtils.getDefaultLDAPTimeout();

  /**
   * The script name to be used to get and set the java arguments for the
   * server runtime.
   */
  public static String SERVER_SCRIPT_NAME = "start-ds";
  /**
   * The script name to be used to get and set the java arguments for the
   * (off-line) import.
   */
  public static String IMPORT_SCRIPT_NAME = "import-ldif.offline";

  /**
   * Creates a user data object with default values.
   */
  public UserData() {
    interactive = true;
    startServer = true;
    enableWindowsService = false;
    forceOnError = true;
    verbose = false;

    LinkedList<String> baseDn = new LinkedList<String>();
    baseDn.add("dc=example,dc=com");
    NewSuffixOptions defaultNewSuffixOptions = NewSuffixOptions.createBaseEntry(
        baseDn);
    setNewSuffixOptions(defaultNewSuffixOptions);

    // See what we can propose as port
    int defaultLdapPort = getDefaultPort();
    if (defaultLdapPort != -1)
    {
      setServerPort(defaultLdapPort);
    }

//  See what we can propose as port
    int defaultAdminPort = getDefaultAdminConnectorPort();
    if (defaultAdminPort != -1)
    {
      setAdminConnectorPort(defaultAdminPort);
    }

    setHostName(getDefaultHostName());

    setDirectoryManagerDn(Constants.DIRECTORY_MANAGER_DN);

    setNewSuffixOptions(defaultNewSuffixOptions);
    DataReplicationOptions repl = DataReplicationOptions.createStandalone();
    setReplicationOptions(repl);
    setGlobalAdministratorUID(Constants.GLOBAL_ADMIN_UID);

    SuffixesToReplicateOptions suffixes =
      new SuffixesToReplicateOptions(
          SuffixesToReplicateOptions.Type.REPLICATE_WITH_EXISTING_SUFFIXES,
          new HashSet<SuffixDescriptor>(),
          new HashSet<SuffixDescriptor>());
    setSuffixesToReplicateOptions(suffixes);
    SecurityOptions sec = SecurityOptions.createNoCertificateOptions();
    sec.setSslPort(getDefaultSslPort(defaultLdapPort));
    setSecurityOptions(sec);

    remoteWithNoReplicationPort =
      new HashMap<ServerDescriptor, AuthenticationData>();

    createDefaultJavaArguments();
  }

  /**
   * Sets the location of the server (installation path).
   * @param serverLocation the new server location (installation path).
   */
  public void setServerLocation(String serverLocation)
  {
    this.serverLocation = serverLocation;
  }

  /**
   * Returns the location of the server (installation path).
   * @return the location of the server (installation path).
   */
  public String getServerLocation()
  {
    return serverLocation;
  }

  /**
   * Sets the host name.
   * @param hostName the server host name.
   */
  public void setHostName(String hostName)
  {
    this.hostName = hostName;
  }

  /**
   * Returns the server host name.
   * @return the server host name.
   */
  public String getHostName()
  {
    return hostName;
  }

  /**
   * Sets the server LDAP port.
   * @param serverPort the new server LDAP port.
   */
  public void setServerPort(int serverPort)
  {
    this.serverPort = serverPort;
  }

  /**
   * Returns the server LDAP port.
   * @return the server LDAP port.
   */
  public int getServerPort()
  {
    return serverPort;
  }

  /**
   * Sets the admin connector port.
   * @param adminConnectorPort the new admin connector port.
   */
  public void setAdminConnectorPort(int adminConnectorPort)
  {
    this.adminConnectorPort = adminConnectorPort;
  }

  /**
   * Returns the admin connector port.
   * @return the admin connector port.
   */
  public int getAdminConnectorPort()
  {
    return adminConnectorPort;
  }

  /**
   * Sets the server JMX port.
   * @param serverJMXPort the new server JMX port.
   */
  public void setServerJMXPort(int serverJMXPort)
  {
    this.serverJMXPort = serverJMXPort;
  }

  /**
   * Returns the server JMX port.
   * @return the server JMX port.
   */
  public int getServerJMXPort()
  {
    return serverJMXPort;
  }

  /**
   * Returns the Directory Manager DN.
   * @return the Directory Manager DN.
   */
  public String getDirectoryManagerDn()
  {
    return directoryManagerDn;
  }

  /**
   * Sets the new Directory Manager DN.
   * @param directoryManagerDn the new Directory Manager DN.
   */
  public void setDirectoryManagerDn(String directoryManagerDn)
  {
    this.directoryManagerDn = directoryManagerDn;
  }

  /**
   * Returns the Directory Manager password.
   * @return the Directory Manager password.
   */
  public String getDirectoryManagerPwd()
  {
    return directoryManagerPwd;
  }

  /**
   * Sets the new Directory Manager password.
   * @param directoryManagerPwd the new Directory Manager password.
   */
  public void setDirectoryManagerPwd(String directoryManagerPwd)
  {
    this.directoryManagerPwd = directoryManagerPwd;
  }

  /**
   * Returns <CODE>true</CODE> if the server must be started once the
   * installation is finished, <CODE>false</CODE> if not.
   * @return <CODE>true</CODE> if the server must be started once the
   * installation is finished, <CODE>false</CODE> if not.
   */
  public boolean getStartServer()
  {
    return startServer;
  }

  /**
   * Sets whether we want to start the server once the installation is finished
   * or not.
   * @param startServer the boolean indicating whether to start the server or
   * not.
   */
  public void setStartServer(boolean startServer)
  {
    this.startServer = startServer;
  }

  /**
   * Sets whether to stop the server or not.
   * @param stopServer stop the server or not.
   */
  public void setStopServer(boolean stopServer)
  {
    this.stopServer = stopServer;
  }

  /**
   * Returns whether the user wants to stop the server or not.
   * @return <CODE>true</CODE> if the user wants to stop the server and <CODE>\
   * false</CODE> otherwise.
   */
  public boolean getStopServer()
  {
    return stopServer;
  }

  /**
   * Returns <CODE>true</CODE> if the windows service must be enabled during
   * installation, <CODE>false</CODE> if not.
   * @return <CODE>true</CODE> if the windows service must be enabled during
   * installation, <CODE>false</CODE> if not.
   */
  public boolean getEnableWindowsService()
  {
    return enableWindowsService;
  }

  /**
   * Sets whether we want to enable windows service during installation or not.
   * @param enableWindowsService the boolean indicating whether we want to
   * enable windows service during installation or not.
   */
  public void setEnableWindowsService(boolean enableWindowsService)
  {
    this.enableWindowsService = enableWindowsService;
  }

  /**
   * Returns the NewSuffixOptions object representing the data in the New Suffix
   * Data Options panel.
   * @return the NewSuffixOptions object representing the data in the New Suffix
   * Data Options panel.
   */
  public NewSuffixOptions getNewSuffixOptions()
  {
    return newSuffixOptions;
  }

  /**
   * Sets the NewSuffixOptions object representing the data in the New Suffix
   * Data Options panel.
   * @param newSuffixOptions the NewSuffixOptions object representing the data
   * in the New Suffix Data Options panel.
   */
  public void setNewSuffixOptions(NewSuffixOptions newSuffixOptions)
  {
    this.newSuffixOptions = newSuffixOptions;
  }

  /**
   * Returns the DataReplicationOptions object representing the data in the
   * Data Replication panel.
   * @return the DataReplicationOptions object representing the data in the
   * Data Replication panel.
   */
  public DataReplicationOptions getReplicationOptions()
  {
    return replicationOptions;
  }

  /**
   * Sets the DataReplicationOptions object representing the data in the
   * Data Replication panel.
   * @param replicationOptions the DataReplicationOptions object
   * representing the data in the Data Replication panel.
   */
  public void setReplicationOptions(
      DataReplicationOptions replicationOptions)
  {
    this.replicationOptions = replicationOptions;
  }

  /**
   * Returns whether must create a global administrator or not.
   * @return <CODE>true</CODE> if we must create a global administrator and
   * <CODE>false</CODE> otherwise.
   */
  public boolean mustCreateAdministrator()
  {
    return createAdministrator;
  }

  /**
   * Sets whether must create a global administrator or not.
   * @param createAdministrator whether we must create a global administrator or
   * not.
   */
  public void createAdministrator(boolean createAdministrator)
  {
    this.createAdministrator = createAdministrator;
  }

  /**
   * Returns the UID of the global administrator.
   * @return the UID of the global administrator.
   */
  public String getGlobalAdministratorUID()
  {
    return globalAdministratorUID;
  }

  /**
   * Sets the UID of the global administrator.
   * @param globalAdministratorUID the UID of the global administrator.
   */
  public void setGlobalAdministratorUID(String globalAdministratorUID)
  {
    this.globalAdministratorUID = globalAdministratorUID;
  }

  /**
   * Returns the password of the global administrator.
   * @return the password of the global administrator.
   */
  public String getGlobalAdministratorPassword()
  {
    return globalAdministratorPassword;
  }

  /**
   * Sets the password of the global administrator.
   * @param globalAdministratorPwd the password of the global administrator.
   */
  public void setGlobalAdministratorPassword(String globalAdministratorPwd)
  {
    this.globalAdministratorPassword = globalAdministratorPwd;
  }

  /**
   * Sets the suffixes to replicate options.
   * @param suffixesToReplicateOptions the suffixes to replicate options
   * object.
   */
  public void setSuffixesToReplicateOptions(
      SuffixesToReplicateOptions suffixesToReplicateOptions)
  {
    this.suffixesToReplicateOptions = suffixesToReplicateOptions;
  }

  /**
   * Returns the suffixes to replicate options.
   * @return the suffixes to replicate options.
   */
  public SuffixesToReplicateOptions getSuffixesToReplicateOptions()
  {
    return suffixesToReplicateOptions;
  }

  /**
   * Returns the SecurityOptions representing the SSL/StartTLS configuration
   * chosen by the user.
   * @return the SecurityOptions representing the SSL/StartTLS configuration
   * chosen by the user.
   */
  public SecurityOptions getSecurityOptions()
  {
    return securityOptions;
  }

  /**
   * Sets the SecurityOptions representing the SSL/StartTLS configuration
   * chosen by the user.
   * @param securityOptions the SecurityOptions representing the SSL/StartTLS
   * configuration chosen by the user.
   */
  public void setSecurityOptions(SecurityOptions securityOptions)
  {
    this.securityOptions = securityOptions;
  }

  /**
   * Sets whether or not this session should print messages to the
   * console if in CLI mode.
   * @param quiet where true indicates this sesssion should be quiet
   */
  public void setQuiet(boolean quiet) {
    this.quiet = quiet;
  }

  /**
   * Indicates whether or not the user has requested quiet mode.
   * <p>
   * Quiet mode in the CLI means that nothing is written to output including
   * prompts for information and whether or not to continue an operation
   * experiencing errors.
   *
   * @return boolean where true indicates this session should be quiet.
   */
  public boolean isQuiet() {
    return this.quiet;
  }

  /**
   * Sets whether or not this session should be verbose.
   * @param verbose where true indicates this sesssion should be verbose
   */
  public void setVerbose(boolean verbose) {
    this.verbose = verbose;
  }

  /**
   * Indicates whether or not the user has requested verbose mode.
   *
   * @return boolean where true indicates this session should be verbose.
   */
  public boolean isVerbose() {
    return this.verbose;
  }

  /**
   * Sets whether or not we must continue when there is a non critical error.
   * @param forceOnError where true indicates to continue uninstall if there is
   * a non critical error.
   */
  public void setForceOnError(boolean forceOnError) {
    this.forceOnError = forceOnError;
  }

  /**
   * Indicates whether or not the user has requested to continue when a non
   * critical error occurs.
   *
   * @return boolean where true indicates to continue uninstall if there is a
   * non critical error.
   */
  public boolean isForceOnError() {
    return this.forceOnError;
  }

  /**
   * Sets whether or not this session should solicite feedback from the user
   * for missing information.
   * @param interactive boolean where true indicates this application
   *        should NOT solicite feedback from the user
   */
  public void setInteractive(boolean interactive) {
    this.interactive = interactive;
  }

  /**
   * Indicates whether or not the user has requested interactive mode.
   * <p>
   * Interactive mode in the CLI means that the CLI will prompt the user
   * for more information if it is required.  Interactivity does NOT
   * affect prompts to the user regarding actions like continuing an operation
   * that is experiencing errors.
   *
   * @return boolean where true indicates this session should be interactive
   */
  public boolean isInteractive() {
    return this.interactive;
  }

  /**
   * Provides the port that will be proposed to the user in the second page of
   * the installation wizard. It will check whether we can use ports of type
   * X389 and if not it will return -1.
   *
   * @return the free port of type x389 if it is available and we can use and -1
   * if not.
   */
  static public int getDefaultPort()
  {
    int defaultPort = -1;

    for (int i=0;i<10000 && (defaultPort == -1);i+=1000)
    {
      int port = i + 389;
      if (Utils.canUseAsPort(port))
      {
        defaultPort = port;
      }
    }
    return defaultPort;
  }

  /**
   * Provides the administration port that will be proposed to the user in the
   * second page of the installation wizard. It will check whether we can use
   * ports of type X444 and if not it will return -1.
   *
   * @return the free port of type x444 if it is available and we can use and -1
   * if not.
   */
  static public int getDefaultAdminConnectorPort()
  {
    int defaultPort = -1;

    for (int i=0;i<10000 && (defaultPort == -1);i+=1000)
    {
      int port = i + 4444;
      if (Utils.canUseAsPort(port))
      {
        defaultPort = port;
      }
    }
    return defaultPort;
  }

  /**
   * Provides the port that will be proposed to the user in the security dialog
   *  of the installation wizard. It will check whether we can use ports of type
   * X636 and if not it will return -1.
   * @param defaultLdapPort the default port used for LDAP.
   *
   * @return the free port of type X636 if it is available and we can use and -1
   * if not.
   */
  public static int getDefaultSslPort(int defaultLdapPort)
  {
    int defaultPort = -1;

    int port = defaultLdapPort - 389 + 636;
    // Try first with the correlated port of the default LDAP port.
    if (Utils.canUseAsPort(port))
    {
      defaultPort = port;
    }

    for (int i=0;i<10000 && (defaultPort == -1);i+=1000)
    {
      port = i + 636;
      if (Utils.canUseAsPort(port))
      {
        defaultPort = port;
      }
    }
    return defaultPort;
  }

  /**
   * Provides the port that will be used by default for JMX.
   *
   * @param forbiddenPorts an array of ports that we cannot use.
   * @return the port X689 if it is available and we can use and -1 if not.
   */
  static public int getDefaultJMXPort(int[] forbiddenPorts)
  {
    int defaultJMXPort = -1;

    for (int i=0;i<65000 && (defaultJMXPort == -1);i+=1000)
    {
      int port = i + org.opends.server.util.SetupUtils.getDefaultJMXPort();
      boolean isForbidden = false;
      if (forbiddenPorts != null)
      {
        for (int j=0; j<forbiddenPorts.length && !isForbidden; j++)
        {
          isForbidden = forbiddenPorts[j] == port;
        }
      }
      if (!isForbidden && Utils.canUseAsPort(port))
      {
        defaultJMXPort = port;
      }
    }
    return defaultJMXPort;
  }

  /**
   * Provides the default host name that will be proposed to the user for the
   * local host.
   * @return the default host name that will be proposed to the user for the
   * local host.
   */
  public static String getDefaultHostName()
  {
    if (defaultHostName == null)
    {
      try
      {
        defaultHostName = java.net.InetAddress.getLocalHost().getHostName();
      }
      catch (Throwable t)
      {
        defaultHostName = "localhost";
      }
    }
    return defaultHostName;
  }

  /**
   * Returns a Map containing as key a ServerDescriptor and as value an Integer
   * corresponding to the Replication Port chosen by the user.
   *
   * Only the servers that have no replication port appear on this map.
   * @return a Map containing as key a ServerDescriptor and as value an
   * AuthenticationData corresponding to the Replication Port chosen by the
   * user.
   */
  public Map<ServerDescriptor, AuthenticationData>
  getRemoteWithNoReplicationPort()
  {
    HashMap<ServerDescriptor, AuthenticationData> copy =
      new HashMap<ServerDescriptor, AuthenticationData>();
    copy.putAll(remoteWithNoReplicationPort);
    return copy;
  }

  /**
   * Sets a the Replication Ports chosen by the user in the remote servers.
   * @param remoteWithNoReplicationPort the Map containing as key a
   * ServerDescriptor and as value an AuthenticationData corresponding to the
   * Replication Port chosen by the user.
   */
  public void setRemoteWithNoReplicationPort(
      Map<ServerDescriptor, AuthenticationData> remoteWithNoReplicationPort)
  {
    this.remoteWithNoReplicationPort.clear();
    this.remoteWithNoReplicationPort.putAll(remoteWithNoReplicationPort);
  }

  /**
   * Returns the configuration file to be used.
   * @return the configuration file to be used.
   */
  public String getConfigurationFile()
  {
    return configurationFile;
  }

  /**
   * Sets the configuration file to be used.
   * @param configurationFile the configuration file to be used.
   */
  public void setConfigurationFile(String configurationFile)
  {
    this.configurationFile = configurationFile;
  }

  /**
   * Returns the configuration class to be used.
   * @return the configuration class to be used.
   */
  public String getConfigurationClassName()
  {
    return configurationClass;
  }

  /**
   * Sets the configuration class to be used.
   * @param configurationClass the configuration class to be used.
   */
  public void setConfigurationClassName(String configurationClass)
  {
    this.configurationClass = configurationClass;
  }

  /**
   * Returns the different script names for which there are java arguments.
   * @return the different script names for which there are java arguments.
   */
  public Set<String> getScriptNamesForJavaArguments()
  {
    return hmJavaArguments.keySet();
  }

  /**
   * Returns the java arguments associated with a script name.  Returns
   * <CODE>null</CODE> if no java arguments are defined.
   * @param scriptName the script name.
   * @return the java arguments associated with a script name.
   */
  public JavaArguments getJavaArguments(String scriptName)
  {
    return hmJavaArguments.get(scriptName);
  }

  /**
   * Returns the default java arguments associated with a script name.  Returns
   * <CODE>null</CODE> if no java arguments are defined.
   * @param scriptName the script name.
   * @return the default java arguments associated with a script name.
   */
  public JavaArguments getDefaultJavaArguments(String scriptName)
  {
    return hmDefaultJavaArguments.get(scriptName);
  }

  /**
   * Sets the java arguments associated with a script name.
   * @param scriptName the script name.
   * @param args the java arguments associated with a script name.
   */
  public void setJavaArguments(String scriptName, JavaArguments args)
  {
    hmJavaArguments.put(scriptName, args);
  }



  private void createDefaultJavaArguments()
  {
    hmJavaArguments = new HashMap<String, JavaArguments>();
    int maxMemoryMb = 256;
    int minMemoryMb = 128;
    final int maxMemoryBytes = maxMemoryMb * 1024 * 1024;
    // If the current max memory is bigger than the max heap we want to set,
    // assume that the JVM ergonomics are going to be able to allocate enough
    // memory.
    long currentMaxMemoryBytes = Runtime.getRuntime().maxMemory();
    if (currentMaxMemoryBytes > maxMemoryBytes)
    {
      maxMemoryMb = -1;
      minMemoryMb = -1;
    }
    for (String clientScript : getClientScripts())
    {
      JavaArguments javaArgument = new JavaArguments();
      javaArgument.setInitialMemory(8);
      javaArgument.setAdditionalArguments(new String[] {"-client"});
      hmJavaArguments.put(clientScript, javaArgument);
    }
    for (String serverScript : getServerScripts())
    {
      JavaArguments javaArgument = new JavaArguments();
      javaArgument.setInitialMemory(minMemoryMb);
      javaArgument.setMaxMemory(maxMemoryMb);
      javaArgument.setAdditionalArguments(new String[] {"-server"});
      hmJavaArguments.put(serverScript, javaArgument);
    }

    JavaArguments controlPanelJavaArgument = new JavaArguments();
    controlPanelJavaArgument.setInitialMemory(64);
    controlPanelJavaArgument.setMaxMemory(128);
    controlPanelJavaArgument.setAdditionalArguments(new String[] {"-client"});
    hmJavaArguments.put("control-panel", controlPanelJavaArgument);

    hmDefaultJavaArguments =
      new HashMap<String, JavaArguments>(hmJavaArguments);
  }

  private String[] getClientScripts()
  {
    return new String[] {
      "backup.online", "base64", "create-rc-script", "dsconfig",
      "dsreplication", "dsframework", "export-ldif.online",
      "import-ldif.online", "ldapcompare", "ldapdelete",
      "ldapmodify", "ldappasswordmodify", "ldapsearch", "list-backends",
      "manage-account", "manage-tasks", "restore.online", "stop-ds",
      "status", "uninstall", "setup"
    };
  }

  private String[] getServerScripts()
  {
    return new String[]
    {
        "backup.offline", "encode-password", "export-ldif.offline",
        IMPORT_SCRIPT_NAME, "ldif-diff", "ldifmodify", "ldifsearch",
        "make-ldif", "rebuild-index", "restore.offline", SERVER_SCRIPT_NAME,
        "upgrade", "verify-index", "dbtest"
    };
  }

  /**
   * Sets the timeout to be used to establish a connection.
   * @param connectTimeout the timeout to be used to establish a connection.
   */
  public void setConnectTimeout(int connectTimeout)
  {
    this.connectTimeout = connectTimeout;
  }

  /**
   * Returns the timeout to be used to connect in milliseconds.
   * @return the timeout to be used to connect in milliseconds.  Returns
   * {@code 0} if there is no timeout.
   */
  public int getConnectTimeout()
  {
    return connectTimeout;
  }
}
