/* This file is part of the KDE project
 *
 * Copyright (C) 2004 Jakub Stachowski <qbast@go2.pl>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "mdnsd-servicebrowser_p.h"
#include "domainbrowser.h"
#include "servicebrowser.h"
#include "mdnsd-responder.h"
#include "remoteservice.h"
#include "mdnsd-sdevent.h"
#include <dns_sd.h>
#include <QtCore/QStringList>
#include <QtCore/QHash>
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer>

#define TIMEOUT_WAN 2000
#define TIMEOUT_LAN 200

namespace DNSSD
{
void query_callback (DNSServiceRef, DNSServiceFlags flags, uint32_t, DNSServiceErrorType errorCode,
		     const char *serviceName, const char *regtype, const char *replyDomain, void *context);

ServiceBrowser::ServiceBrowser(const QString& type,bool autoResolve,const QString& domain, const QString& subtype)
	:d(new ServiceBrowserPrivate(this))
{
	d->m_type=type;
	d->m_autoResolve=autoResolve;
	d->m_domain=domain;
	d->m_subtype=subtype;
        d->timeout.setSingleShot(true);
	connect(&d->timeout,SIGNAL(timeout()),d,SLOT(onTimeout()));
}


ServiceBrowser::State ServiceBrowser::isAvailable()
{
//	DNSServiceRef ref;
//	bool ok (DNSServiceCreateConnection(&ref)==kDNSServiceErr_NoError);
//	if (ok) DNSServiceRefDeallocate(ref);
//	return (ok) ? Working : Stopped;
	return Working;
}
ServiceBrowser::~ ServiceBrowser()
{
	delete d;
}

bool ServiceBrowser::isAutoResolving() const
{
    return d->m_autoResolve;
}


void ServiceBrowserPrivate::serviceResolved(bool success)
{
	QObject* sender_obj = const_cast<QObject*>(sender());
	RemoteService* svr = static_cast<RemoteService*>(sender_obj);
	disconnect(svr,SIGNAL(resolved(bool)),this,SLOT(serviceResolved(bool)));
	QList<RemoteService::Ptr>::Iterator it = m_duringResolve.begin();
	QList<RemoteService::Ptr>::Iterator itEnd = m_duringResolve.end();
	while ( it!= itEnd && svr!= (*it).data()) ++it;
	if (it != itEnd) {
		if (success) {
		  	m_services+=(*it);
			emit m_parent->serviceAdded(RemoteService::Ptr(svr));
		}
		m_duringResolve.erase(it);
		queryFinished();
	}
}

void ServiceBrowser::startBrowse()
{
	if (d->isRunning()) return;
	d->m_finished = false;
	DNSServiceRef ref;
	QString fullType=d->m_type;
	if (!d->m_subtype.isEmpty()) fullType=d->m_subtype+"._sub."+d->m_type;
	if (DNSServiceBrowse(&ref,0,0, fullType.toAscii().constData(),
	    domainToDNS(d->m_domain),query_callback,reinterpret_cast<void*>(d))
		   == kDNSServiceErr_NoError) d->setRef(ref);
	if (!d->isRunning()) emit finished();
	else d->timeout.start(domainIsLocal(d->m_domain) ? TIMEOUT_LAN : TIMEOUT_WAN);
}


void ServiceBrowserPrivate::queryFinished()
{
	if (!m_duringResolve.count() && m_finished) emit m_parent->finished();
}


QList<RemoteService::Ptr> ServiceBrowser::services() const
{
	return d->m_services;
}

void ServiceBrowser::virtual_hook(int, void*)
{}

RemoteService::Ptr ServiceBrowserPrivate::find(RemoteService::Ptr s) const
{
    Q_FOREACH (const RemoteService::Ptr& i, m_services) if (*s==*i) return i;
    Q_FOREACH (const RemoteService::Ptr& i, m_duringResolve) if (*s==*i) return i;
    return s;
}


void ServiceBrowserPrivate::customEvent(QEvent* event)
{
	if (event->type()==QEvent::User+SD_ERROR) {
		stop();
		m_finished=false;
		queryFinished();
	}
	if (event->type()==QEvent::User+SD_ADDREMOVE) {
		AddRemoveEvent *aev = static_cast<AddRemoveEvent*>(event);
		// m_type has useless trailing dot
		RemoteService::Ptr svr(new RemoteService(aev->m_name,aev->m_type.left(aev->m_type.length()-1),aev->m_domain));
		if (aev->m_op==AddRemoveEvent::Add) {
		    if (m_autoResolve) {
			connect(svr.data(),SIGNAL(resolved(bool )),this,SLOT(serviceResolved(bool )));
			m_duringResolve+=svr;
			svr->resolveAsync();
		    } else {
			m_services+=svr;
			emit m_parent->serviceAdded(svr);
		    }
		}
		else {
		    svr=find(svr);
		    emit m_parent->serviceRemoved(svr);
		    m_services.removeAll(svr);
		}
		m_finished = aev->m_last;
		if (m_finished) queryFinished();
	}
}

void ServiceBrowserPrivate::onTimeout()
{
	m_finished=true;
	queryFinished();
}

void query_callback (DNSServiceRef, DNSServiceFlags flags, uint32_t, DNSServiceErrorType errorCode,
		     const char *serviceName, const char *regtype, const char *replyDomain,
		     void *context)
{
	QObject *obj = reinterpret_cast<QObject*>(context);
	if (errorCode != kDNSServiceErr_NoError) {
		ErrorEvent err;
		QCoreApplication::sendEvent(obj, &err);
	} else {
		AddRemoveEvent arev((flags & kDNSServiceFlagsAdd) ? AddRemoveEvent::Add :
			AddRemoveEvent::Remove, QString::fromUtf8(serviceName), regtype,
			DNSToDomain(replyDomain), !(flags & kDNSServiceFlagsMoreComing));
		QCoreApplication::sendEvent(obj, &arev);
	}
}


}

#include "servicebrowser.moc"
#include "mdnsd-servicebrowser_p.moc"
