/*
 * Copyright (c) 2010-2016 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

#include <Swiften/LinkLocal/LinkLocalServiceBrowser.h>

#include <iostream>

#include <boost/bind.hpp>

#include <Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h>
#include <Swiften/Network/HostAddress.h>

namespace Swift {

LinkLocalServiceBrowser::LinkLocalServiceBrowser(std::shared_ptr<DNSSDQuerier> querier) : querier(querier), haveError(false) {
}

LinkLocalServiceBrowser::~LinkLocalServiceBrowser() {
    if (isRunning()) {
        std::cerr << "WARNING: LinkLocalServiceBrowser still running on destruction" << std::endl;
    }
}


void LinkLocalServiceBrowser::start() {
    assert(!isRunning());
    haveError = false;
    browseQuery = querier->createBrowseQuery();
    browseQuery->onServiceAdded.connect(
            boost::bind(&LinkLocalServiceBrowser::handleServiceAdded, this, _1));
    browseQuery->onServiceRemoved.connect(
            boost::bind(&LinkLocalServiceBrowser::handleServiceRemoved, this, _1));
    browseQuery->onError.connect(
            boost::bind(&LinkLocalServiceBrowser::handleBrowseError, this));
    browseQuery->startBrowsing();
}

void LinkLocalServiceBrowser::stop() {
    assert(isRunning());
    if (isRegistered()) {
        unregisterService();
    }
    for (ResolveQueryMap::const_iterator i = resolveQueries.begin(); i != resolveQueries.end(); ++i) {
        i->second->stop();
    }
    resolveQueries.clear();
    services.clear();
    browseQuery->stopBrowsing();
    browseQuery.reset();
    onStopped(haveError);
}

bool LinkLocalServiceBrowser::isRunning() const {
    return !!browseQuery;
}

bool LinkLocalServiceBrowser::hasError() const {
    return haveError;
}

bool LinkLocalServiceBrowser::isRegistered() const {
    return !!registerQuery;
}

void LinkLocalServiceBrowser::registerService(const std::string& name, int port, const LinkLocalServiceInfo& info) {
    assert(!registerQuery);
    registerQuery = querier->createRegisterQuery(name, port, info.toTXTRecord());
    registerQuery->onRegisterFinished.connect(
        boost::bind(&LinkLocalServiceBrowser::handleRegisterFinished, this, _1));
    registerQuery->registerService();
}

void LinkLocalServiceBrowser::updateService(const LinkLocalServiceInfo& info) {
    assert(registerQuery);
    registerQuery->updateServiceInfo(info.toTXTRecord());
}

void LinkLocalServiceBrowser::unregisterService() {
    assert(registerQuery);
    registerQuery->unregisterService();
    registerQuery.reset();
    selfService.reset();
}

std::vector<LinkLocalService> LinkLocalServiceBrowser::getServices() const {
    std::vector<LinkLocalService> result;
    for (const auto& service : services) {
        result.push_back(LinkLocalService(service.first, service.second));
    }
    return result;
}

void LinkLocalServiceBrowser::handleServiceAdded(const DNSSDServiceID& service) {
    if (selfService && service == *selfService) {
        return;
    }

    std::pair<ResolveQueryMap::iterator, bool> r = resolveQueries.insert(std::make_pair(service, std::shared_ptr<DNSSDResolveServiceQuery>()));
    if (r.second) {
        // There was no existing query yet. Start a new query.
        std::shared_ptr<DNSSDResolveServiceQuery> resolveQuery = querier->createResolveServiceQuery(service);
        resolveQuery->onServiceResolved.connect(
            boost::bind(&LinkLocalServiceBrowser::handleServiceResolved, this, service, _1));
        r.first->second = resolveQuery;
        resolveQuery->start();
    }
}

void LinkLocalServiceBrowser::handleServiceRemoved(const DNSSDServiceID& service) {
    ResolveQueryMap::iterator i = resolveQueries.find(service);
    if (i == resolveQueries.end()) {
        // Can happen after an unregister(), when getting the old 'self'
        // service remove notification.
        return;
    }
    i->second->stop();
    resolveQueries.erase(i);
    ServiceMap::iterator j = services.find(service);
    assert(j != services.end());
    LinkLocalService linkLocalService(j->first, j->second);
    services.erase(j);
    onServiceRemoved(linkLocalService);
}

void LinkLocalServiceBrowser::handleServiceResolved(const DNSSDServiceID& service, const boost::optional<DNSSDResolveServiceQuery::Result>& result) {
    if (result) {
        std::pair<ServiceMap::iterator, bool> r = services.insert(std::make_pair(service, *result));
        if (r.second) {
            onServiceAdded(LinkLocalService(r.first->first, r.first->second));
        }
        else {
            r.first->second = *result;
            onServiceChanged(LinkLocalService(r.first->first, r.first->second));
        }
    }
}

void LinkLocalServiceBrowser::handleRegisterFinished(const boost::optional<DNSSDServiceID>& result) {
    if (result) {
        selfService = result;
        onServiceRegistered(*result);
    }
    else {
        haveError = true;
        stop();
    }
}

void LinkLocalServiceBrowser::handleBrowseError() {
    haveError = true;
    stop();
}

}