/*
 *  This file is part of qtzeroconf. (c) 2012 Johannes Hilden
 *  https://github.com/johanneshilden/qtzeroconf
 *
 *  qtzeroconf is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1 of the
 *  License, or (at your option) any later version.
 *
 *  qtzeroconf 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 Lesser General
 *  Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with qtzeroconf; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#include <QDebug>
#include <QStringBuilder>
#include <avahi-client/publish.h>
#include <avahi-common/error.h>
#include <avahi-common/alternative.h>
#include "zconfserviceclient.h"
#include "zconfservice.h"

class ZConfServicePrivate
{
public:
    ZConfServicePrivate()
        : client(0), group(0), error(0)
    {
    }

    static void callback(AvahiEntryGroup *group, AvahiEntryGroupState state, void *userdata)
    {
        Q_UNUSED(group);
        ZConfService *serviceGroup = static_cast<ZConfService *>(userdata);
        if (serviceGroup) {
            switch (state)
            {
            case AVAHI_ENTRY_GROUP_ESTABLISHED:
                emit serviceGroup->entryGroupEstablished();
                qDebug() << ("Service '" % serviceGroup->m_ptr->name % "' successfully establised.");
                break;
            case AVAHI_ENTRY_GROUP_COLLISION:
                emit serviceGroup->entryGroupNameCollision();
                break;
            case AVAHI_ENTRY_GROUP_FAILURE:
                emit serviceGroup->entryGroupFailure();
                qDebug() << ("Entry group failure: " % serviceGroup->errorString());
                break;
            case AVAHI_ENTRY_GROUP_UNCOMMITED:
                qDebug() << "AVAHI_ENTRY_GROUP_UNCOMMITED";
                break;
            case AVAHI_ENTRY_GROUP_REGISTERING:
                qDebug() << "AVAHI_ENTRY_GROUP_REGISTERING";
            } // end switch
        }
    }

    ZConfServiceClient *client;
    AvahiEntryGroup    *group;
    QString             name;
    in_port_t           port;
    QString             type;
    int                 error;
};

/*!
    \class ZConfService

    \brief This class provides Avahi Zeroconf service registration. It can be
    used by server applications to announce a service on the local area network.

    Typical use involves creating an instance of ZConfService and calling
    registerService() with a service name and port number.
 */

ZConfService::ZConfService(QObject *parent)
    : QObject(parent),
      m_ptr(new ZConfServicePrivate)
{
    m_ptr->client = new ZConfServiceClient(this);
    m_ptr->client->run();
}

/*!
    Destroys the object and releases all resources associated with it.
 */
ZConfService::~ZConfService()
{
    if (m_ptr->group)
        avahi_entry_group_free(m_ptr->group);
    delete m_ptr;
}

/*!
    Returns true if the service group was added and commited without error.
 */
bool ZConfService::isValid() const
{
    return (m_ptr->group && !m_ptr->error);
}

/*!
    Returns a human readable error string with details of the last error that
    occured.
 */
QString ZConfService::errorString() const
{
    if (!m_ptr->client->m_client)
        return "No client!";
    return avahi_strerror(avahi_client_errno(m_ptr->client->m_client));
}

/*!
    Registers a Zeroconf service on the LAN. If no service type is specified,
    "_http._tcp" is assumed. Needless to say, the server should be available
    and listen on the specified port.
 */
void ZConfService::registerService(QString name, in_port_t port, QString type)
{
    if (!m_ptr->client->m_client || AVAHI_CLIENT_S_RUNNING
            != avahi_client_get_state(m_ptr->client->m_client)) {
        qDebug() << "ZConfService error: Client is not running.";
        return;
    }

    m_ptr->name = name;
    m_ptr->port = port;
    m_ptr->type = type;

    if (!m_ptr->group) {
        m_ptr->group = avahi_entry_group_new(m_ptr->client->m_client,
                                             ZConfServicePrivate::callback,
                                             this);
    }
    if (avahi_entry_group_is_empty(m_ptr->group)) {
        m_ptr->error = avahi_entry_group_add_service(m_ptr->group,
                                                     AVAHI_IF_UNSPEC,
                                                     AVAHI_PROTO_INET,
                                                     (AvahiPublishFlags) 0,
                                                     m_ptr->name.toLatin1().data(),
                                                     m_ptr->type.toLatin1().data(),
                                                     0,
                                                     0,
                                                     m_ptr->port,
                                                     NULL);
        if (!m_ptr->error) {
            m_ptr->error = avahi_entry_group_commit(m_ptr->group);
        }
        if (m_ptr->error)
            qDebug() << ("Error creating service: " % errorString());
    }
}

/*!
    Deregisters the service associated with this object. You can reuse the same
    ZConfService object at any time to register another service on the network.
 */
void ZConfService::resetService()
{
    avahi_entry_group_reset(m_ptr->group);
}
