/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "marshalling.h"
#include <QDBusMetaType>
#include <QDBusConnection>
#include <QIcon>
#include <QtDebug>
#include <interfaces/core/icoreproxy.h>
#include "coreproxyserverwrapper.h"
#include "coreproxyproxy.h"

QDBusArgument& operator<< (QDBusArgument& arg,
		const LC::DBus::ObjectManager::ObjectDataInfo& info)
{
	arg.beginStructure ();
	arg << info.Path_ << info.Service_;
	arg.endStructure ();
	return arg;
}

const QDBusArgument& operator>> (const QDBusArgument& arg,
		LC::DBus::ObjectManager::ObjectDataInfo& info)
{
	arg.beginStructure ();
	arg >> info.Path_ >> info.Service_;
	arg.endStructure ();
	return arg;
}

QDBusArgument& operator<< (QDBusArgument& arg, const ICoreProxy_ptr& proxy)
{
	return arg << LC::DBus::ObjectManager::Instance ().RegisterObject (proxy);
}

const QDBusArgument& operator>> (const QDBusArgument& arg, ICoreProxy_ptr& proxy)
{
	LC::DBus::ObjectManager::ObjectDataInfo info;
	arg >> info;
	LC::DBus::ObjectManager::Instance ().Wrap (proxy, info);
	return arg;
}

QDBusArgument& operator<< (QDBusArgument& arg, const QIcon& icon)
{
	QByteArray ba;
	{
		QDataStream ostr (&ba, QIODevice::WriteOnly);
		ostr << icon;
	}
	arg.beginStructure ();
	arg << ba;
	arg.endStructure ();
	return arg;
}

const QDBusArgument& operator>> (const QDBusArgument& arg, QIcon& icon)
{
	arg.beginStructure ();
	QByteArray ba;
	arg >> ba;
	arg.endStructure ();

	{
		QDataStream istr (ba);
		istr >> icon;
	}

	return arg;
}

namespace LC
{
namespace DBus
{
	namespace
	{
		template<typename T>
		struct AdaptorCreator;

		template<>
		struct AdaptorCreator<ICoreProxy>
		{
			static void Create (ICoreProxy *w, QObject *wObj)
			{
				new CoreProxyServerWrapper (w, wObj);
			}
		};

		template<typename T>
		struct ProxyCreator;

		template<>
		struct ProxyCreator<ICoreProxy>
		{
			static ICoreProxy* Create (const ObjectManager::ObjectDataInfo& info)
			{
				return new CoreProxyProxy { info.Service_, info.Path_ };
			}
		};
	}

	void RegisterTypes ()
	{
		qDBusRegisterMetaType<ICoreProxy_ptr> ();
		qDBusRegisterMetaType<QIcon> ();
	}

	ObjectManager::ObjectManager ()
	: Counter_ (0)
	{
	}

	ObjectManager& ObjectManager::Instance ()
	{
		static ObjectManager m;
		return m;
	}

	template<typename T>
	auto ObjectManager::RegisterObject (std::shared_ptr<T> obj) -> ObjectDataInfo
	{
		if (!obj)
			return {};

		return RegisterObject (obj.get ());
	}

	template<typename T>
	auto ObjectManager::RegisterObject (T *t) -> ObjectDataInfo
	{
		const auto qobj = dynamic_cast<QObject*> (t);
		if (!qobj)
		{
			qWarning () << Q_FUNC_INFO
					<< "unable to cast the type to QObject*";
			return {};
		}

		if (Registered_.contains (qobj))
			return Registered_.value (qobj);

		connect (qobj,
				SIGNAL (destroyed (QObject*)),
				this,
				SLOT (handleObjectDestroyed (QObject*)));

		AdaptorCreator<T>::Create (t, qobj);

		const QString path { "/org/LeechCraft/Object_" + QString::number (Counter_++) };
		QDBusConnection::sessionBus ().registerObject (path,
				qobj, QDBusConnection::ExportAllContents);

		ObjectDataInfo info
		{
			"org.LeechCraft.MainInstance",
			QDBusObjectPath { path }
		};

		Registered_ [qobj] = info;

		return info;
	}

	template<typename T>
	void ObjectManager::Wrap (std::shared_ptr<T>& obj, const ObjectDataInfo& info)
	{
		T *rawObj;
		Wrap (rawObj, info);
		obj.reset (rawObj);
	}

	template<typename T>
	void ObjectManager::Wrap (T*& obj, const ObjectDataInfo& info)
	{
		obj = ProxyCreator<T>::Create (info);
	}

	void ObjectManager::handleObjectDestroyed (QObject *obj)
	{
		Registered_.remove (obj);
	}
}
}
