/* libobby - Network text editing library
 * Copyright (C) 2005-2006 0x539 dev group
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdexcept>
#include <sstream>

#include "zeroconf_howl.hpp"

namespace obby
{

zeroconf_howl::zeroconf_howl()
{
	if (sw_discovery_init(&m_session) != SW_OKAY)
		throw std::runtime_error("sw_discovery_init() failed.");
	if (sw_discovery_salt(m_session, &m_salt) != SW_OKAY)
		throw std::runtime_error("sw_discovery_salt() failed.");
}

zeroconf_howl::~zeroconf_howl()
{
	unpublish_all();
	sw_discovery_fina(m_session);
}

void zeroconf_howl::publish(const std::string& name, unsigned int port)
{
	sw_discovery_oid oid;
	sw_result result;

	/* This publishes a record for other users of this library within the
	 * default domain (.local) */
	if((result = sw_discovery_publish(m_session, 0, name.c_str(),
		"_lobby._tcp.", NULL, NULL, port, NULL, 0,
	       	&zeroconf_howl::handle_publish_reply,
		static_cast<sw_opaque>(this), &oid)) != SW_OKAY)
	{
		std::stringstream stream;
		stream << "sw_discovery_publish(...) failed: " << result;
		throw std::runtime_error(stream.str());
	}
	else
	{
		m_published[name] = oid;
	}
}

void zeroconf_howl::unpublish(const std::string& name)
{
	if(!m_published[name])
	{
		std::stringstream stream;
		stream << "unpublish not possible for \"" << name << "\"";
		throw std::runtime_error(stream.str());
	}
	else
	{
		sw_discovery_cancel(m_session, m_published[name]);
	}
	m_published.erase(name);
}

void zeroconf_howl::unpublish_all()
{
	std::map<std::string, sw_discovery_oid>::iterator i;
	for(i = m_published.begin(); i != m_published.end(); ++i)
		sw_discovery_cancel(m_session, i->second);
	m_published.clear();
}

void zeroconf_howl::discover()
{
	sw_discovery_oid oid;
	sw_result result;

	if ((result = sw_discovery_browse(m_session, 0, "_lobby._tcp", NULL,
		&zeroconf_howl::handle_browse_reply,
		static_cast<sw_opaque>(this), &oid)) != SW_OKAY)
	{
		std::stringstream stream;
		stream << "discover failed: " << result;
		throw std::runtime_error(stream.str());
	}
}

void zeroconf_howl::select()
{
	sw_discovery_run(m_session);
}

void zeroconf_howl::select(unsigned int msecs)
{
	sw_ulong ms = msecs;
	sw_salt_step(m_salt, &ms);
}

sw_result zeroconf_howl::handle_publish_reply(sw_discovery discovery,
	sw_discovery_oid oid, sw_discovery_publish_status status,
	sw_opaque extra)
{
	if (status != SW_OKAY)
	{
		std::stringstream stream;
		stream << "publish failed: " << status;
		throw std::runtime_error(stream.str());
	}
	return SW_OKAY;
}

sw_result zeroconf_howl::handle_browse_reply(sw_discovery discovery,
	sw_discovery_oid oid, sw_discovery_browse_status status,
	sw_uint32 interface_index, sw_const_string name, sw_const_string type,
	sw_const_string domain, sw_opaque extra)
{
	sw_discovery session = static_cast<zeroconf_howl*>(extra)->m_session;

	switch(status)
	{
		case SW_DISCOVERY_BROWSE_INVALID:
		{
			throw std::runtime_error(
				"sw_discovery failed within the callback");
			break;
		}

		case SW_DISCOVERY_BROWSE_ADD_SERVICE:
		{
			sw_result result;
			if ((result = sw_discovery_resolve(session,
				interface_index, name, type, domain,
				&zeroconf_howl::handle_resolve_reply,
				extra, &oid)) != SW_OKAY)
			{
				std::stringstream stream;
				stream << "resolve failed: " << result;
				throw std::runtime_error(stream.str());
			}
			break;
		}

		case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
		{
			static_cast<zeroconf_howl*>(
				extra)->leave_event().emit(name);
			break;
		}
	}

	return SW_OKAY;
}

sw_result zeroconf_howl::handle_resolve_reply(sw_discovery discovery,
	sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name,
	sw_const_string type, sw_const_string domain, sw_ipv4_address address,
	sw_port port, sw_octets text_record, sw_ulong text_record_len,
	sw_opaque extra)
{
	// At least newer revisions of OS X emit 0.0.0.0 as a second pseudo
	// entry when discovering the local host as the IPv6 address gets
	// parsed as a IPv4 address anywhere within Howl which does not yet
	// support service discovery on IPv6.
	uint32_t ip(sw_ipv4_address_saddr(address));
	if(ip != 0)
		static_cast<zeroconf_howl*>(extra)->discover_event().emit(
			name, net6::ipv4_address::create_from_address(
			ip, port));
	return SW_OKAY;
}

}



syntax highlighted by Code2HTML, v. 0.9.1