/*
 * 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 <avahi-client/client.h>
#include <avahi-common/alternative.h>
#include <avahi-common/error.h>
#include <avahi-qt4/qt-watch.h>

#include "AvahiBroadcaster.h"

static void avahiClientCallback(AvahiClient *client, AvahiClientState state, void *userData);
static void avahiEntryGroupCallback(AvahiEntryGroup *group, AvahiEntryGroupState state, void *userData);

static void avahiClientCallback(AvahiClient *client, AvahiClientState state, void *userData) {
    AvahiBroadcaster *broadcaster = static_cast<AvahiBroadcaster *>(userData);
    broadcaster->_avahiClientCallback(client, state);
}

static void avahiEntryGroupCallback(AvahiEntryGroup *group, AvahiEntryGroupState state, void *userData) {
    AvahiBroadcaster *broadcaster = static_cast<AvahiBroadcaster *>(userData);
    broadcaster->_avahiEntryGroupCallback(state);
}

AvahiBroadcaster::AvahiBroadcaster(quint16 _port) :
    avahiClient(NULL),
    avahiGroup(NULL),
    port(_port) {
    avahiName = QHostInfo::localHostName() + " Remote Input";

    avahiClient = avahi_client_new(avahi_qt_poll_get(), AVAHI_CLIENT_NO_FAIL, avahiClientCallback, this, NULL);
    if (!avahiClient) {
        qWarning("Warning: Could not create avahi client, will not publish rinputd on network");
    }
}

void AvahiBroadcaster::_avahiClientCallback(AvahiClient *client, AvahiClientState state) {
    switch (state) {
        case AVAHI_CLIENT_FAILURE:
            qWarning("Warning: Avahi client error: %s", avahi_strerror(avahi_client_errno(avahiClient)));
            if (avahiGroup) {
                avahi_entry_group_free(avahiGroup);
                avahiGroup = NULL;
            }
            break;

        case AVAHI_CLIENT_S_COLLISION:
        case AVAHI_CLIENT_S_REGISTERING:
            if (avahiGroup) {
                avahi_entry_group_reset(avahiGroup);
            }
            break;
            
        case AVAHI_CLIENT_S_RUNNING:
            createService(client);
            break;

        case AVAHI_CLIENT_CONNECTING:
            break;
    }
}

void AvahiBroadcaster::createService(AvahiClient *client) {
    if (!avahiGroup) {
        avahiGroup = avahi_entry_group_new(client, avahiEntryGroupCallback, this);
        if (!avahiGroup) {
            qWarning("Warning: Failed to create new Avahi group %s", avahi_strerror(avahi_client_errno(client)));
            return;
        }
    }

    if (avahi_entry_group_is_empty(avahiGroup)) {
retry:
        int err = avahi_entry_group_add_service(avahiGroup, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, avahiName.toUtf8().data(), "_rinput._tcp", NULL, NULL, port, NULL);
        if (err < 0) {
            if (err == AVAHI_ERR_COLLISION) {
                alternativeServiceName();
                goto retry;
            }
            else {
                qWarning("Warning: Failed to add _rinput._tcp service to Avahi: %s", avahi_strerror(err));
                return;
            }
        }

        err = avahi_entry_group_commit(avahiGroup);
        if (err < 0) {
            qWarning("Warning: Failed to commit Avahi group entry: %s", avahi_strerror(err));
        }
    }
}

void AvahiBroadcaster::alternativeServiceName() {
    avahiName = avahi_alternative_service_name(avahiName.toUtf8().data());
    qDebug("Previous Avahi service name collided, renaming servive to %s", qPrintable(avahiName));
    avahi_entry_group_reset(avahiGroup);
}

void AvahiBroadcaster::_avahiEntryGroupCallback(AvahiEntryGroupState state) {
    switch (state) {
        case AVAHI_ENTRY_GROUP_COLLISION:
            alternativeServiceName();
            createService(avahiClient);
            break;

        case AVAHI_ENTRY_GROUP_FAILURE:
            qWarning("Warning: Avahi entry group error: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(avahiGroup))));
            break;

        case AVAHI_ENTRY_GROUP_ESTABLISHED:
            qDebug("Avahi service _rinput._tcp with name '%s' established", qPrintable(avahiName));
            break;

        case AVAHI_ENTRY_GROUP_UNCOMMITED:
        case AVAHI_ENTRY_GROUP_REGISTERING:
            break;
    }
}

AvahiBroadcaster::~AvahiBroadcaster() {
    if (avahiGroup) {
        avahi_entry_group_free(avahiGroup);
    }
    if (avahiClient) {
        avahi_client_free(avahiClient);
    }
}
