/*
 *
 *  D-Bus++ - C++ bindings for D-Bus
 *
 *  Copyright (C) 2005-2007  Paolo Durante <shackan@gmail.com>
 *
 *
 *  This library 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.
 *
 *  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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


#include <dbus-c++/debug.h>
#include <dbus-c++/connection.h>

#include <dbus/dbus.h>
#include <string>

#include "internalerror.h"

#include "connection_p.h"
#include "dispatcher_p.h"
#include "server_p.h"
#include "message_p.h"
#include "pendingcall_p.h"

using namespace DBus;

Connection::Private::Private( DBusConnection* c, Server::Private* s )
: conn(c) , dispatcher(0), server(s)
{
	init();
}

Connection::Private::Private( DBusBusType type )
{
	InternalError e;

	conn = dbus_bus_get_private(type, e);

	if(e) throw Error(e);

	init();
}

Connection::Private::~Private()
{
	debug_log("terminating connection 0x%08x", conn);

	detach_server();

	if(dbus_connection_get_is_connected(conn))
	{
		std::vector<std::string>::iterator i = names.begin();

		while(i != names.end())
		{
			debug_log("%s: releasing bus name %s", dbus_bus_get_unique_name(conn), i->c_str());
			dbus_bus_release_name(conn, i->c_str(), NULL);
			++i;
		}
		dbus_connection_close(conn);
	}
	dbus_connection_unref(conn);
}

void Connection::Private::init()
{
	dbus_connection_ref(conn);
	dbus_connection_ref(conn);	//todo: the library has to own another reference

	disconn_filter = new Callback<Connection::Private, bool, const Message&>(
		this, &Connection::Private::disconn_filter_function
	);

	dbus_connection_add_filter(conn, message_filter_stub, &disconn_filter, NULL);

	dbus_connection_set_dispatch_status_function(conn, dispatch_status_stub, this, 0);
	dbus_connection_set_exit_on_disconnect(conn, false); //why was this set to true??
}

void Connection::Private::detach_server()
{
/*	Server::Private* tmp = server;

	server = NULL;

	if(tmp)
	{
		ConnectionList::iterator i;

		for(i = tmp->connections.begin(); i != tmp->connections.end(); ++i)
		{
			if(i->_pvt.get() == this)
			{
				tmp->connections.erase(i);
				break;
			}
		}
	}*/
}

bool Connection::Private::do_dispatch()
{
	debug_log("dispatching on %p", conn);

	if(!dbus_connection_get_is_connected(conn))
	{
		debug_log("connection terminated");

		detach_server();

		return true;
	}

	return dbus_connection_dispatch(conn) != DBUS_DISPATCH_DATA_REMAINS;
}

void Connection::Private::dispatch_status_stub( DBusConnection* dc, DBusDispatchStatus status, void* data )
{
	Private* p = static_cast<Private*>(data);

	switch(status)
	{
		case DBUS_DISPATCH_DATA_REMAINS:
		debug_log("some dispatching to do on %p", dc);
		p->dispatcher->queue_connection(p);
		break;

		case DBUS_DISPATCH_COMPLETE:
		debug_log("all dispatching done on %p", dc);
		break;

		case DBUS_DISPATCH_NEED_MEMORY: //uh oh...
		debug_log("connection %p needs memory", dc);
		break;
	}
}

DBusHandlerResult Connection::Private::message_filter_stub( DBusConnection* conn, DBusMessage* dmsg, void* data )
{
	MessageSlot* slot = static_cast<MessageSlot*>(data);

	Message msg = Message(new Message::Private(dmsg));

	return slot && !slot->empty() && slot->call(msg) 
			? DBUS_HANDLER_RESULT_HANDLED
			: DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

bool Connection::Private::disconn_filter_function( const Message& msg )
{
	if(msg.is_signal(DBUS_INTERFACE_LOCAL,"Disconnected"))
	{
		debug_log("%p disconnected by local bus", conn);
		dbus_connection_close(conn);

		return true;
	}
	return false;
}

Connection Connection::SystemBus()
{
	return Connection(new Private(DBUS_BUS_SYSTEM));
}

Connection Connection::SessionBus()
{
	return Connection(new Private(DBUS_BUS_SESSION));
}

Connection Connection::ActivationBus()
{
	return Connection(new Private(DBUS_BUS_STARTER));
}

Connection::Connection( const char* address, bool priv )
{
	InternalError e;
	DBusConnection* conn = priv 
		? dbus_connection_open_private(address, e)
		: dbus_connection_open(address, e);

	if(e) throw Error(e);

	_pvt = new Private(conn);

	setup(default_dispatcher);

	debug_log("connected to %s", address);
}

Connection::Connection( Connection::Private* p )
: _pvt(p)
{
	setup(default_dispatcher);
}

Connection::Connection( const Connection& c )
: _pvt(c._pvt)
{
	dbus_connection_ref(_pvt->conn);
}

Connection::~Connection()
{
	dbus_connection_unref(_pvt->conn);
}

Dispatcher* Connection::setup( Dispatcher* dispatcher )
{
	debug_log("registering stubs for connection %p", _pvt->conn);

	if(!dispatcher) dispatcher = default_dispatcher;

	if(!dispatcher) throw ErrorFailed("no default dispatcher set for new connection");

	Dispatcher* prev = _pvt->dispatcher;

	_pvt->dispatcher = dispatcher;

	dispatcher->queue_connection(_pvt.get());

	dbus_connection_set_watch_functions(
		_pvt->conn,
		Dispatcher::Private::on_add_watch,
		Dispatcher::Private::on_rem_watch,
		Dispatcher::Private::on_toggle_watch,
		dispatcher,
		0
	);

	dbus_connection_set_timeout_functions(
		_pvt->conn,
		Dispatcher::Private::on_add_timeout,
		Dispatcher::Private::on_rem_timeout,
		Dispatcher::Private::on_toggle_timeout,
		dispatcher,
		0
	);

	return prev;
}

bool Connection::operator == ( const Connection& c ) const
{
	return _pvt->conn == c._pvt->conn;
}

bool Connection::register_bus()
{
	InternalError e;

	bool r = dbus_bus_register(_pvt->conn, e);
  
	if(e) throw (e);

	return r;
}

bool Connection::connected() const
{
	return dbus_connection_get_is_connected(_pvt->conn);
}

void Connection::disconnect()
{
//	dbus_connection_disconnect(_pvt->conn); // disappeared in 0.9x
	dbus_connection_close(_pvt->conn);
}

void Connection::exit_on_disconnect( bool exit )
{
	dbus_connection_set_exit_on_disconnect(_pvt->conn, exit);
}

bool Connection::unique_name( const char* n )
{
	return dbus_bus_set_unique_name(_pvt->conn, n);
}

const char* Connection::unique_name() const
{
	return dbus_bus_get_unique_name(_pvt->conn);
}

void Connection::flush()
{
	dbus_connection_flush(_pvt->conn);
}

void Connection::add_match( const char* rule )
{
	InternalError e;

	dbus_bus_add_match(_pvt->conn, rule, e);

	debug_log("%s: added match rule %s", unique_name(), rule);

	if(e) throw Error(e);
}

void Connection::remove_match( const char* rule )
{
	InternalError e;
	
	dbus_bus_remove_match(_pvt->conn, rule, e);

	debug_log("%s: removed match rule %s", unique_name(), rule);

	if(e) throw Error(e);
}

bool Connection::add_filter( MessageSlot& s )
{
	debug_log("%s: adding filter", unique_name());
	return dbus_connection_add_filter(_pvt->conn, Private::message_filter_stub, &s, NULL);
}

void Connection::remove_filter( MessageSlot& s )
{
	debug_log("%s: removing filter", unique_name());
	dbus_connection_remove_filter(_pvt->conn, Private::message_filter_stub, &s);
}

bool Connection::send( const Message& msg, unsigned int* serial )
{
	return dbus_connection_send(_pvt->conn, msg._pvt->msg, serial);
}

Message Connection::send_blocking( Message& msg, int timeout )
{
	DBusMessage* reply;
	InternalError e;
	
	reply = dbus_connection_send_with_reply_and_block(_pvt->conn, msg._pvt->msg, timeout, e);

	if(e) throw Error(e);

	return Message(new Message::Private(reply), false);
}

PendingCall Connection::send_async( Message& msg, int timeout )
{
	DBusPendingCall* pending;

	if(!dbus_connection_send_with_reply(_pvt->conn, msg._pvt->msg, &pending, timeout))
	{
		throw ErrorNoMemory("Unable to start asynchronous call");
	}
	return PendingCall(new PendingCall::Private(pending));
}

void Connection::request_name( const char* name, int flags )
{
	InternalError e;

	debug_log("%s: registering bus name %s", unique_name(), name);

	dbus_bus_request_name(_pvt->conn, name, flags, e);	//we deliberately don't check return value

	if(e) throw Error(e);

//	this->remove_match("destination");

	if(name)
	{
		_pvt->names.push_back(name);
		std::string match = "destination='" + _pvt->names.back() + "'";
		add_match(match.c_str());
	}
}

bool Connection::has_name( const char* name )
{	
	InternalError e;

	bool b = dbus_bus_name_has_owner(_pvt->conn, name, e);

	if(e) throw Error(e);

	return b;
}

const std::vector<std::string>& Connection::names()
{
	return _pvt->names;
}

bool Connection::start_service( const char* name, unsigned long flags )
{
	InternalError e;

	bool b = dbus_bus_start_service_by_name(_pvt->conn, name, flags, NULL, e);

	if(e) throw Error(e);
	
	return b;
}

