#ifndef QTJSONSERIALIZER_CBORSERIALIZER_H
#define QTJSONSERIALIZER_CBORSERIALIZER_H

#include "qtjsonserializer_global.h"

#include "jsonserializer/serializerbase.h"

namespace QtJsonSerializer {

class CborSerializerPrivate;
//! A class to serialize and deserialize c++ classes to and from CBOR
class Q_JSONSERIALIZER_EXPORT CborSerializer : public SerializerBase
{
	Q_OBJECT

	//! If enabled, specially tagged number types will be automatically deserialized to their type
	Q_PROPERTY(bool handleSpecialNumbers READ handleSpecialNumbers WRITE setHandleSpecialNumbers NOTIFY handleSpecialNumbersChanged)

public:
	//! Additional official CBOR-Tags, taken from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
	enum ExtendedTags : std::underlying_type_t<QCborTag> {
		GenericObject = 27, //!< Serialised language-independent object with type name and constructor arguments
		RationaleNumber = 30, //!< Rational number
		Identifier = 39, //!< Identifier
		Homogeneous = 41, //!< Homogeneous Array
		Set = 258, //!< Mathematical finite set
		ExplicitMap = 259, //!< Map datatype with key-value operations (e.g. `.get()/.set()/.delete()`)
		NetworkAddress = 260, //!< Network Address (IPv4 or IPv6 or MAC Address)
		NetworkAddressPrefix = 261, //!< Network Address Prefix (IPv4 or IPv6 Address + Mask Length)
	};
	Q_ENUM(ExtendedTags)

	// Additional unofficial CBOR-Tags, as defined by this library to tag Qt types for better typesafety
	enum CustomTags : std::underlying_type_t<QCborTag> {
		Color = 10000, //!< Tag used for QColor
		Font = 10001, //!< Tag used for QFont
		Enum = 10002, //!< Tag used for enums
		Flags = 10003, //!< Tag used for flags
		ConstructedObject = 10004, //!< Tag used for constructed object (GenericObject + standard object)
		Pair = 10005, //!< Tag used for QPair/std::pair
		MultiMap = 10006, //!< Tag used for QMultiMap/QMultiHash
		VersionNumber = 10007, //!< Tag used for QVersionNumber
		Tuple = 10008, //!< Tag used for std::tuple
		BitArray = 10009, //!< Tag used for QBitArray
		Date = 10010, //!< Tag used for QDate (short ISO format)
		Time = 10011, //!< Tag used for QTime (short ISO format)

		LocaleISO = 10100, //!< Tag used for QLocale, encoded via the ISO format
		LocaleBCP47 = 10101, //!< Tag used for QLocale, encoded via the BCP47 format

		GeomSize = 10110, //!< Tag used for QSize/QSizeF
		GeomPoint = 10111, //!< Tag used for QPoint/QPointF
		GeomLine = 10112, //!< Tag used for QLine/QLineF
		GeomRect = 10113, //!< Tag used for QRect/QRectF

		ChronoNanoSeconds = 10120, //!< Tag used for std::chrono::nanoseconds
		ChronoMicroSeconds = 10121, //!< Tag used for std::chrono::microseconds
		ChronoMilliSeconds = 10122, //!< Tag used for std::chrono::milliseconds
		ChronoSeconds = 10123, //!< Tag used for std::chrono::seconds
		ChronoMinutes = 10124, //!< Tag used for std::chrono::minutes
		ChronoHours = 10125, //!< Tag used for std::chrono::hours

		NoTag = std::numeric_limits<std::underlying_type_t<QCborTag>>::max() //!< Used as placeholder for setTypeTag to not change the tag
	};
	Q_ENUM(CustomTags)

	//! Default constructor
	explicit CborSerializer(QObject *parent = nullptr);

	//! @readAcFn{CborSerializer::handleSpecialNumbers}
	bool handleSpecialNumbers() const;

	//! Set a tag to always be used when serializing the given type
	template <typename T>
	void setTypeTag(QCborTag tag = static_cast<QCborTag>(NoTag));
	//! @copybrief CborSerializer::setTypeTag(QCborTag)
	void setTypeTag(int metaTypeId, QCborTag tag = static_cast<QCborTag>(NoTag));
	//! @copybrief TypeConverter::SerializationHelper::typeTag
	template <typename T>
	QCborTag typeTag() const;
	QCborTag typeTag(int metaTypeId) const override;

	//! Serializers a QVariant value to a QCborValue
	QCborValue serialize(const QVariant &data) const;
	//! Serializers a QVariant value to a device
	void serializeTo(QIODevice *device, const QVariant &data, QCborValue::EncodingOptions options = QCborValue::NoTransformation) const;
	//! Serializers a QVariant value to a byte array
	QByteArray serializeTo(const QVariant &data, QCborValue::EncodingOptions options = QCborValue::NoTransformation) const;

	//! Serializers a c++ type to cbor
	template <typename T>
	QCborValue serialize(const T &data) const;
	//! Serializers a c++ type to a device
	template <typename T>
	void serializeTo(QIODevice *device, const T &data, QCborValue::EncodingOptions options = QCborValue::NoTransformation) const;
	//! Serializers a c++ type to a byte array
	template <typename T>
	QByteArray serializeTo(const T &data, QCborValue::EncodingOptions options = QCborValue::NoTransformation) const;

	//! Deserializes a QCborValue to a QVariant value, based on the given type id
	QVariant deserialize(const QCborValue &cbor, int metaTypeId, QObject *parent = nullptr) const;
	//! Deserializes data from a device to a QVariant value, based on the given type id
	QVariant deserializeFrom(QIODevice *device, int metaTypeId, QObject *parent = nullptr) const;
	//! Deserializes data from a device to a QVariant value, based on the given type id
	QVariant deserializeFrom(const QByteArray &data, int metaTypeId, QObject *parent = nullptr) const;

	//! Deserializes cbor to the given c++ type
	template <typename T>
	T deserialize(const QCborValue &cbor, QObject *parent = nullptr) const;
	//! Deserializes data from a device to the given c++ type
	template <typename T>
	T deserializeFrom(QIODevice *device, QObject *parent = nullptr) const;
	//! Deserializes data from a byte array to the given c++ type
	template <typename T>
	T deserializeFrom(const QByteArray &data, QObject *parent = nullptr) const;

	std::variant<QCborValue, QJsonValue> serializeGeneric(const QVariant &value) const override;
	QVariant deserializeGeneric(const std::variant<QCborValue, QJsonValue> &value, int metaTypeId, QObject *parent) const override;

public Q_SLOTS:
	//! @writeAcFn{CborSerializer::handleSpecialNumbers}
	void setHandleSpecialNumbers(bool handleSpecialNumbers);

Q_SIGNALS:
	//! @notifyAcFn{CborSerializer::handleSpecialNumbers}
	void handleSpecialNumbersChanged(bool handleSpecialNumbers, QPrivateSignal);

protected:
	// protected implementation -> internal use for the type converters
	bool jsonMode() const override;
	QList<int> typesForTag(QCborTag tag) const override;

private:
	Q_DECLARE_PRIVATE(CborSerializer)
};

// ------------- generic implementation -------------

template<typename T>
void CborSerializer::setTypeTag(QCborTag tag)
{
	setTypeTag(qMetaTypeId<T>(), tag);
}

template<typename T>
QCborTag CborSerializer::typeTag() const
{
	return typeTag(qMetaTypeId<T>());
}

template<typename T>
QCborValue CborSerializer::serialize(const T &data) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be serialized");
	return serialize(__private::variant_helper<T>::toVariant(data));
}

template<typename T>
void CborSerializer::serializeTo(QIODevice *device, const T &data, QCborValue::EncodingOptions options) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be serialized");
	serializeTo(device, __private::variant_helper<T>::toVariant(data), options);
}

template<typename T>
QByteArray CborSerializer::serializeTo(const T &data, QCborValue::EncodingOptions options) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be serialized");
	return serializeTo(__private::variant_helper<T>::toVariant(data), options);
}

template<typename T>
T CborSerializer::deserialize(const QCborValue &cbor, QObject *parent) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be deserialized");
	return __private::variant_helper<T>::fromVariant(deserialize(cbor, qMetaTypeId<T>(), parent));
}

template<typename T>
T CborSerializer::deserializeFrom(QIODevice *device, QObject *parent) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be deserialized");
	return __private::variant_helper<T>::fromVariant(deserializeFrom(device, qMetaTypeId<T>(), parent));
}

template<typename T>
T CborSerializer::deserializeFrom(const QByteArray &data, QObject *parent) const
{
	static_assert(__private::is_serializable<T>::value, "T cannot be deserialized");
	return __private::variant_helper<T>::fromVariant(deserializeFrom(data, qMetaTypeId<T>(), parent));
}

}

#endif // QTJSONSERIALIZER_CBORSERIALIZER_H
