/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

#include "config.h"
#include "qpidd.h"
#include "qpid/Exception.h"
#include "qpid/broker/Broker.h"
#include "qpid/broker/Daemon.h"
#include "qpid/broker/SignalHandler.h"
#include "qpid/log/Logger.h"

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/utsname.h>

using std::cout;
using std::endl;

namespace qpid {
namespace broker {

BootstrapOptions::BootstrapOptions(const char* argv0)
  : qpid::Options("Options"),
    common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
    module(QPIDD_MODULE_DIR),
    log(argv0)
{
    add(common);
    add(module);
    add(log);
}

void BootstrapOptions::usage() const {
    cout << "Usage: qpidd [OPTIONS]" << endl << endl << *this << endl;
}

namespace {
const std::string TCP = "tcp";
}

struct DaemonOptions : public qpid::Options {
    bool daemon;
    bool quit;
    bool check;
    int wait;
    std::string piddir;
    std::string transport;

    DaemonOptions() : qpid::Options("Daemon options"), daemon(false), quit(false), check(false), wait(600), transport(TCP)
    {
        char *home = ::getenv("HOME");

        if (home == 0)
            piddir += "/tmp";
        else
            piddir += home;
        piddir += "/.qpidd";

        addOptions()
            ("daemon,d", pure_switch(daemon), "Run as a daemon. Logs to syslog by default in this mode.")
            ("transport", optValue(transport, "TRANSPORT"), "The transport for which to return the port")
            ("pid-dir", optValue(piddir, "DIR"), "Directory where port-specific PID file is stored")
            ("wait,w", optValue(wait, "SECONDS"), "Sets the maximum wait time to initialize or shutdown the daemon. If the daemon fails to initialize/shutdown, prints an error and returns 1")
            ("check,c", pure_switch(check), "Prints the daemon's process ID to stdout and returns 0 if the daemon is running, otherwise returns 1")
            ("quit,q", pure_switch(quit), "Tells the daemon to shut down");
    }
};

struct QpiddPosixOptions : public QpiddOptionsPrivate {
    DaemonOptions daemon;
    QpiddOptions *parent;

    QpiddPosixOptions(QpiddOptions *parent_) : parent(parent_) {
        parent->add(daemon);
    }
};

QpiddOptions::QpiddOptions(const char* argv0)
  : qpid::Options("Options"),
    common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
    module(QPIDD_MODULE_DIR),
    log(argv0)
{
    add(common);
    add(module);
    add(broker);
    add(log);

    platform.reset(new QpiddPosixOptions(this));
    qpid::Plugin::addOptions(*this);
}

void QpiddOptions::usage() const {
    cout << "Usage: qpidd [OPTIONS]" << endl << endl << *this << endl;
}

// Set the broker pointer on the signal handler, then reset at end of scope.
// This is to ensure that the signal handler doesn't keep a broker
// reference after main() has returned.
//
struct ScopedSetBroker {
    ScopedSetBroker(const boost::intrusive_ptr<Broker>& broker) {
        qpid::broker::SignalHandler::setBroker(broker.get());
    }
    ~ScopedSetBroker() { qpid::broker::SignalHandler::setBroker(0); }
};

struct QpiddDaemon : public Daemon {
    QpiddPosixOptions *options;

    QpiddDaemon(std::string pidDir, QpiddPosixOptions *opts)
      : Daemon(pidDir), options(opts) {}

    /** Code for parent process */
    void parent() {
        uint16_t port = wait(options->daemon.wait);
        if (options->parent->broker.port == 0
        ) cout << port << endl;
    }

    /** Code for forked child process */
    void child() {
        boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->parent->broker));
        ScopedSetBroker ssb(brokerPtr);
        brokerPtr->accept();
        uint16_t port=brokerPtr->getPort(options->daemon.transport);
        ready(port);            // Notify parent.
        if (options->parent->broker.enableMgmt && (options->parent->broker.port == 0 || options->daemon.transport != TCP)) {
            boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(brokerPtr->GetManagementObject())->set_port(port);
        }
        brokerPtr->run();
    }
};

int QpiddBroker::execute (QpiddOptions *options) {
    // Options that affect a running daemon.
    QpiddPosixOptions *myOptions =
      static_cast<QpiddPosixOptions *>(options->platform.get());
    if (myOptions == 0)
        throw Exception("Internal error obtaining platform options");

    if (myOptions->daemon.check || myOptions->daemon.quit) {
        pid_t pid;
        try {
            pid = Daemon::getPid(myOptions->daemon.piddir, options->broker.port);
        } catch (const ErrnoException& e) {
            // This is not a critical error, usually means broker is not running
            QPID_LOG(notice, "Cannot stop broker: " << e.what());
            return 1;
        }
        if (pid < 0)
            return 1;
        if (myOptions->daemon.check)
            cout << pid << endl;
        if (myOptions->daemon.quit) {
            if (kill(pid, SIGINT) < 0)
                throw Exception("Failed to stop daemon: " + qpid::sys::strError(errno));
            // Wait for the process to die before returning
            int retry=myOptions->daemon.wait*1000;    // Try up to "--wait N" seconds, do retry every millisecond
            while (kill(pid,0) == 0 && --retry)
                sys::usleep(1000);
            if (retry == 0)
                throw Exception("Gave up waiting for daemon process to exit");
        }
        return 0;
    }

    // Starting the broker.
    if (myOptions->daemon.daemon) {
        // For daemon mode replace default stderr with syslog.
        options->log.sinkOptions->detached();
        qpid::log::Logger::instance().configure(options->log);
        // Fork the daemon
        QpiddDaemon d(myOptions->daemon.piddir, myOptions);
        d.fork();           // Broker is stared in QpiddDaemon::child()
    }
    else {                  // Non-daemon broker.
        boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->broker));
        ScopedSetBroker ssb(brokerPtr);
        brokerPtr->accept();
        if (options->broker.port == 0) {
            uint16_t port = brokerPtr->getPort(myOptions->daemon.transport);
            cout << port << endl;
            if (options->broker.enableMgmt) {
                boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(brokerPtr->GetManagementObject())->set_port(port);
            }
        }
        brokerPtr->run();
    }
    return 0;
}

}} // namespace qpid::Broker

int main(int argc, char* argv[])
{
    return qpid::broker::run_broker(argc, argv);
}
