/*
 * 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 <QHostInfo>
#include <QSslCertificate>
#include <QSslKey>
#include <QSslSocket>

#include "config.h"

#include "Connection.h"
#include "Server.h"
#include "macros.h"
#include "rinput.h"

#ifdef linux
#include "LinuxInputDevice.h"
#elif __APPLE__
#include "MacAuthThread.h"
#include "MacInputDevice.h"
#endif

Server::Server(quint16 port, QString &realm, QSslCertificate &cert, QSslKey &key, bool broadcast) :
    config(QSslConfiguration::defaultConfiguration()) {
#if defined __APPLE__ || defined USE_AVAHI
    broadcaster = NULL;
#else
    broadcast = FALSE;
#endif
    
#ifndef __APPLE__
    int result = sasl_server_init(NULL, "rinput");
    if (result != SASL_OK) {
        qCritical("Error: Failed to initialize SASL interface (%d)", result);
        return;
    }

    result = sasl_server_new("rinput", NULL, realm.toAscii().data(), NULL, NULL, NULL, 0, &sasl);
    if (result != SASL_OK) {
        qCritical("Error: Failed to create SASL interface (%d)", result);
        return;
    }
#endif

    config.setLocalCertificate(cert);
    config.setPrivateKey(key);

    if (!listen(QHostAddress::Any, port)) {
        qCritical("Error: Unable to bind to port %hu", port);
        return;
    }

    qWarning("Listening on port %hu", serverPort());

    if (broadcast) {
#ifdef __APPLE__
        broadcaster = new MacBroadcaster(serverPort());
        broadcaster->start();
        broadcaster->setParent(this);
#elif defined USE_AVAHI
        broadcaster = new AvahiBroadcaster(serverPort());
        broadcaster->setParent(this);
#endif
    }
}

void Server::incomingConnection(int sock) {
    Connection *connection = new Connection(sock, config);
    connection->setParent(this);

    connect(connection, SIGNAL(encrypted()), SLOT(connectionEncrypted()));
    connect(connection, SIGNAL(checkPass(const QByteArray &, const QByteArray &)), SLOT(checkPass(const QByteArray &, const QByteArray &)));
    connect(connection, SIGNAL(authenticated()), SLOT(connectionAuthenticated()));

#ifndef NO_ENCRYPTION
    connection->startEncryption();
#else
#ifndef NO_AUTHENTICATION
    connection->startAuthentication();
#else
    connection->startCommunication();
#endif
#endif
}

void Server::connectionEncrypted() {
    Connection *connection = static_cast<Connection *>(sender());
#ifndef NO_AUTHENTICATION
    connection->startAuthentication();
#else
    connection->startCommunication();
#endif
}

void Server::checkPass(const QByteArray &user, const QByteArray &pass) {
    Connection *connection = static_cast<Connection *>(sender());
    bool ok = FALSE;
    
#ifdef __APPLE__
    ok = MacAuthThread::authorized(*connection, user, pass);
#else
    ok = (sasl_checkpass(sasl, user.data(), user.length(), pass.data(), pass.length()) == SASL_OK);
#endif
    connection->checkPassResult(ok);
}

void Server::connectionAuthenticated() {
    Connection *connection = static_cast<Connection *>(sender());
    connection->startCommunication();

    rinput_message_t message = { RINPUT_VERSION, { RINPUT_PROTOCOL_VERSION } };
    hton_rinput(&message);
    if (!connection->write(QByteArray((char *)&message, sizeof(message)))) {
        qWarning("Error: Failed to send protocol version message to client %s", qPrintable(connection->peerAddress().toString()));
        connection->disconnect();
        return;
    }

#ifdef linux
    LinuxInputDevice *device = new LinuxInputDevice(connection);
#elif __APPLE__
    MacInputDevice *device = new MacInputDevice(connection);
#endif

    if (!device->create()) {
        delete device;
    }
}
