/*
 * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program 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 General Public License, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.admin;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;

import com.mysql.cj.Constants;
import com.mysql.cj.util.StringUtils;

/**
 * Controls a MySQL server using Java RunTime methods
 */
public class ServerController {

    /**
     * Where is the server installed?
     */
    public static final String BASEDIR_KEY = "basedir";

    /**
     * Where are the databases installed?
     */
    public static final String DATADIR_KEY = "datadir";

    /**
     * Where is the config file located?
     */

    public static final String DEFAULTS_FILE_KEY = "defaults-file";

    /**
     * What is the name of the executable to run?
     */

    public static final String EXECUTABLE_NAME_KEY = "executable";

    /**
     * What is the path to the mysql server executable (if not standard?)
     */

    public static final String EXECUTABLE_PATH_KEY = "executablePath";

    /**
     * The default executable to run
     */

    /**
     * The process representing the MySQL server
     */
    private Process serverProcess = null;

    /**
     * The list of properties for this server
     */
    private Properties serverProps = null;

    /**
     * The system properties
     */
    //private Properties systemProps = null;

    /**
     * Creates a ServerController with the directory for the MySQL server.
     * 
     * The 'datadir' is set to the same directory.
     * 
     * @param baseDir
     *            the base directory for the MySQL server.
     */
    public ServerController(String baseDir) {
        setBaseDir(baseDir);
    }

    /**
     * Creates a server controller for the MySQL server with the given basedir
     * and datadir.
     * 
     * @param basedir
     *            the basedir to use when starting MySQL.
     * @param datadir
     *            the datadir to use when starting MySQL.
     */
    public ServerController(String basedir, String datadir) {
    }

    /**
     * Sets the basedir to use when starting MySQL.
     * 
     * @param baseDir
     *            the basedir to use when starting MySQL.
     */
    public void setBaseDir(String baseDir) {
        getServerProps().setProperty(BASEDIR_KEY, baseDir);
    }

    /**
     * Sets the data to use when starting MySQL.
     * 
     * @param dataDir
     *            the basedir to use when starting MySQL.
     */
    public void setDataDir(String dataDir) {
        getServerProps().setProperty(DATADIR_KEY, dataDir);
    }

    /**
     * Starts the server, returning a java.lang.Process instance that represents
     * the mysql server.
     * 
     * @return Process a java.lang.Process instance representing the mysql
     *         server process.
     * @throws IOException
     *             if an error occurs while starting the mysql server.
     */
    public Process start() throws IOException {
        if (this.serverProcess != null) {
            throw new IllegalArgumentException("Server already started");
        }
        this.serverProcess = Runtime.getRuntime().exec(getCommandLine());

        return this.serverProcess;
    }

    /**
     * Stops the server (if started)
     * 
     * @param forceIfNecessary
     *            use forceStop if mysqladmin doesn't shut the server down
     * 
     * @throws IOException
     *             if an error occurs while stopping the server
     */
    public void stop(boolean forceIfNecessary) throws IOException {
        if (this.serverProcess != null) {

            String basedir = getServerProps().getProperty(BASEDIR_KEY);

            StringBuilder pathBuf = new StringBuilder(basedir);

            if (!basedir.endsWith(File.separator)) {
                pathBuf.append(File.separator);
            }

            //String defaultsFilePath = getServerProps().getProperty(DEFAULTS_FILE_KEY);

            pathBuf.append("bin");
            pathBuf.append(File.separator);
            pathBuf.append("mysqladmin shutdown");

            System.out.println(pathBuf.toString());

            Process mysqladmin = Runtime.getRuntime().exec(pathBuf.toString());

            int exitStatus = -1;

            try {
                exitStatus = mysqladmin.waitFor();
            } catch (InterruptedException ie) {
                // ignore
            }

            //
            // Terminate the process if mysqladmin couldn't do it, and the user requested a force stop.
            //
            if (exitStatus != 0 && forceIfNecessary) {
                forceStop();
            }
        }
    }

    /**
     * Forcefully terminates the server process (if started).
     */
    public void forceStop() {
        if (this.serverProcess != null) {
            this.serverProcess.destroy();
            this.serverProcess = null;
        }
    }

    /**
     * Returns the list of properties that will be used to start/control the
     * server.
     * 
     * @return Properties the list of properties.
     */
    public synchronized Properties getServerProps() {
        if (this.serverProps == null) {
            this.serverProps = new Properties();
        }

        return this.serverProps;
    }

    /**
     * Returns the full commandline used to start the mysql server, including
     * and arguments to be passed to the server process.
     * 
     * @return String the commandline used to start the mysql server.
     */
    private String getCommandLine() {
        StringBuilder commandLine = new StringBuilder(getFullExecutablePath());
        commandLine.append(buildOptionalCommandLine());

        return commandLine.toString();
    }

    /**
     * Returns the fully-qualifed path to the 'mysqld' executable
     * 
     * @return String the path to the server executable.
     */
    private String getFullExecutablePath() {
        StringBuilder pathBuf = new StringBuilder();

        String optionalExecutablePath = getServerProps().getProperty(EXECUTABLE_PATH_KEY);

        if (optionalExecutablePath == null) {
            // build the path using the defaults
            String basedir = getServerProps().getProperty(BASEDIR_KEY);
            pathBuf.append(basedir);

            if (!basedir.endsWith(File.separator)) {
                pathBuf.append(File.separatorChar);
            }

            if (runningOnWindows()) {
                pathBuf.append("bin");
            } else {
                pathBuf.append("libexec");
            }

            pathBuf.append(File.separatorChar);
        } else {
            pathBuf.append(optionalExecutablePath);

            if (!optionalExecutablePath.endsWith(File.separator)) {
                pathBuf.append(File.separatorChar);
            }
        }

        String executableName = getServerProps().getProperty(EXECUTABLE_NAME_KEY, "mysqld");

        pathBuf.append(executableName);

        return pathBuf.toString();
    }

    /**
     * Builds the list of command-line arguments that will be passed to the
     * mysql server to be started.
     * 
     * @return String the list of command-line arguments.
     */
    private String buildOptionalCommandLine() {
        StringBuilder commandLineBuf = new StringBuilder();

        if (this.serverProps != null) {

            for (Iterator<Object> iter = this.serverProps.keySet().iterator(); iter.hasNext();) {
                String key = (String) iter.next();
                String value = this.serverProps.getProperty(key);

                if (!isNonCommandLineArgument(key)) {
                    if (value != null && value.length() > 0) {
                        commandLineBuf.append(" \"");
                        commandLineBuf.append("--");
                        commandLineBuf.append(key);
                        commandLineBuf.append("=");
                        commandLineBuf.append(value);
                        commandLineBuf.append("\"");
                    } else {
                        commandLineBuf.append(" --");
                        commandLineBuf.append(key);
                    }
                }
            }
        }

        return commandLineBuf.toString();
    }

    /**
     * Returns true if the property does not belong as a command-line argument
     * 
     * @param propName
     *            property name
     * 
     * @return boolean if the property should not be a command-line argument.
     */
    private boolean isNonCommandLineArgument(String propName) {
        return propName.equals(EXECUTABLE_NAME_KEY) || propName.equals(EXECUTABLE_PATH_KEY);
    }

    /**
     * Is this ServerController running on a Windows operating system?
     * 
     * @return boolean if this ServerController is running on Windows
     */
    private boolean runningOnWindows() {
        return StringUtils.indexOfIgnoreCase(Constants.OS_NAME, "WINDOWS") != -1;
    }
}
