/* This file is part of VoltDB.
 * Copyright (C) 2008-2017 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.voltdb;

import java.io.File;
import java.net.URL;

import org.voltcore.common.Constants;
import org.voltcore.utils.InstanceId;
import org.voltdb.probe.MeshProber;
import org.voltdb.utils.MiscUtils;

/**
 * Wraps VoltDB in a Thread
 */
public class ServerThread extends Thread {
    VoltDB.Configuration m_config;

    public ServerThread(VoltDB.Configuration config) {
        m_config = config;
        if (m_config.m_pathToLicense == null) {
            m_config.m_pathToLicense = getTestLicensePath();
        }
        if (m_config.m_leader == null) {
            m_config.m_leader = "";
        }
        if (m_config.m_coordinators == null || m_config.m_coordinators.isEmpty()) {
            m_config.m_coordinators = MeshProber.hosts(m_config.m_internalPort);
        }
        if (m_config.m_startAction != StartAction.PROBE) {
            m_config.m_hostCount = VoltDB.UNDEFINED;
        }

        if (!m_config.validate()) {
            System.exit(-1);
        }

        // Disable loading the EE if running against HSQL.
        m_config.m_noLoadLibVOLTDB = m_config.m_backend == BackendTarget.HSQLDB_BACKEND;
        m_config.m_forceVoltdbCreate = true;
        if (config.m_startAction == StartAction.INITIALIZE || config.m_startAction == StartAction.GET) {
            VoltDB.ignoreCrash = true;
        }
        setName("ServerThread");
    }

    public ServerThread(String pathToCatalog, BackendTarget target) {
        m_config = new VoltDB.Configuration();
        m_config.m_pathToCatalog = pathToCatalog;
        m_config.m_backend = target;
        if (m_config.m_pathToLicense == null) {
            m_config.m_pathToLicense = getTestLicensePath();
        }
        m_config.m_leader = "";
        m_config.m_coordinators = MeshProber.hosts(m_config.m_internalPort);
        VoltDB.instance().setMode(OperationMode.INITIALIZING);


        // Disable loading the EE if running against HSQL.
        m_config.m_noLoadLibVOLTDB = m_config.m_backend == BackendTarget.HSQLDB_BACKEND;
        m_config.m_forceVoltdbCreate = true;

        setName("ServerThread");
    }

    public ServerThread(String pathToCatalog, String pathToDeployment, BackendTarget target) {
        m_config = new VoltDB.Configuration();
        m_config.m_pathToCatalog = pathToCatalog;
        m_config.m_pathToDeployment = pathToDeployment;
        m_config.m_backend = target;
        if (m_config.m_pathToLicense == null) {
            m_config.m_pathToLicense = getTestLicensePath();
        }
        m_config.m_leader = "";
        m_config.m_coordinators = MeshProber.hosts(m_config.m_internalPort);
        VoltDB.instance().setMode(OperationMode.INITIALIZING);


        // Disable loading the EE if running against HSQL.
        m_config.m_noLoadLibVOLTDB = m_config.m_backend == BackendTarget.HSQLDB_BACKEND;
        m_config.m_forceVoltdbCreate = true;

        if (!m_config.validate()) {
            System.exit(-1);
        }

        setName("ServerThread");
    }

    public ServerThread(String pathToCatalog,
            String pathToDeployment,
            int internalPort,
            int zkPort,
            BackendTarget target) {
        this(pathToCatalog, pathToDeployment, Constants.DEFAULT_INTERNAL_PORT, internalPort, zkPort, target);
    }

    private ServerThread(String pathToCatalog,
                        String pathToDeployment,
                        int leaderPort,
                        int internalPort,
                        int zkPort,
                        BackendTarget target)
    {
        m_config = new VoltDB.Configuration();
        m_config.m_pathToCatalog = pathToCatalog;
        m_config.m_pathToDeployment = pathToDeployment;
        m_config.m_backend = target;
        if (m_config.m_pathToLicense == null) {
            m_config.m_pathToLicense = getTestLicensePath();
        }
        m_config.m_leader = MiscUtils.getHostnameColonPortString("localhost", leaderPort);
        m_config.m_coordinators = MeshProber.hosts(internalPort);
        m_config.m_internalPort = internalPort;
        m_config.m_zkInterface = "127.0.0.1:" + zkPort;
        VoltDB.instance().setMode(OperationMode.INITIALIZING);

        // Disable loading the EE if running against HSQL.
        m_config.m_noLoadLibVOLTDB = m_config.m_backend == BackendTarget.HSQLDB_BACKEND;
        m_config.m_forceVoltdbCreate = true;

        if (!m_config.validate()) {
            System.exit(-1);
        }

        setName("ServerThread");
    }

    @Override
    public void run() {
        VoltDB.initialize(m_config);
        VoltDB.instance().run();
    }

    //Call this if you are doing init only or action GET
    public void initialize() {
        VoltDB.initialize(m_config);
    }

    //Call this if you are doing init only or action GET
    public void cli() {
        VoltDB.cli(m_config);
    }

    public void waitForInitialization() {
        // Wait until the server has actually started running.
        while (!VoltDB.instance().isRunning() ||
               VoltDB.instance().getMode() == OperationMode.INITIALIZING) {
            Thread.yield();
        }
    }

    public void waitForRejoin() {
        while (!VoltDB.instance().isRunning() ||
               VoltDB.instance().getMode() == OperationMode.INITIALIZING ||
               VoltDB.instance().rejoining()) {
            Thread.yield();
        }
    }

    public void waitForClientInterface() {
        while (!VoltDB.instance().isRunning() ||
                VoltDB.instance().getClientInterface() == null ||
                !VoltDB.instance().getClientInterface().isAcceptingConnections()) {
            Thread.yield();
        }
    }

    public void shutdown() throws InterruptedException {
        assert Thread.currentThread() != this;
        VoltDB.instance().shutdown(this);
        this.join();
        while (VoltDB.instance().isRunning()) {
            Thread.sleep(1);
        }
    }

    /**
     * For tests only, mostly with ServerThread or LocalCluster:
     *
     * Provide a valid license in the case where license checking
     * is enabled.
     *
     * Outside tests, the license file probably won't exist.
     */
    public static String getTestLicensePath() {
        // magic license stored in the voltdb enterprise code
        URL resource = ServerThread.class.getResource("valid_dr_active_subscription.xml");

        // in the community edition, any non-empty string
        // should work fine here, as it won't be checked
        if (resource == null) return "[community]";

        // return the filesystem path
        File licxml = new File(resource.getFile());
        return licxml.getPath();
    }

    public InstanceId getInstanceId()
    {
        return VoltDB.instance().getHostMessenger().getInstanceId();
    }
}
