/*
    kopeteproperties.h - Kopete Properties

    Copyright (c) 2004      by Richard Smith         <kde@metafoo.co.uk>
    Kopete    (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * 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 of the License, or (at your option) any later version.      *
    *                                                                       *
    *************************************************************************
*/

#ifndef KOPETEPROPERTIES_H
#define KOPETEPROPERTIES_H

#include <QMultiHash>

#include <typeinfo>

class QString;
class QVariant;
class QDomElement;

namespace Kopete
{

/**
 * Contains the classes forming Kopete's Properties system.
 *
 * @todo Explain more, give examples.
 *
 * @author Richard Smith <kde@metafoo.co.uk>
 */
namespace Properties
{

//BEGIN core functionality

/**
 * @brief Property-type-independent base class for properties
 *
 * The base class for all properties of any type which can be set or got for @p Parent
 * objects. It is rare to need to use this class directly. Usually you will want to use
 * the @ref Property derived class, or dynamic_cast the PropertyBase object to another interface.
 *
 * @see Property UserVisible XMLSerializable StringSerializable
 *
 * @author Richard Smith <kde@metafoo.co.uk>
 */
template<class Parent>
class PropertyBase
{
public:
	/**
	 * Returns the name of the property. This name should uniquely identify this property
	 * within the type Parent, and will be used for persistently identifying this property.
	 *
	 * For core properties, the chosen name should not contain any slash characters. For
	 * properties defined in plugins kept in Kopete's CVS, the name should be of the form
	 * pluginName/propertyName. For third-party plugins, please use a URL with a host which
	 * you own, such as "http://my-host.com/kopete/properties/groupId".
	 *
	 * @return the name of this property.
	 */
	virtual const QByteArray &name() const = 0;
};

/**
 * @brief Property-type-dependent base class for properties
 *
 * This class represents a property of type @p Type applicable to @p Parent objects. Usage
 * of this class is usually as simple as:
 *
 * \code
 * SomeParent *propertyContainer = ...
 * Property<SomeParent,QString> &myProperty = ...
 * QString value = propertyContainer->property(myProperty);
 * propertyContainer->setProperty(myProperty, "hello");
 * \endcode
 *
 * You should never need to call functions in this class directly.
 */
template<class Parent, typename Type>
class Property : public PropertyBase<Parent>
{
public:
	/**
	 * Returns the value of this property in the object @p parent.
	 */
	virtual Type get( const Parent *parent ) const = 0;
	/**
	 * Sets the value of this property in the object @p parent.
	 */
	virtual void set( Parent *, const Type & ) const = 0;
};

/**
 * @brief Base class for property data objects
 *
 * Some property objects want to store property-specific data in their parent objects.
 * To support that, subclasses of this class are permitted to be stored. Once passed
 * to the @ref PropertyStorage object via @ref PropertyStorage::setCustomPropertyData,
 * the @ref PropertyStorage object owns the PropertyData, and will delete it when it
 * is no longer needed.
 */
struct PropertyData
{
	virtual ~PropertyData() {}
};

/**
 * @brief Storage object for PropertyData objects
 *
 * This class is responsible for storing PropertyData-derived data objects for properties.
 * This is the non-templated part of the @ref WithProperties class, split out into its own
 * class to eliminate the template bloat.
 */
class PropertyStorage
{
	typedef QMultiHash<QByteArray, PropertyData*> PropertyDict;
	// setCustomPropertyData can be called on a const object, allowing the
	// guarantee that DataProperty::data() never returns 0.
	mutable PropertyDict _storage;

public:
	PropertyStorage() {}
	~PropertyStorage()
	{
		qDeleteAll(_storage);
	}

	/**
	 * Sets the stored property data with name @p name to be @p data.
	 *
	 * @note The @p name argument should usually be the name of the property which the data
	 * is being stored for. However, if properties wish to share data, they may choose to
	 * name their custom data differently. Names are bound by the same rules as are laid out
	 * for naming properties in PropertyBase<Parent>::name.
	 */
	void setCustomPropertyData( const QByteArray &name, PropertyData *data ) const { _storage.replace( name, data ); }

	/**
	 * Gets the stored property data with name @p name. Returns a null
	 * pointer if no data has been stored for that property.
	 */
	PropertyData *getCustomPropertyData( const QByteArray &name ) const { return _storage.value(name); }
};

/**
 * @brief Base class for classes to which properties can be applied
 *
 * This class provides support for properties to another class. If you want your class
 * to support properties, derive from this passing your class as the Parent parameter:
 *
 * \code
 * class YourClass : public WithProperties<YourClass> { ... };
 * \endcode
 *
 * You will also need to explicitly specialise the propertyCreated() member function to
 * load property data upon creation of a new property object.
 */
template<class Parent>
class WithProperties : public PropertyStorage
{
public:
	/**
	 * Get the value of property @p prop in this object.
	 * @param prop the Property object representing the property to get
	 */
	template<typename T>
	T property( Property<Parent,T> const &prop ) { return prop.get( static_cast<Parent*>(this) ); }
	/**
	 * Set the value of property @p prop in this object.
	 * @param prop the Property object representing the property to get
	 * @param value the value to set the property to
	 */
	template<typename T>
	void setProperty( Property<Parent,T> const &prop, const T &value ) { prop.set( static_cast<Parent*>(this), value ); }

	/**
	 * Called when a property is created; loads the Parent object's data into the property.
	 *
	 * @note Derived classes must explicitly specialize this to load the property's data into
	 *       every object of this type.
	 */
	static void propertyCreated( const PropertyBase<Parent> &property );
};

//END core functionality

//BEGIN interfaces

/**
 * @brief An interface for user-visible properties
 * @todo document
 */
template<class Parent>
struct UserVisible
{
	virtual QString userText( Parent * ) = 0;
	virtual QString label() = 0;
	virtual QString icon() = 0;
};

/**
 * @brief An interface for properties which can be serialized as XML
 * @todo document
 */
template<class Parent>
struct XMLSerializable
{
	virtual void fromXML( Parent *, const QDomElement & ) = 0;
	virtual void toXML( const Parent *, QDomElement & ) = 0;
};

/**
 * @brief An interface for properties which can be serialized as strings
 * @todo document
 */
template<class Parent>
struct StringSerializable
{
	virtual void fromString( Parent *, const QString & ) = 0;
	virtual QString toString( const Parent * ) = 0;
};

//END interfaces

//BEGIN convenience classes

/**
 * @internal Display a warning message when the wrong type of property data is found
 */
void customPropertyDataIncorrectType( const char *name, const std::type_info &found, const std::type_info &expected );

/**
 * @brief Convenience implementation of a Property that stores PropertyData
 *
 * A property for objects of type @p Parent, that stores data in the class @p Data.
 * @p Data must be derived from @ref PropertyBase, or your code will not compile.
 */
template<class Parent, typename Type, class Data>
class DataProperty : public Property<Parent,Type>
{
public:
	Data *data( const Parent *c ) const
	{
		PropertyData *pd = c->getCustomPropertyData( this->name() );
		Data *data = dynamic_cast<Data*>(pd);
		if ( !data )
		{
			if ( pd )
				customPropertyDataIncorrectType( this->name(), typeid(*pd), typeid(Data) );
			data = new Data;
			c->setCustomPropertyData( this->name(), data );
		}
		return data;
	}
};

/**
 * @brief Convenience implementation of a PropertyData subclass which stores a single datum
 *
 * If a @ref Property needs to store only a single value in an object, using this
 * class is simpler than deriving from @ref PropertyData yourself. The value will
 * be default-constructed (which means for numeric types and pointers it will be
 * set to 0).
 */
template<typename T>
struct SimplePropertyData : public PropertyData
{
	SimplePropertyData() : value() {}
	T value;
};

/**
 * @brief Convenience implementation of a Property which stores a single datum as PropertyData
 *
 * This convenience class implements the @ref Property interface by simply storing and
 * retrieving the datum from PropertyData. This class does not provide any serialization
 * of the data.
 *
 * @note You will need to derive from this class to use it; the @ref name function is
 * still pure virtual.
 */
template<class Parent, typename Type>
class SimpleDataProperty : public DataProperty<Parent,Type,SimplePropertyData<Type> >
{
public:
	Type get( const Parent *p ) const { return data(p)->value; }
	void set( Parent *p, const Type &v ) const { data(p)->value = v; }
};

/**
 * Move somewhere else
 * @{
 */

/**
 * Explicitly specialized for all types QVariant supports
 */
template<class T> T variantTo(QVariant);

QVariant variantFromXML(const QDomElement&);
void variantToXML(QVariant v, QDomElement &);

/**
 * @}
 */

/**
 * @brief Convenience implementation of XMLSerializable in terms of QVariants
 *
 * This class provides XML serialization for data that can be stored in a QVariant. You
 * will need to multiply-inherit from this class and (usually indirectly) from @ref Property.
 *
 * You can combine this class with other convenience classes such as SimpleDataProperty
 * like this:
 *
 * \code
 * class ContactNickNameProperty
 *     : public SimpleDataProperty<Contact,QString>
 *     , XMLProperty<ContactNickNameProperty,Contact,QString>
 * {
 * public:
 *   const char *name() const { return "nickName"; }
 * };
 * \endcode
 */
template<class Derived, class Parent, typename Type>
class XMLProperty : public XMLSerializable<Parent>
{
public:
	void fromXML( Parent *t, const QDomElement &e )
	{
		static_cast<Derived*>(this)->set(t, variantTo<Type>(variantFromXML(e)));
	}
	void toXML( const Parent *t, QDomElement &e )
	{
		variantToXML(QVariant(static_cast<Derived*>(this)->get(t)),e);
	}
};

//END convenience classes

} // namespace Properties

} // namespace Kopete

#endif
