/* Copyright (C) 2007 One Laptop Per Child
 * Author: Marc Maurer <uwog@uwog.net>
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "SugarUnixAccountHandler.h"
#include "SugarBuddy.h"
#include <xp/AccountEvent.h>
#include <xp/AbiCollabSessionManager.h>
#include <xp/AbiCollab.h>
#include <ev_EditMethod.h>
#include <xap_App.h>
#include <fv_View.h>
#include <xp/Event.h>

// some fucntion prototype declarations
static bool s_offerTube(AV_View* v, EV_EditMethodCallData *d);
static bool s_joinTube(AV_View* v, EV_EditMethodCallData *d);
static bool s_buddyJoined(AV_View* v, EV_EditMethodCallData *d);
static bool s_buddyLeft(AV_View* v, EV_EditMethodCallData *d);
static DBusHandlerResult s_dbus_handle_message(DBusConnection *connection, DBusMessage *message, void *user_data);

#define INTERFACE "com.abisource.abiword.abicollab.olpc"
#define SEND_ALL_METHOD "SendAll"
#define SEND_ONE_METHOD "SendOne"

SugarAccountHandler* SugarAccountHandler::m_pHandler = NULL;
SugarAccountHandler* SugarAccountHandler::getHandler() { return m_pHandler; }

SugarAccountHandler::SugarAccountHandler()
	: AccountHandler(),
	m_pTube(NULL),
	m_bLocallyControlled(false)
{
	UT_DEBUGMSG(("SugarAccountHandler::SugarAccountHandler()\n"));
	m_pHandler = this;
	_registerEditMethods();
}

SugarAccountHandler::~SugarAccountHandler()
{
	m_pHandler = NULL;
	if (m_pTube)
	{
		dbus_connection_unref(m_pTube);
		m_pTube = NULL;
	}
}

UT_UTF8String SugarAccountHandler::getDescription()
{
	return "Sugar Presence Service";
}

UT_UTF8String SugarAccountHandler::getDisplayType()
{
	return "Sugar Presence Service";
}

UT_UTF8String SugarAccountHandler::getStorageType()
{
	return "com.abisource.abiword.abicollab.backend.sugar";
}

void SugarAccountHandler::storeProperties()
{
	// no need to implement this as we will be getting
	// all our info always directly from sugar
}

ConnectResult SugarAccountHandler::connect()
{
	UT_ASSERT_HARMLESS(UT_NOT_REACHED);
	return CONNECT_SUCCESS;
}

bool SugarAccountHandler::disconnect()
{
	UT_ASSERT_HARMLESS(UT_NOT_REACHED);
	return true;
}

bool SugarAccountHandler::isOnline()
{
	return true;
}

Buddy* SugarAccountHandler::constructBuddy(const PropertyMap& props)
{
	UT_DEBUGMSG(("SugarAccountHandler::constructBuddy()\n"));

	PropertyMap::const_iterator cit = props.find("dbusAddress");
	UT_return_val_if_fail(cit != props.end(), 0);
	UT_return_val_if_fail(cit->second.size() > 0, 0);

	UT_DEBUGMSG(("Constructing SugarBuddy (dbusAddress: %s)\n", cit->second.c_str()));
	// we're lazy, so we'll reuse the dbus address for the buddy's name
	// NOTE: the buddy name must uniquely identify a buddy, and I can't
	// guarantee at the moment that the name we could get from the sugar
	// presence framework would always be unique to one buddy; hence the
	// dbus address will do for now
	return new SugarBuddy(this, cit->second.c_str(), cit->second.c_str());
}

void  SugarAccountHandler::handleEvent(Session& pSession)
{
	// TODO: implement me
}

void SugarAccountHandler::signal(const Event& event, const Buddy* pSource)
{
	AccountHandler::signal(event, pSource);

	UT_DEBUGMSG(("SugarAccountHandler::signal()\n"));

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_if_fail(pManager);

	// we just want to listen for when we get a document handle from the other side
	// (this obviously only makes sense for a joining party, not an offering one;
	// the offering party should never even receive such an event
	if (event.getClassType() == PCT_AccountBuddyAddDocumentEvent)
	{
		UT_DEBUGMSG(("We received a document handle from an offering party; let's join it immediately!\n"));
		AccountBuddyAddDocumentEvent& abade = (AccountBuddyAddDocumentEvent&)event;

		if (!m_bLocallyControlled)
		{
			DocHandle* pDocHandle = abade.getDocHandle();
			if (pDocHandle)
			{
				UT_DEBUGMSG(("Got dochandle, going to initiate a join on it!\n"));
				// FIXME: remove const cast
				pManager->joinSessionInitiate(const_cast<Buddy*>(pSource), pDocHandle);
			}
			else
				UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
		}
		else
			UT_ASSERT_HARMLESS(UT_NOT_REACHED);
	}
}

bool SugarAccountHandler::send(const Packet* pPacket)
{
	UT_DEBUGMSG(("SugarAccountHandler::send(const Packet* pPacket)\n"));
	UT_return_val_if_fail(pPacket, false);
	UT_return_val_if_fail(m_pTube, false);

	// TODO: implement me

	return true;
}

bool SugarAccountHandler::send(const Packet* pPacket, const Buddy& buddy)
{
	UT_DEBUGMSG(("SugarAccountHandler::send(const Packet* pPacket, const Buddy& buddy)\n"));
	UT_return_val_if_fail(pPacket, false);
	UT_return_val_if_fail(m_pTube, false);
	
	SugarBuddy& sugarBuddy = (SugarBuddy&)buddy;
	UT_DEBUGMSG(("Sending packet to sugar buddy on dbus addess: %s\n", sugarBuddy.getDBusName().utf8_str()));

	DBusMessage* pMessage = dbus_message_new_method_call(sugarBuddy.getDBusName().utf8_str(), "/org/laptop/Sugar/Presence/Buddies", INTERFACE, SEND_ONE_METHOD);
	// TODO: check dst
	bool dst = dbus_message_set_destination(pMessage, sugarBuddy.getDBusName().utf8_str());
	UT_DEBUGMSG(("Destination (%s) set on message\n", sugarBuddy.getDBusName().utf8_str()));
	
	// make to-be-send-stream once
	std::string data;
	_createPacketStream( data, pPacket );

	const char* packet_contents = data.c_str();
	
	bool append = dbus_message_append_args(pMessage,
					DBUS_TYPE_STRING, &packet_contents,
					DBUS_TYPE_INVALID);
	UT_DEBUGMSG(("Appended packet contents\n"));

	bool sent = dbus_connection_send(m_pTube, pMessage, NULL);
	UT_ASSERT_HARMLESS(sent);
	if (sent)
		dbus_connection_flush(m_pTube);
	dbus_message_unref(pMessage);
	return sent;
}

void SugarAccountHandler::_registerEditMethods()
{
	UT_DEBUGMSG(("SugarAccountHandler::_registerEditMethods()\n"));

    // First we need to get a pointer to the application itself.
    XAP_App *pApp = XAP_App::getApp();
    EV_EditMethodContainer* pEMC = pApp->getEditMethodContainer();

	EV_EditMethod *emOfferTube = new EV_EditMethod (
		"com.abisource.abiword.abicollab.olpc.offerTube",     // name of callback function
		s_offerTube,       // callback function itself.
		0,                      // no additional data required.
		""                      // description -- allegedly never used for anything
	);
	pEMC->addEditMethod(emOfferTube);

	EV_EditMethod *emJoinTube = new EV_EditMethod (
		"com.abisource.abiword.abicollab.olpc.joinTube",     // name of callback function
		s_joinTube,       // callback function itself.
		0,                      // no additional data required.
		""                      // description -- allegedly never used for anything
	);
	pEMC->addEditMethod(emJoinTube);

	EV_EditMethod *emBuddyJoined = new EV_EditMethod (
		"com.abisource.abiword.abicollab.olpc.buddyJoined",     // name of callback function
		s_buddyJoined,       // callback function itself.
		0,                      // no additional data required.
		""                      // description -- allegedly never used for anything
	);
	pEMC->addEditMethod(emBuddyJoined);

	EV_EditMethod *emBuddyLeft = new EV_EditMethod (
		"com.abisource.abiword.abicollab.olpc.buddyLeft",     // name of callback function
		s_buddyLeft,       // callback function itself.
		0,                      // no additional data required.
		""                      // description -- allegedly never used for anything
	);
	pEMC->addEditMethod(emBuddyLeft);

}

bool SugarAccountHandler::offerTube(FV_View* pView, const UT_UTF8String& tubeDBusAddress)
{
	UT_DEBUGMSG(("SugarAccountHandler::offerTube()\n"));
	UT_return_val_if_fail(pView, false);

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_val_if_fail(pManager, false);

	// TODO: check that we aren't already in a session; this backend can only host one session at a time (for now)

	PD_Document * pDoc = pView->getDocument();
	UT_return_val_if_fail(pDoc, false);

	UT_DEBUGMSG(("Got tube address: %s\n", tubeDBusAddress.utf8_str()));

	DBusError error;
	m_pTube = dbus_connection_open(tubeDBusAddress.utf8_str(), NULL);
	if (m_pTube)
	{
		UT_DEBUGMSG(("Opened a dbus connection for tube: %s\n", tubeDBusAddress.utf8_str()));

		UT_DEBUGMSG(("Adding dbus handlers to the main loop\n"));
		dbus_connection_setup_with_g_main(m_pTube, NULL);

		UT_DEBUGMSG(("Adding message filter\n"));
		dbus_connection_add_filter(m_pTube, s_dbus_handle_message, this, NULL);

		m_bLocallyControlled = true;

		// we are "connected" now, time to start sending out, and listening to messages (such as events)
		pManager->registerEventListener(this);
		// start hosting a session on the current document
		pManager->startSession(pDoc);
		return true;
	}
	else
		UT_DEBUGMSG(("Failed to open a dbus connection for tube: %s\n", tubeDBusAddress.utf8_str()));

	return false;
}

bool SugarAccountHandler::joinTube(FV_View* pView, const UT_UTF8String& tubeDBusAddress)
{
	UT_DEBUGMSG(("SugarAccountHandler::joinTube()\n"));
	UT_return_val_if_fail(pView, false);

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_val_if_fail(pManager, false);

	// TODO: check that we aren't already in a session; this backend can only join one session at a time (for now)

	DBusError error;
	m_pTube = dbus_connection_open(tubeDBusAddress.utf8_str(), NULL);
	if (m_pTube)
	{
		UT_DEBUGMSG(("Opened a dbus connection for tube: %s\n", tubeDBusAddress.utf8_str()));

		UT_DEBUGMSG(("Adding dbus handlers to the main loop\n"));
		dbus_connection_setup_with_g_main(m_pTube, NULL);

		UT_DEBUGMSG(("Adding message filter\n"));
		dbus_connection_add_filter(m_pTube, s_dbus_handle_message, this, NULL);

		m_bLocallyControlled = false;

		// we are "connected" now, time to start sending out, and listening to messages (such as events)
		pManager->registerEventListener(this);
	}
	else
		UT_DEBUGMSG(("Failed to open a dbus connection for tube: %s\n", tubeDBusAddress.utf8_str()));

	return false;
}

bool SugarAccountHandler::joinBuddy(FV_View* pView, const UT_UTF8String& buddyDBusAddress)
{
	UT_DEBUGMSG(("SugarAccountHandler::joinBuddy()\n"));
	UT_return_val_if_fail(pView, false);

	Buddy* pBuddy = new SugarBuddy(this, buddyDBusAddress, buddyDBusAddress);
	addBuddy(pBuddy);

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_val_if_fail(pManager, false);

	PD_Document * pDoc = pView->getDocument();
	UT_return_val_if_fail(pDoc, false);

	if (m_bLocallyControlled)
	{
		AbiCollab* pSession = pManager->getSession(pDoc);
		UT_return_val_if_fail(pSession, false);
		pSession->addCollaborator(pBuddy);
		return true;
	}
	else
	{
		UT_DEBUGMSG(("Buddy joined, while we are NOT hosting a session; requesting sessions buddy: %s\n", pBuddy->getName().utf8_str()));
		getSessionsAsync(*pBuddy);
		return true;
	}

	return false;
}

bool SugarAccountHandler::disjoinBuddy(FV_View* pView, const UT_UTF8String& buddyDBusAddress)
{
	UT_DEBUGMSG(("SugarAccountHandler::disjoinBuddy()\n"));
	UT_return_val_if_fail(pView, false);

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_val_if_fail(pManager, false);

	PD_Document * pDoc = pView->getDocument();
	UT_return_val_if_fail(pDoc, false);

 	if (m_bLocallyControlled)
	{
		UT_DEBUGMSG(("Dropping buddy %s from the session!", buddyDBusAddress.utf8_str()));
		AbiCollab* pSession = pManager->getSessionFromDocumentId(pDoc->getDocUUIDString());
		if (pSession)
		{	
			Buddy* pTmpBuddy = new SugarBuddy(this, buddyDBusAddress, buddyDBusAddress);
			pSession->removeCollaborator(pTmpBuddy);
			DELETEP(pTmpBuddy);
			return true;
		}
	}
	else
	{
		UT_DEBUGMSG(("The session owner (%s) left!", buddyDBusAddress.utf8_str()));
		UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
		return true;
	}

	return false;
}

static bool s_offerTube(AV_View* v, EV_EditMethodCallData *d)
{
	UT_DEBUGMSG(("s_offerTube()\n"));
	UT_return_val_if_fail(v, false);
	UT_return_val_if_fail(d && d->m_pData && d->m_dataLength > 0, false);

	FV_View* pView = static_cast<FV_View *>(v);
	UT_UTF8String tubeDBusAddress(d->m_pData, d->m_dataLength);

	SugarAccountHandler* pHandler = SugarAccountHandler::getHandler();
	UT_return_val_if_fail(pHandler, false);
	return pHandler->offerTube(pView, tubeDBusAddress);
}

static bool s_joinTube(AV_View* v, EV_EditMethodCallData *d)
{
	UT_DEBUGMSG(("s_joinTube()\n"));
	UT_return_val_if_fail(v, false);
	UT_return_val_if_fail(d && d->m_pData && d->m_dataLength > 0, false);

	FV_View* pView = static_cast<FV_View *>(v);
	UT_UTF8String tubeDBusAddress(d->m_pData, d->m_dataLength);
	UT_DEBUGMSG(("Got tube address: %s\n", tubeDBusAddress.utf8_str()));

	SugarAccountHandler* pHandler = SugarAccountHandler::getHandler();
	UT_return_val_if_fail(pHandler, false);
	return pHandler->joinTube(pView, tubeDBusAddress);
}

static bool s_buddyJoined(AV_View* v, EV_EditMethodCallData *d)
{
	UT_DEBUGMSG(("s_buddyJoined()\n"));
	UT_return_val_if_fail(SugarAccountHandler::getHandler(), false);
	UT_return_val_if_fail(d && d->m_pData && d->m_dataLength > 0, false);

	FV_View* pView = static_cast<FV_View *>(v);
	UT_UTF8String buddyPath(d->m_pData, d->m_dataLength);
	UT_DEBUGMSG(("Adding buddy with dbus path: %s\n", buddyPath.utf8_str()));

	SugarAccountHandler* pHandler = SugarAccountHandler::getHandler();
	UT_return_val_if_fail(pHandler, false);
	return pHandler->joinBuddy(pView, buddyPath);
}

static bool s_buddyLeft(AV_View* v, EV_EditMethodCallData *d)
{
	UT_DEBUGMSG(("s_buddyLeft()\n"));
	UT_return_val_if_fail(SugarAccountHandler::getHandler(), false);
	UT_return_val_if_fail(d && d->m_pData && d->m_dataLength > 0, false);

	FV_View* pView = static_cast<FV_View *>(v);
	UT_UTF8String buddyPath(d->m_pData, d->m_dataLength);
	UT_DEBUGMSG(("Removing buddy with dbus path %s\n", buddyPath.utf8_str()));

	AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager();
	UT_return_val_if_fail(pManager, false);

	SugarAccountHandler* pHandler = SugarAccountHandler::getHandler();
	UT_return_val_if_fail(pHandler, false);

	if (pHandler->isLocallyControlled())
	{
		return pHandler->disjoinBuddy(pView, buddyPath);
	}
	else
	{
		// not much we can do here, but cry; kill off the entire handler
		pManager->destroyAccount(pHandler);
		return true;
	}
}

DBusHandlerResult s_dbus_handle_message(DBusConnection *connection, DBusMessage *message, void *user_data)
{
	UT_DEBUGMSG(("s_dbus_handle_message()\n"));
	UT_return_val_if_fail(connection, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
	UT_return_val_if_fail(message, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
	UT_return_val_if_fail(user_data, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
	SugarAccountHandler* pHandler = reinterpret_cast<SugarAccountHandler*>(user_data);

	if (dbus_message_is_method_call(message, INTERFACE, SEND_ONE_METHOD))
	{
		UT_DEBUGMSG(("%s message accepted!\n", SEND_ONE_METHOD));

		const char* senderDBusAddress = dbus_message_get_sender(message);

		DBusError error;
		dbus_error_init (&error);
		char* packet;
	    if (dbus_message_get_args(message, &error, 
					DBUS_TYPE_STRING, &packet, 
					DBUS_TYPE_INVALID))
		{
			UT_DEBUGMSG(("Received packet from %s: >%s<\n", senderDBusAddress, packet));

			// import the packet
			RawPacket rp;
			// TODO: fix this ugly construct, as the buddy is never freed!
			SugarBuddy* pSender = new SugarBuddy(pHandler, senderDBusAddress, senderDBusAddress);
			rp.buddy = pSender;
			rp.packet = packet;
			pHandler->handleMessage(rp);

			//dbus_free(packet);
			return DBUS_HANDLER_RESULT_HANDLED;
		}
		else
			UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
	}	

	UT_DEBUGMSG(("Unhandled message\n"));
	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}