/*
    QAD - Qt ADvanced

    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program 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 3 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef QAD_TYPES_H
#define QAD_TYPES_H

#include "propertystorage.h"
#include "qad_utils_export.h"

#include <QApplication>

//! Set QCoreApplication properties from CMake "deploy_properties"
//! Affect applicationName, organizationName and applicationVersion
#define QAD_SETUP_APPLICATION                                                          \
	QCoreApplication::setApplicationName(QString::fromLocal8Bit(__TARGET_NAME__));     \
	QCoreApplication::setOrganizationName(QString::fromLocal8Bit(__TARGET_COMPANY__)); \
	QCoreApplication::setApplicationVersion(QString::fromLocal8Bit(__TARGET_VERSION__));

#if QT_VERSION_MAJOR <= 5
#  define QT_MID_BUTTON Qt::MidButton
#  include <QMapIterator>
template<typename K, typename T>
using QMultiMapIterator = QMapIterator<K, T>;
#else
#  define QT_MID_BUTTON Qt::MiddleButton
#endif


class QMetaEnum;


namespace QAD {


struct QAD_UTILS_EXPORT Enumerator {
	Enumerator(int v = 0, const QString & n = QString()): value(v), name(n) {}
	int value;
	QString name;
};


struct QAD_UTILS_EXPORT Enum {
	Enum(const QString & n = QString()): enum_name(n) {}
	Enum(const QMetaEnum & meta, int selected = 0);
	int selectedValue() const;
	QString selectedName() const { return selected; }
	bool selectValue(int v);
	bool selectName(const QString & n);
	int value(const QString & n) const;
	QString name(int v) const;
	QList<int> values() const;
	QStringList names() const;
	int size() const { return enum_list.size(); }
	QString enum_name;
	QString selected;
	QList<Enumerator> enum_list;
	Enum & operator<<(const Enumerator & v);
	Enum & operator<<(const QString & v);
	Enum & operator<<(const QStringList & v);
};


struct QAD_UTILS_EXPORT File {
	File(const QString & p = QString(), const QString & f = QString(), bool abs = false, bool save_mode = false)
		: file(p)
		, filter(f)
		, is_abs(abs)
		, is_save(save_mode) {}
	QString toString() const { return file; }
	QString file;
	QString filter;
	bool is_abs;
	bool is_save;
};


struct QAD_UTILS_EXPORT Dir {
	Dir(const QString & d = QString(), bool abs = false): dir(d), is_abs(abs) {}
	QString toString() const { return dir; }
	QString dir;
	bool is_abs;
};


struct QAD_UTILS_EXPORT IODevice {
	IODevice(const QString & device_prefix             = QString(),
	         const PropertyStorage & device_properties = PropertyStorage(),
	         int open_mode                             = QIODevice::ReadWrite,
	         int device_options                        = 0)
		: prefix(device_prefix)
		, mode(open_mode)
		, options(device_options)
		, props(device_properties) {}
	QString toString() const;
	bool isValid() const { return !prefix.isEmpty(); }
	QString prefix;
	int mode;
	int options;
	PropertyStorage props;
};


struct QAD_UTILS_EXPORT MathVector {
	MathVector(const QVector<double> & vec = QVector<double>()) { v = vec; }
	QVector<double> v;
};


struct QAD_UTILS_EXPORT MathMatrix {
	MathMatrix(const QVector<QVector<double>> & mat = QVector<QVector<double>>()) { m = mat; }
	QVector<QVector<double>> m; // [Row][Column]
};


class QAD_UTILS_EXPORT CursorOverrider {
public:
	CursorOverrider(const QCursor & c = Qt::WaitCursor, bool condition = true);
	~CursorOverrider();
	void restore();

private:
	bool cond = true;
	bool on   = true;
};


} // namespace QAD

Q_DECLARE_METATYPE(QAD::Enumerator)
inline QDataStream & operator<<(QDataStream & s, const QAD::Enumerator & v) {
	s << v.value << v.name;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::Enumerator & v) {
	s >> v.value >> v.name;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::Enumerator & v) {
	s.nospace() << v.name << "(" << v.value << ")";
	return s.space();
}

Q_DECLARE_METATYPE(QAD::Enum)
inline QDataStream & operator<<(QDataStream & s, const QAD::Enum & v) {
	s << v.enum_name << v.selected << v.enum_list;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::Enum & v) {
	s >> v.enum_name >> v.selected >> v.enum_list;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::Enum & v) {
	s.nospace() << v.selected;
	return s.space();
}

Q_DECLARE_METATYPE(QAD::File)
inline QDataStream & operator<<(QDataStream & s, const QAD::File & v) {
	s << v.file << v.filter << uchar((v.is_abs ? 1 : 0) + (v.is_save ? 2 : 0));
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::File & v) {
	uchar f(0);
	s >> v.file >> v.filter >> f;
	v.is_abs  = ((f & 1) == 1);
	v.is_save = ((f & 2) == 2);
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::File & v) {
	s.nospace() << v.file;
	return s.space();
}

Q_DECLARE_METATYPE(QAD::Dir)
inline QDataStream & operator<<(QDataStream & s, const QAD::Dir & v) {
	s << v.dir << v.is_abs;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::Dir & v) {
	s >> v.dir >> v.is_abs;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::Dir & v) {
	s.nospace() << v.dir;
	return s.space();
}

Q_DECLARE_METATYPE(QAD::IODevice)
inline QDataStream & operator<<(QDataStream & s, const QAD::IODevice & v) {
	s << v.prefix << v.mode << v.options << v.props;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::IODevice & v) {
	s >> v.prefix >> v.mode >> v.options >> v.props;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::IODevice & v) {
	s.nospace() << v.toString();
	return s.space();
}

Q_DECLARE_METATYPE(QAD::MathVector)
inline QDataStream & operator<<(QDataStream & s, const QAD::MathVector & v) {
	s << v.v;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::MathVector & v) {
	s >> v.v;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::MathVector & v) {
	s.nospace() << "Vector " << v.v;
	return s.space();
}

Q_DECLARE_METATYPE(QAD::MathMatrix)
inline QDataStream & operator<<(QDataStream & s, const QAD::MathMatrix & v) {
	s << v.m;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, QAD::MathMatrix & v) {
	s >> v.m;
	return s;
}
inline QDebug operator<<(QDebug s, const QAD::MathMatrix & v) {
	s.nospace() << "Matrix " << v.m;
	return s.space();
}


class QAD_UTILS_EXPORT __QADTypesRegistrator__ {
public:
	__QADTypesRegistrator__(int);
	static __QADTypesRegistrator__ * instance();
	QMap<int, void (*)(const QVariant &, QString &)> toString_funcs;

private:
	__QADTypesRegistrator__();
	static bool _inited;
};


inline qreal quantize(qreal x, qreal q = 10.f) {
	return qRound(x / q) * q;
}
inline QPointF quantize(QPointF x, qreal q = 10.f) {
	return QPointF(quantize(x.x(), q), quantize(x.y(), q));
}

inline qreal distPointToLine(const QPointF & lp0, const QPointF & lp1, const QPointF & p) {
	QLineF a(lp0, lp1), b(lp0, p), c(lp1, p);
	qreal f = qAbs(a.dx() * b.dy() - a.dy() * b.dx()) / a.length(), s = b.length() + c.length() - a.length();
	return qMax(f, s);
}
inline QPointF nearestPointOnLine(const QPointF & lp0, const QPointF & lp1, const QPointF & p) {
	QLineF a(lp0, lp1), b(lp0, p);
	return a.pointAt(b.length() / a.length());
}
inline QRectF enlargedRect(const QRectF & r, qreal dx, qreal dy, qreal v) {
	return QRectF(r.left() - v + dx, r.top() - v + dy, r.width() + v + v, r.height() + v + v);
}

template<typename T>
QSet<T> QList2QSet(const QList<T> & l) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
	return QSet<T>(l.begin(), l.end());
#else
	return QSet<T>::fromList(l);
#endif
}

template<typename K, typename T>
void addToMap(QMap<K, T> & into, const QMap<K, T> & what) {
	QMapIterator<K, T> it(what);
	while (it.hasNext()) {
		it.next();
		if (!into.contains(it.key())) into[it.key()] = it.value();
	}
}

template<typename K, typename T>
void addToMapOverwrite(QMap<K, T> & into, const QMap<K, T> & what) {
	QMapIterator<K, T> it(what);
	while (it.hasNext()) {
		it.next();
		into[it.key()] = it.value();
	}
}

QAD_UTILS_EXPORT
#if QT_VERSION_MAJOR <= 5
QVariant::Type
#else
QMetaType
#endif
typeFromLetter(const QString & l);
QAD_UTILS_EXPORT QString uniqueName(QString n, const QStringList & names);

QAD_UTILS_EXPORT int fontHeight(const QWidget * window = nullptr);
QAD_UTILS_EXPORT int lineThickness(const QWidget * window = nullptr);
QAD_UTILS_EXPORT QSize preferredIconSize(float x = 1.f, const QWidget * window = nullptr);
QAD_UTILS_EXPORT double appScale(const QWidget * window = nullptr);
QAD_UTILS_EXPORT void enableHighDPI();
QAD_UTILS_EXPORT const QMetaObject * getQtMetaObject();


template<typename T>
QByteArray qSerialize(const T & value, int version = -1) {
	QByteArray ret;
	QDataStream s(&ret, QIODevice::ReadWrite);
	if (version > 0) s.setVersion((QDataStream::Version)version);
	s << value;
	return ret;
}

template<typename T>
T qDeserialize(const QByteArray & data, int version = -1) {
	T ret = T();
	if (!data.isEmpty()) {
		QDataStream s(data);
		if (version > 0) s.setVersion((QDataStream::Version)version);
		s >> ret;
	}
	return ret;
}


/// much more compact and easy to use alternative to QMetaObject::invokeMethod(... , Qt::QueuedConnection)
/// examples:
/// qCallQueued(this, &MyClass::myFunc);
/// qCallQueued(this, &MyClass::myFuncWithInt, 0xFF);
/// qCallQueued(this, &MyClass::myFuncWithIntAndString, 0xAB, "string");
template<typename O, typename F, typename... Args>
void qCallQueued(O * o, F f, Args... args) {
	QMetaObject::invokeMethod(o, [o, f, args...]() { (o->*f)(args...); }, Qt::QueuedConnection);
}

/// qCallQueued with lambda
template<typename O, typename L>
void qCallQueuedL(O * o, L func) {
	QMetaObject::invokeMethod(o, [func] { func(); }, Qt::QueuedConnection);
}


#endif // QAD_TYPES_H
