/*
 * Copyright (C) 2009 Chase Douglas
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.
 */

#include <QFile>
#include <QQueue>
#include <QSslCertificate>
#include <QSslKey>
#include <QStringList>
#include <QtCore/QCoreApplication>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/stat.h>
#include <unistd.h>

#include "Server.h"
#include "config.h"
#include "macros.h"

#ifdef __APPLE__
#include "MacAuthThread.h"
#endif

#if defined __APPLE__ || defined USE_AVAHI
#define BROADCAST
#endif

static void usage(char *name) {
    qCritical(
              "Usage: %s [options]\n"
              "Options:\n"
              "  -r|--realm <realm>\t\tRealm to authenticate clients against (rinput)\n"
              "  -c|--cert <certificate>\tSSL certificate file (/etc/rinput/rinput.crt)\n"
              "  -k|--key <key>\t\tSSL private key file (/etc/rinput/rinput.key)\n"
              "  -p|--port <port>\t\tPort to listen for clients on, 0 for any (8771)\n"
#ifdef BROADCAST
              "  -n|--no-broadcast\t\tDo not advertise rinput service through mDNS\n"
#endif
#ifndef __APPLE__
              "  -f|--foreground\t\tDo not fork, run in foreground\n"
#endif
              "  -v|--verbose\t\t\tVerbose output\n"
              "  -q|--quiet\t\t\tQuiet output\n",
              name
             );
}

static void normalMsgHandler(QtMsgType type, const char *msg) {
    switch (type) {
        case QtDebugMsg:
        case QtWarningMsg:
            break;
        case QtCriticalMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtFatalMsg:
            fprintf(stderr, "%s\n", msg);
            abort();
     }
}

static void warningMsgHandler(QtMsgType type, const char *msg) {
    switch (type) {
        case QtDebugMsg:
            break;
        case QtWarningMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtCriticalMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtFatalMsg:
            fprintf(stderr, "%s\n", msg);
            abort();
     }
}

static void debugMsgHandler(QtMsgType type, const char *msg) {
    switch (type) {
        case QtDebugMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtWarningMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtCriticalMsg:
            fprintf(stderr, "%s\n", msg);
            break;
        case QtFatalMsg:
            fprintf(stderr, "%s\n", msg);
            abort();
     }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QStringList args(a.arguments());
    args.removeFirst();

    QString realm("rinput");

    QString certFilename("/etc/rinput/rinput.crt");
    QString keyFilename("/etc/rinput/rinput.key");

    quint16 port = 8771;

    bool broadcast = TRUE;

    int verbosity = 1;

#ifndef __APPLE__
    bool foreground = FALSE;
#endif

    forever {
        static const struct option longOptions[] = {
            {"help", no_argument, 0, 'h'},
            {"realm", required_argument, 0, 'r'},
            {"cert", required_argument, 0, 'c'},
            {"key", required_argument, 0, 'k'},
            {"port", required_argument, 0, 'p'},
#ifdef BROADCAST
            {"no-broadcast", no_argument, 0, 'n'},
#endif
            {"verbose", no_argument, 0, 'v'},
            {"quiet", no_argument, 0, 'q'},
#ifndef __APPLE__
            {"foreground", no_argument, 0, 'f'},
#endif
            {0, 0, 0, 0}
        };

        switch (getopt_long(argc, argv, "hr:c:k:p:vq"
#ifdef BROADCAST
                "n"
#endif
#ifndef __APPLE__
                "f"
#endif
                , longOptions, NULL)) {
            case 'h':
                usage(argv[0]);
                return -1;

            case 'r':
                realm = optarg;
                break;

            case 'c':
                certFilename = optarg;
                break;

            case 'k':
                keyFilename = optarg;
                break;

            case 'p': {
                QByteArray portString(optarg);
                bool ok;
                port = portString.toUShort(&ok);
                if (!ok) {
                    qCritical("Error: Port number invalid");
                    return -1;
                }
                break;
            }

#ifdef BROADCAST
            case 'n':
                broadcast = FALSE;
                break;
#endif

            case 'v':
                if (verbosity != 1) {
                    qCritical("Error: Verbose and quiet are mutually exclusive and may each only be used once");
                    return -1;
                }
                verbosity++;
                break;

            case 'q':
                if (verbosity != 1) {
                    qCritical("Error: Verbose and quiet are mutually exclusive and may each only be used once");
                    return -1;
                }
                verbosity--;
                break;

#ifndef __APPLE__
            case 'f':
                foreground = TRUE;
                break;
#endif

            case -1:
                goto done;

            case '?':
                usage(argv[0]);
                return -1;

            default:
                qFatal("getopt_long failed to return a valid value");
                break;
        }
    }

done:
#ifndef __APPLE__
    if (!foreground) {
        if (daemon(0, 1)) {
            qCritical("Failed to become a daemon: %s", strerror(errno));
            return -1;
        }
    }
#endif

    switch(verbosity) {
        case 0:
            qInstallMsgHandler(normalMsgHandler);
            break;
        case 1:
            qInstallMsgHandler(warningMsgHandler);
            break;
        default:
            qInstallMsgHandler(debugMsgHandler);
            break;
    }

    QFile certFile(certFilename);
    if (!certFile.open(QIODevice::ReadOnly)) {
        qCritical("Could not open certificate file");
        return -1;
    }

    QFile keyFile(keyFilename);
    if (!keyFile.open(QIODevice::ReadOnly)) {
        qCritical("Could not open private key file");
        return -1;
    }

    QSslCertificate cert(&certFile);
    if (cert.isNull()) {
        qCritical("Certificate file invalid");
        return -1;
    }

    QSslKey *key = new QSslKey(&keyFile, QSsl::Rsa);
    if (key->isNull()) {
        keyFile.reset();
        key = new QSslKey(&keyFile, QSsl::Dsa);
        if (key->isNull()) {
            qCritical("Key file invalid");
            return -1;
        }
    }
        
#ifdef __APPLE__
    MacAuthThread authThread;
    if (!authThread.setup()) {
        return -1;
    }
    authThread.start();
#endif

    Server server(port, realm, cert, *key, broadcast);
    if (!server.isListening()) {
        return -1;
    }

    delete key;

    return a.exec();
}
