#ifndef QTJSONSERIALIZER_TYPECONVERTER_H
#define QTJSONSERIALIZER_TYPECONVERTER_H

#include "qtjsonserializer_global.h"

#include <type_traits>
#include <limits>

#include <QtCore/qmetatype.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qcborvalue.h>
#include <QtCore/qvariant.h>
#include <QtCore/qsharedpointer.h>

namespace QtJsonSerializer {

//! Interface to extract data from any generic container
class Q_JSONSERIALIZER_EXPORT TypeExtractor
{
	Q_DISABLE_COPY(TypeExtractor)

public:
	TypeExtractor();
	virtual ~TypeExtractor();

	//! Returns the identifier of the general type this extractor is for
	virtual QByteArray baseType() const = 0;
	//! Returns an ordered list of subtypes found in types handled by this extractor
	virtual QList<int> subtypes() const = 0;
	//! Extracts the data of a value of the extractors type at the given index
	virtual QVariant extract(const QVariant &value, int index = -1) const = 0;
	//! Emplaces the value into the target of the extractors type at the given index
	virtual void emplace(QVariant &target, const QVariant &value, int index = -1) const = 0;
};

class TypeConverterPrivate;
//! An interface to create custom serializer type converters
class Q_JSONSERIALIZER_EXPORT TypeConverter
{
	Q_DISABLE_COPY(TypeConverter)
public:
	//! A placeholder tag to be used if no tag is expected/allowed/given
	static constexpr auto NoTag = static_cast<QCborTag>(std::numeric_limits<std::underlying_type_t<QCborTag>>::max());

	//! Sample values for a priority value (default converters are mostly Standard and are guaranteed to be between Low and High)
	enum Priority : int {
		ExtremlyLow = -0x00FFFFFF,
		VeryLow = -0x0000FFFF,
		Low = -0x000000FF,
		Standard = 0x00000000,
		High = 0x000000FF,
		VeryHigh = 0x0000FFFF,
		ExtremlyHigh = 0x00FFFFFF
	};

	//! The possible results of canDeserialize(), used internally only
	enum DeserializationCapabilityResult : int {
		Positive = 1, //!< The converter can deserialize the given data
		Guessed = 2, //!< The converter guessed a type for the given data
		Negative = -1, //!< The converter cannot deserialize the given data
		WrongTag = -2 //!< The converter could deserialize the given data, but the tag does not match
	};

	//! Helper class passed to the type converter by the serializer. Do not implement yourself
	class Q_JSONSERIALIZER_EXPORT SerializationHelper
	{
		Q_DISABLE_COPY(SerializationHelper)
	public:
		SerializationHelper();
		virtual ~SerializationHelper();

		//! Returns true, if de/serializing to JSON, and false for CBOR
		virtual bool jsonMode() const = 0;
		//! Returns a property from the serializer
		virtual QVariant getProperty(const char *name) const = 0;
		//! Returns a tag registered for the given metaTypeId
		virtual QCborTag typeTag(int metaTypeId) const = 0;
		//! Returns a reference to an extractor for the given type, or nullptr
		virtual QSharedPointer<const TypeExtractor> extractor(int metaTypeId) const = 0;

		//! Serialize a subvalue, represented by a meta property
		virtual QCborValue serializeSubtype(const QMetaProperty &property, const QVariant &value) const = 0;
		//! Serialize a subvalue, represented by a type id
		virtual QCborValue serializeSubtype(int propertyType, const QVariant &value, const QByteArray &traceHint = {}) const = 0;
		//! Deserialize a subvalue, represented by a meta property
		virtual QVariant deserializeSubtype(const QMetaProperty &property, const QCborValue &value, QObject *parent) const = 0;
		//! Deserialize a subvalue, represented by a type id
		virtual QVariant deserializeSubtype(int propertyType, const QCborValue &value, QObject *parent, const QByteArray &traceHint = {}) const = 0;
	};

	//! Constructor
	TypeConverter();
	//! Destructor
	virtual ~TypeConverter();

	//! The name of the converter. Used for debugging purpose
	virtual QByteArray name() const = 0;

	//! Returns the priority of this converter
	int priority() const;
	//! Sets the priority of this converter
	void setPriority(int priority);

	//! Returns the helper associated with this converter
	const SerializationHelper *helper() const;
	//! @private
	void setHelper(const SerializationHelper *helper);

	//! Returns true, if this implementation can convert the given type
	virtual bool canConvert(int metaTypeId) const = 0;
	//! Returns a list of allowed tags for the given type
	virtual QList<QCborTag> allowedCborTags(int metaTypeId) const;
	//! Returns a list of allowed types for the given type and tag
	virtual QList<QCborValue::Type> allowedCborTypes(int metaTypeId, QCborTag tag) const = 0;
	//! Returns a type guessed from the tag and data, that is supported by the converter
	virtual int guessType(QCborTag tag, QCborValue::Type dataType) const;
	//! @private
	DeserializationCapabilityResult canDeserialize(int &metaTypeId,
												   QCborTag tag,
												   QCborValue::Type dataType) const;

	//! Called by the serializer to serializer your given type to CBOR
	virtual QCborValue serialize(int propertyType, const QVariant &value) const = 0;
	//! Called by the serializer to deserializer your given type from CBOR
	virtual QVariant deserializeCbor(int propertyType, const QCborValue &value, QObject *parent) const = 0;
	//! Called by the serializer to deserializer your given type from JSON
	virtual QVariant deserializeJson(int propertyType, const QCborValue &value, QObject *parent) const;

private:
	QScopedPointer<TypeConverterPrivate> d;

	void mapTypesToJson(QList<QCborValue::Type> &typeList) const;
};

//! Macro to implement the TypeConverter::name method in a subclass
#define QT_JSONSERIALIZER_TYPECONVERTER_NAME(className) inline QByteArray name() const override { \
	static_assert(std::is_same_v<className, std::decay_t<decltype(*this)>>); \
	return QByteArrayLiteral(#className); \
}

//! A factory interface to create instances of QJsonTypeConverters
class Q_JSONSERIALIZER_EXPORT TypeConverterFactory
{
	Q_DISABLE_COPY(TypeConverterFactory)

public:
	TypeConverterFactory();
	virtual ~TypeConverterFactory();

	//! The primary factory method to create converters
	virtual QSharedPointer<TypeConverter> createConverter() const = 0;
};

//! A template implementation of TypeConverterFactory to generically create simply converters
template <typename TConverter, int OverwritePriority = TypeConverter::Priority::Standard>
class TypeConverterStandardFactory : public TypeConverterFactory
{
public:
	QSharedPointer<TypeConverter> createConverter() const override;
};

// ------------- GENERIC IMPLEMENTATION -------------

template<typename TConverter, int OverwritePriority>
QSharedPointer<TypeConverter> TypeConverterStandardFactory<TConverter, OverwritePriority>::createConverter() const
{
	auto converter = QSharedPointer<TConverter>::create();
	if (OverwritePriority != TypeConverter::Priority::Standard)
		converter->setPriority(OverwritePriority);
	return converter;
}

}

#endif // QTJSONSERIALIZER_TYPECONVERTER_H
