/**********************************************************************
 * 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.
 **********************************************************************/

#pragma once

#include <stdexcept>
#include <type_traits>
#include <memory>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/fold.hpp>
#include <boost/fusion/include/filter_if.hpp>
#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/transform.hpp>
#include <boost/fusion/include/zip.hpp>
#include <boost/fusion/container/generation/make_vector.hpp>
#include <boost/variant/variant.hpp>
#include <boost/optional.hpp>
#include <QStringList>
#include <QDateTime>
#include <QPair>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QVariant>
#include <QDateTime>
#include <QtDebug>
#include <util/sll/qtutil.h>
#include <util/sll/prelude.h>
#include <util/sll/typelist.h>
#include <util/sll/oldcppkludges.h>
#include <util/db/dblock.h>
#include <util/db/util.h>
#include "oraltypes.h"

using QSqlQuery_ptr = std::shared_ptr<QSqlQuery>;

namespace LeechCraft
{
namespace Util
{
namespace oral
{
	class QueryException : public std::runtime_error
	{
		const QSqlQuery_ptr Query_;
	public:
		QueryException (const std::string& str, const QSqlQuery_ptr& q)
		: std::runtime_error (str)
		, Query_ (q)
		{
		}

		virtual ~QueryException () throw ()
		{
		}

		const QSqlQuery_ptr& GetQueryPtr () const
		{
			return Query_;
		}

		const QSqlQuery& GetQuery () const
		{
			return *Query_;
		}
	};

	template<int... Fields>
	struct UniqueSubset;

	template<typename... Args>
	using Constraints = Typelist<Args...>;

	namespace detail
	{
		template<typename T>
		QString MorphFieldNameImpl (const QString& str, int, decltype (&T::FieldNameMorpher)* = nullptr)
		{
			return T::FieldNameMorpher (str);
		}

		template<typename T>
		QString MorphFieldNameImpl (const QString& str, float)
		{
			return str;
		}

		template<typename T>
		QString MorphFieldName (const QString& str)
		{
			return MorphFieldNameImpl<T> (str, 0);
		}

		template<typename Seq, int Idx>
		struct GetFieldName
		{
			static QString value ()
			{
				const QString rawName { boost::fusion::extension::struct_member_name<Seq, Idx>::call () };
				return MorphFieldName<Seq> (rawName);
			}
		};

		template<typename S, typename N>
		struct GetFieldsNames_
		{
			QStringList operator() () const
			{
				return QStringList { GetFieldName<S, N::value>::value () } + GetFieldsNames_<S, typename boost::mpl::next<N>::type> {} ();
			}
		};

		template<typename S>
		struct GetFieldsNames_<S, typename boost::fusion::result_of::size<S>::type>
		{
			QStringList operator() () const
			{
				return {};
			}
		};

		template<typename S>
		struct GetFieldsNames : GetFieldsNames_<S, boost::mpl::int_<0>>
		{
		};

		template<typename Seq, int Idx>
		struct GetBoundName
		{
			static QString value () { return ':' + Seq::ClassName () + "_" + GetFieldName<Seq, Idx>::value (); }
		};
	}

	template<typename T>
	struct Type2Name;

	template<>
	struct Type2Name<int>
	{
		QString operator() () const { return "INTEGER"; }
	};

	template<>
	struct Type2Name<double>
	{
		QString operator() () const { return "REAL"; }
	};

	template<>
	struct Type2Name<bool>
	{
		QString operator() () const { return "INTEGER"; }
	};

	template<>
	struct Type2Name<QString>
	{
		QString operator() () const { return "TEXT"; }
	};

	template<>
	struct Type2Name<QByteArray>
	{
		QString operator() () const { return "BLOB"; }
	};

	template<>
	struct Type2Name<QDateTime>
	{
		QString operator() () const { return "TEXT"; }
	};

	template<typename T>
	struct Type2Name<Unique<T>>
	{
		QString operator() () const { return Type2Name<T> () () + " UNIQUE"; }
	};

	template<typename T, typename... Tags>
	struct Type2Name<PKey<T, Tags...>>
	{
		QString operator() () const { return Type2Name<T> () () + " PRIMARY KEY"; }
	};

	template<typename... Tags>
	struct Type2Name<PKey<int, Tags...>>
	{
		QString operator() () const { return Type2Name<int> () () + " PRIMARY KEY AUTOINCREMENT"; }
	};

	template<typename Seq, int Idx>
	struct Type2Name<References<Seq, Idx>>
	{
		QString operator() () const
		{
			return Type2Name<typename References<Seq, Idx>::value_type> () () +
					" REFERENCES " + Seq::ClassName () + " (" + detail::GetFieldName<Seq, Idx>::value () + ") ON DELETE CASCADE";
		}
	};

	template<typename T>
	struct ToVariant
	{
		QVariant operator() (const T& t) const
		{
			return t;
		}
	};

	template<>
	struct ToVariant<QDateTime>
	{
		QVariant operator() (const QDateTime& t) const
		{
			return t.toString (Qt::ISODate);
		}
	};

	template<typename T>
	struct ToVariant<Unique<T>>
	{
		QVariant operator() (const Unique<T>& t) const
		{
			return static_cast<typename Unique<T>::value_type> (t);
		}
	};

	template<typename T, typename... Tags>
	struct ToVariant<PKey<T, Tags...>>
	{
		QVariant operator() (const PKey<T, Tags...>& t) const
		{
			return static_cast<typename PKey<T, Tags...>::value_type> (t);
		}
	};

	template<typename Seq, int Idx>
	struct ToVariant<References<Seq, Idx>>
	{
		QVariant operator() (const References<Seq, Idx>& t) const
		{
			return static_cast<typename References<Seq, Idx>::value_type> (t);
		}
	};

	template<typename T>
	struct FromVariant
	{
		T operator() (const QVariant& var) const
		{
			return var.value<T> ();
		}
	};

	template<>
	struct FromVariant<QDateTime>
	{
		QDateTime operator() (const QVariant& var) const
		{
			return QDateTime::fromString (var.toString (), Qt::ISODate);
		}
	};

	template<typename T>
	struct FromVariant<Unique<T>>
	{
		T operator() (const QVariant& var) const
		{
			return var.value<T> ();
		}
	};

	template<typename T, typename... Tags>
	struct FromVariant<PKey<T, Tags...>>
	{
		T operator() (const QVariant& var) const
		{
			return var.value<T> ();
		}
	};

	template<typename Seq, int Idx>
	struct FromVariant<References<Seq, Idx>>
	{
		using value_type = typename References<Seq, Idx>::value_type;

		value_type operator() (const QVariant& var) const
		{
			return var.value<value_type> ();
		}
	};

	enum class InsertAction
	{
		Default,
		Ignore,
		Replace
	};

	namespace detail
	{
		struct Types
		{
			template<typename T>
			QStringList operator() (const QStringList& init, const T&) const
			{
				return init + QStringList { Type2Name<T> () () };
			}
		};

		struct Inserter
		{
			/** Whether the PKey values should be bound.
			 *
			 * The NoAutogen tag is not considered.
			 */
			const bool BindPrimaryKey_;
			QSqlQuery_ptr Q_;

			template<typename T>
			QStringList operator() (QStringList bounds, const T& t) const
			{
				if (BindPrimaryKey_ || !IsPKey<T>::value)
					Q_->bindValue (bounds.takeFirst (), ToVariant<T> {} (t));
				return bounds;
			}
		};

		struct Selector
		{
			QSqlQuery_ptr Q_;

			template<typename T>
			int operator() (int index, T& t) const
			{
				t = FromVariant<T> {} (Q_->value (index));
				return index + 1;
			}
		};

		struct CachedFieldsData
		{
			QString Table_;
			QSqlDatabase DB_;

			QList<QString> Fields_;
			QList<QString> BoundFields_;
		};

		template<typename T>
		std::function<void (T)> MakeInserter (CachedFieldsData data, QSqlQuery_ptr insertQuery, bool bindPrimaryKey)
		{
			return [data, insertQuery, bindPrimaryKey] (const T& t)
			{
				boost::fusion::fold<T, QStringList, Inserter> (t, data.BoundFields_, Inserter { bindPrimaryKey, insertQuery });
				if (!insertQuery->exec ())
				{
					DBLock::DumpError (*insertQuery);
					throw QueryException ("insert query execution failed", insertQuery);
				}
			};
		}

		template<typename T>
		struct Lazy
		{
			using type = T;
		};

		template<typename Seq, int Idx>
		using ValueAtC_t = typename boost::fusion::result_of::value_at_c<Seq, Idx>::type;

		template<typename Seq, typename Idx>
		using ValueAt_t = typename boost::fusion::result_of::value_at<Seq, Idx>::type;

		template<typename Seq, typename MemberIdx = boost::mpl::int_<0>>
		struct FindPKey
		{
			static_assert ((boost::fusion::result_of::size<Seq>::value) != (MemberIdx::value),
					"Primary key not found");

			using result_type = typename std::conditional<
						IsPKey<ValueAt_t<Seq, MemberIdx>>::value,
						Lazy<MemberIdx>,
						Lazy<FindPKey<Seq, typename boost::mpl::next<MemberIdx>>>
					>::type::type;
		};

		template<typename Seq, int Idx = FindPKey<Seq>::result_type::value>
		constexpr bool HasAutogenPKeyImpl (int)
		{
			return !HasType<NoAutogen> (AsTypelist_t<ValueAtC_t<Seq, Idx>> {});
		}

		template<typename>
		constexpr bool HasAutogenPKeyImpl (float)
		{
			return false;
		}

		template<typename Seq>
		constexpr bool HasAutogenPKey ()
		{
			return HasAutogenPKeyImpl<Seq> (0);
		}

		QString GetInsertPrefix (InsertAction action)
		{
			switch (action)
			{
			case InsertAction::Default:
				return "INSERT";
			case InsertAction::Ignore:
				return "INSERT OR IGNORE";
			case InsertAction::Replace:
				return "INSERT OR REPLACE";
			}

			qWarning () << Q_FUNC_INFO
					<< "unknown action"
					<< static_cast<int> (action);
			return "INSERT";
		}

		template<typename Seq>
		struct AdaptInsert
		{
			const CachedFieldsData Data_;
			const QString InsertSuffix_;

			struct PrivateTag {};

			AdaptInsert (const CachedFieldsData& data, const PrivateTag&)
			: Data_ (data)
			, InsertSuffix_ (" INTO " + data.Table_ +
					" (" + QStringList { data.Fields_ }.join (", ") + ") VALUES (" +
					QStringList { data.BoundFields_ }.join (", ") + ");")
			{
			}
		public:
			template<bool Autogen = HasAutogenPKey<Seq> ()>
			AdaptInsert (CachedFieldsData data, EnableIf_t<Autogen>* = nullptr)
			: AdaptInsert
			{
				{
					[data] () mutable
					{
						constexpr auto index = FindPKey<Seq>::result_type::value;
						data.Fields_.removeAt (index);
						data.BoundFields_.removeAt (index);
						return data;
					} ()
				},
				PrivateTag {}
			}
			{
			}

			template<bool Autogen = HasAutogenPKey<Seq> ()>
			AdaptInsert (const CachedFieldsData& data, EnableIf_t<!Autogen>* = nullptr)
			: AdaptInsert { data, PrivateTag {} }
			{
			}

			template<bool Autogen = HasAutogenPKey<Seq> ()>
			EnableIf_t<Autogen> operator() (Seq& t, InsertAction action = InsertAction::Default) const
			{
				auto query = std::make_shared<QSqlQuery> (Data_.DB_);
				query->prepare (GetInsertPrefix (action) + InsertSuffix_);
				MakeInserter<Seq> (Data_, query, false) (t);

				constexpr auto index = FindPKey<Seq>::result_type::value;
				boost::fusion::at_c<index> (t) = FromVariant<ValueAtC_t<Seq, index>> {} (query->lastInsertId ());
			}

			template<bool Autogen = HasAutogenPKey<Seq> ()>
			EnableIf_t<!Autogen> operator() (const Seq& t, InsertAction action = InsertAction::Default) const
			{
				auto query = std::make_shared<QSqlQuery> (Data_.DB_);
				query->prepare (GetInsertPrefix (action) + InsertSuffix_);
				MakeInserter<Seq> (Data_, query, true) (t);
			}
		};

		template<typename T>
		std::function<void (T)> AdaptUpdate (const CachedFieldsData& data)
		{
			const auto index = FindPKey<T>::result_type::value;

			auto removedFields = data.Fields_;
			auto removedBoundFields = data.BoundFields_;

			const auto& fieldName = removedFields.takeAt (index);
			const auto& boundName = removedBoundFields.takeAt (index);

			const auto& statements = Util::ZipWith (removedFields, removedBoundFields,
					[] (const QString& s1, const QString& s2) -> QString
						{ return s1 + " = " + s2; });

			const auto& update = "UPDATE " + data.Table_ +
					" SET " + QStringList { statements }.join (", ") +
					" WHERE " + fieldName + " = " + boundName + ";";

			const auto updateQuery = std::make_shared<QSqlQuery> (data.DB_);
			updateQuery->prepare (update);
			return MakeInserter<T> (data, updateQuery, true);
		}

		template<typename T>
		std::function<void (T)> AdaptDelete (CachedFieldsData data)
		{
			const auto index = FindPKey<T>::result_type::value;

			const auto& boundName = data.BoundFields_.at (index);
			const auto& del = "DELETE FROM " + data.Table_ +
					" WHERE " + data.Fields_.at (index) + " = " + boundName + ";";

			const auto deleteQuery = std::make_shared<QSqlQuery> (data.DB_);
			deleteQuery->prepare (del);

			return [deleteQuery, boundName] (const T& t)
			{
				constexpr auto index = FindPKey<T>::result_type::value;
				deleteQuery->bindValue (boundName,
						ToVariant<ValueAtC_t<T, index>> {} (boost::fusion::at_c<index> (t)));
				if (!deleteQuery->exec ())
					throw QueryException ("delete query execution failed", deleteQuery);
			};
		}

		template<typename T>
		QList<T> PerformSelect (QSqlQuery_ptr q)
		{
			if (!q->exec ())
				throw QueryException ("fetch query execution failed", q);

			QList<T> result;
			while (q->next ())
			{
				T t;
				boost::fusion::fold<T, int, Selector> (t, 0, Selector { q });
				result << t;
			}
			q->finish ();
			return result;
		}

		template<typename T>
		std::function<QList<T> ()> AdaptSelectAll (const CachedFieldsData& data)
		{
			const auto& selectAll = "SELECT " + QStringList { data.Fields_ }.join (", ") + " FROM " + data.Table_ + ";";
			const auto selectQuery = std::make_shared<QSqlQuery> (data.DB_);
			selectQuery->prepare (selectAll);
			return [selectQuery] { return PerformSelect<T> (selectQuery); };
		}

		template<int HeadT, int... TailT>
		struct FieldsUnpacker
		{
			static const int Head = HeadT;
			using Tail_t = FieldsUnpacker<TailT...>;
		};

		template<int HeadT>
		struct FieldsUnpacker<HeadT>
		{
			static const int Head = HeadT;
			using Tail_t = std::false_type;
		};

		template<typename FieldsUnpacker, typename HeadArg, typename... TailArgs>
		struct ValueBinder
		{
			QSqlQuery_ptr Query_;
			QList<QString> BoundFields_;

			void operator() (const HeadArg& arg, const TailArgs&... tail) const
			{
				Query_->bindValue (BoundFields_.at (FieldsUnpacker::Head), arg);

				ValueBinder<typename FieldsUnpacker::Tail_t, TailArgs...> { Query_, BoundFields_ } (tail...);
			}
		};

		template<typename FieldsUnpacker, typename HeadArg>
		struct ValueBinder<FieldsUnpacker, HeadArg>
		{
			QSqlQuery_ptr Query_;
			QList<QString> BoundFields_;

			void operator() (const HeadArg& arg) const
			{
				Query_->bindValue (BoundFields_.at (FieldsUnpacker::Head), arg);
			}
		};

		enum class ExprType
		{
			LeafStaticPlaceholder,
			LeafData,

			Greater,
			Less,
			Equal,
			Geq,
			Leq,
			Neq,

			And,
			Or
		};

		QString TypeToSql (ExprType type)
		{
			switch (type)
			{
			case ExprType::Greater:
				return ">";
			case ExprType::Less:
				return "<";
			case ExprType::Equal:
				return "=";
			case ExprType::Geq:
				return ">=";
			case ExprType::Leq:
				return "<=";
			case ExprType::Neq:
				return "!=";
			case ExprType::And:
				return "AND";
			case ExprType::Or:
				return "OR";

			case ExprType::LeafStaticPlaceholder:
			case ExprType::LeafData:
				return "invalid type";
			}

			qWarning () << Q_FUNC_INFO
					<< "unhandled type"
					<< static_cast<int> (type);
			return {};
		}

		template<ExprType Type>
		struct IsLeaf : std::false_type {};

		template<>
		struct IsLeaf<ExprType::LeafStaticPlaceholder> : std::true_type {};

		template<>
		struct IsLeaf<ExprType::LeafData> : std::true_type {};

		template<ExprType Type1, ExprType Type2>
		struct IsCompatible : std::false_type {};

		template<ExprType Type>
		struct IsCompatible<Type, ExprType::And> : std::true_type {};

		template<ExprType Type>
		struct IsCompatible<Type, ExprType::Or> : std::true_type {};

		template<ExprType Type>
		struct IsCompatible<Type, ExprType::LeafStaticPlaceholder> : std::true_type {};

		template<ExprType Type>
		struct IsCompatible<Type, ExprType::LeafData> : std::true_type {};

		template<typename T>
		constexpr T Ctfy (T t)
		{
			return t;
		}

		template<ExprType T1, ExprType T2>
		constexpr bool CheckCompatible ()
		{
			return IsCompatible<T1, T2>::value || IsCompatible<T2, T1>::value;
		}

		constexpr bool IsRelational (ExprType type)
		{
			return type == ExprType::Greater ||
					type == ExprType::Less ||
					type == ExprType::Equal ||
					type == ExprType::Geq ||
					type == ExprType::Leq ||
					type == ExprType::Neq;
		}

		template<typename T>
		struct ToSqlState
		{
			int LastID_;
			QVariantMap BoundMembers_;
		};

		template<typename Seq, typename L, typename R>
		constexpr bool AreComparableTypesImpl (int,
				decltype (std::declval<typename L::template ValueType_t<Seq>> () == std::declval<typename R::template ValueType_t<Seq>> ())* = nullptr)
		{
			return true;
		}

		template<typename Seq, typename L, typename R>
		constexpr bool AreComparableTypesImpl (float)
		{
			return false;
		}

		template<typename Seq, typename L, typename R>
		constexpr bool AreComparableTypes ()
		{
			return AreComparableTypesImpl<Seq, L, R> (0) ||
					AreComparableTypesImpl<Seq, R, L> (0);
		}

		template<ExprType Type, typename Seq, typename L, typename R, typename = void>
		struct RelationalTypesChecker : std::true_type {};

		template<typename Seq, typename L, typename R, typename = void>
		struct RelationalTypesCheckerBase : std::false_type {};

		template<typename Seq, typename L, typename R>
		struct RelationalTypesCheckerBase<Seq, L, R, EnableIf_t<AreComparableTypes<Seq, L, R> ()>> : std::true_type {};

		template<ExprType Type, typename Seq, typename L, typename R>
		struct RelationalTypesChecker<Type, Seq, L, R, EnableIf_t<IsRelational (Type)>> : RelationalTypesCheckerBase<Seq, L, R> {};

		template<ExprType Type, typename L = void, typename R = void>
		class ExprTree
		{
			L Left_;
			R Right_;
		public:
			ExprTree (const L& l, const R& r)
			: Left_ (l)
			, Right_ (r)
			{
			}

			template<typename T>
			QString ToSql (ToSqlState<T>& state) const
			{
				static_assert (RelationalTypesChecker<Type, T, L, R>::value,
						"Incompatible types passed to a relational operator.");

				return Left_.ToSql (state) + " " + TypeToSql (Type) + " " + Right_.ToSql (state);
			}
		};

		template<int Idx>
		class ExprTree<ExprType::LeafStaticPlaceholder, boost::mpl::int_<Idx>, void>
		{
		public:
			template<typename T>
			using ValueType_t = ValueAtC_t<T, Idx>;

			template<typename T>
			QString ToSql (ToSqlState<T>&) const
			{
				static_assert (Idx < boost::fusion::result_of::size<T>::type::value, "Index out of bounds.");
				return detail::GetFieldsNames<T> {} ().at (Idx);
			}
		};

		template<typename T>
		class ExprTree<ExprType::LeafData, T, void>
		{
			T Data_;
		public:
			template<typename>
			using ValueType_t = T;

			ExprTree (const T& t)
			: Data_ (t)
			{
			}

			template<typename ObjT>
			QString ToSql (ToSqlState<ObjT>& state) const
			{
				const auto& name = ":bound_" + QString::number (++state.LastID_);
				state.BoundMembers_ [name] = ToVariant<T> {} (Data_);
				return name;
			}
		};

		template<typename T>
		struct IsExprTree : std::false_type {};

		template<ExprType Type, typename L, typename R>
		struct IsExprTree<ExprTree<Type, L, R>> : std::true_type {};

		template<ExprType LType, typename LL, typename LR, ExprType RType, typename RL, typename RR>
		ExprTree<ExprType::Less, ExprTree<LType, LL, LR>, ExprTree<RType, RL, RR>> operator< (const ExprTree<LType, LL, LR>& left, const ExprTree<RType, RL, RR>& right)
		{
			static_assert (CheckCompatible<LType, RType> (), "comparing incompatible subexpressions");
			return { left, right };
		}

		template<ExprType LType, typename LL, typename LR, typename R>
		ExprTree<ExprType::Less, ExprTree<LType, LL, LR>, ExprTree<ExprType::LeafData, R>> operator< (const ExprTree<LType, LL, LR>& left, const R& right)
		{
			return left < ExprTree<ExprType::LeafData, R> { right };
		}

		template<ExprType RType, typename RL, typename RR, typename L>
		ExprTree<ExprType::Less, ExprTree<ExprType::LeafData, L>, ExprTree<RType, RL, RR>> operator< (const L& left, const ExprTree<RType, RL, RR>& right)
		{
			return ExprTree<ExprType::LeafData, L> { left } < right;
		}

		template<ExprType LType, typename LL, typename LR, ExprType RType, typename RL, typename RR>
		ExprTree<ExprType::Equal, ExprTree<LType, LL, LR>, ExprTree<RType, RL, RR>> operator== (const ExprTree<LType, LL, LR>& left, const ExprTree<RType, RL, RR>& right)
		{
			static_assert (CheckCompatible<LType, RType> (), "comparing incompatible subexpressions");
			return { left, right };
		}

		template<ExprType LType, typename LL, typename LR, typename R>
		ExprTree<ExprType::Equal, ExprTree<LType, LL, LR>, ExprTree<ExprType::LeafData, R>> operator== (const ExprTree<LType, LL, LR>& left, const R& right)
		{
			return left == ExprTree<ExprType::LeafData, R> { right };
		}

		template<ExprType RType, typename RL, typename RR, typename L>
		ExprTree<ExprType::Equal, ExprTree<ExprType::LeafData, L>, ExprTree<RType, RL, RR>> operator== (const L& left, const ExprTree<RType, RL, RR>& right)
		{
			return ExprTree<ExprType::LeafData, L> { left } == right;
		}

		template<ExprType LType, typename LL, typename LR, ExprType RType, typename RL, typename RR>
		ExprTree<ExprType::And, ExprTree<LType, LL, LR>, ExprTree<RType, RL, RR>> operator&& (const ExprTree<LType, LL, LR>& left, const ExprTree<RType, RL, RR>& right)
		{
			return { left, right };
		}

		template<ExprType LType, typename LL, typename LR, typename R>
		ExprTree<ExprType::And, ExprTree<LType, LL, LR>, ExprTree<ExprType::LeafData, R>> operator&& (const ExprTree<LType, LL, LR>& left, const R& right)
		{
			return left && ExprTree<ExprType::LeafData, R> { right };
		}

		template<ExprType RType, typename RL, typename RR, typename L>
		ExprTree<ExprType::And, ExprTree<ExprType::LeafData, L>, ExprTree<RType, RL, RR>> operator&& (const L& left, const ExprTree<RType, RL, RR>& right)
		{
			return ExprTree<ExprType::LeafData, L> { left } && right;
		}

		template<typename Seq, ExprType Type, typename L, typename R>
		QPair<QString, std::function<void (QSqlQuery_ptr)>> HandleExprTree (const ExprTree<Type, L, R>& tree)
		{
			ToSqlState<Seq> state { 0, {} };

			const auto& sql = tree.ToSql (state);

			return
			{
				sql,
				[state] (const QSqlQuery_ptr& query)
				{
					for (const auto& pair : Stlize (state.BoundMembers_))
						query->bindValue (pair.first, pair.second);
				}
			};
		}
	}

	namespace sph
	{
		template<int Idx>
		using pos = detail::ExprTree<detail::ExprType::LeafStaticPlaceholder, boost::mpl::int_<Idx>>;

		static constexpr pos<0> _0 = {};
		static constexpr pos<1> _1 = {};
		static constexpr pos<2> _2 = {};
		static constexpr pos<3> _3 = {};
		static constexpr pos<4> _4 = {};

#if __cpp_variable_templates >= 201304
		template<int Idx>
		static constexpr pos<Idx> _ = {};
#endif
	};

	namespace detail
	{
		template<typename T>
		class SelectByFieldsWrapper
		{
			const CachedFieldsData Cached_;
		public:
			SelectByFieldsWrapper (const CachedFieldsData& data)
			: Cached_ (data)
			{
			}

			template<ExprType Type, typename L, typename R>
			QList<T> operator() (const ExprTree<Type, L, R>& tree) const
			{
				const auto& treeResult = HandleExprTree<T> (tree);

				const auto& selectAll = "SELECT " + QStringList { Cached_.Fields_ }.join (", ") +
						" FROM " + Cached_.Table_ +
						" WHERE " + treeResult.first + ";";

				const auto query = std::make_shared<QSqlQuery> (Cached_.DB_);
				query->prepare (selectAll);
				treeResult.second (query);
				return PerformSelect<T> (query);
			}

			template<int Idx, ExprType Type, typename L, typename R>
			QList<ValueAtC_t<T, Idx>> operator() (sph::pos<Idx>, const ExprTree<Type, L, R>& tree) const
			{
				const auto& treeResult = HandleExprTree<T> (tree);

				const auto& selectOne = "SELECT " + Cached_.Fields_.value (Idx) +
						" FROM " + Cached_.Table_ +
						" WHERE " + treeResult.first + ";";

				const auto query = std::make_shared<QSqlQuery> (Cached_.DB_);
				query->prepare (selectOne);
				treeResult.second (query);

				if (!query->exec ())
					throw QueryException ("fetch query execution failed", query);

				using Type_t = ValueAtC_t<T, Idx>;

				QList<Type_t> result;
				while (query->next ())
					result << FromVariant<Type_t> {} (query->value (0));
				query->finish ();
				return result;
			}
		};

		template<typename T>
		class SelectOneByFieldsWrapper
		{
			const SelectByFieldsWrapper<T> Select_;
		public:
			SelectOneByFieldsWrapper (const CachedFieldsData& data)
			: Select_ { data }
			{
			}

			template<ExprType Type, typename L, typename R>
			boost::optional<T> operator() (const ExprTree<Type, L, R>& tree) const
			{
				const auto& result = Select_ (tree);
				if (result.isEmpty ())
					return {};

				return result.value (0);
			}

			template<int Idx, ExprType Type, typename L, typename R>
			boost::optional<ValueAtC_t<T, Idx>> operator() (sph::pos<Idx> p, const ExprTree<Type, L, R>& tree) const
			{
				const auto& result = Select_ (p, tree);
				if (result.isEmpty ())
					return {};

				return result.value (0);
			}
		};

		template<typename T>
		class DeleteByFieldsWrapper
		{
			const CachedFieldsData Cached_;
		public:
			DeleteByFieldsWrapper (const CachedFieldsData& data)
			: Cached_ (data)
			{
			}

			template<ExprType Type, typename L, typename R>
			void operator() (const ExprTree<Type, L, R>& tree) const
			{
				const auto& treeResult = HandleExprTree<T> (tree);

				const auto& selectAll = "DELETE FROM " + Cached_.Table_ +
						" WHERE " + treeResult.first + ";";

				const auto query = std::make_shared<QSqlQuery> (Cached_.DB_);
				query->prepare (selectAll);
				treeResult.second (query);
				query->exec ();
			}
		};

		template<typename T>
		SelectByFieldsWrapper<T> AdaptSelectFields (const CachedFieldsData& data)
		{
			return { data };
		}

		template<typename T>
		SelectOneByFieldsWrapper<T> AdaptSelectOneFields (const CachedFieldsData& data)
		{
			return { data };
		}

		template<typename T>
		DeleteByFieldsWrapper<T> AdaptDeleteFields (const CachedFieldsData& data)
		{
			return { data };
		}

		template<typename OrigSeq, typename OrigIdx, typename RefSeq, typename MemberIdx>
		struct FieldInfo
		{
		};

		template<typename To, typename OrigSeq, typename OrigIdx, typename T>
		struct FieldAppender
		{
			using value_type = To;
		};

		template<typename To, typename OrigSeq, typename OrigIdx, typename RefSeq, int RefIdx>
		struct FieldAppender<To, OrigSeq, OrigIdx, References<RefSeq, RefIdx>>
		{
			using value_type = typename boost::fusion::result_of::as_vector<
					typename boost::fusion::result_of::push_front<
						To,
						FieldInfo<OrigSeq, OrigIdx, RefSeq, boost::mpl::int_<RefIdx>>
					>::type
				>::type;
		};

		template<typename Seq, typename MemberIdx>
		struct CollectRefs_
		{
			using type_list = typename FieldAppender<
					typename CollectRefs_<Seq, typename boost::mpl::next<MemberIdx>::type>::type_list,
					Seq,
					MemberIdx,
					typename std::decay<typename boost::fusion::result_of::at<Seq, MemberIdx>::type>::type
				>::value_type;
		};

		template<typename Seq>
		struct CollectRefs_<Seq, typename boost::fusion::result_of::size<Seq>::type>
		{
			using type_list = boost::fusion::vector<>;
		};

		template<typename Seq>
		struct CollectRefs : CollectRefs_<Seq, boost::mpl::int_<0>>
		{
		};

		struct Ref2Select
		{
			template<typename OrigSeq, typename OrigIdx, typename RefSeq, typename RefIdx>
			QStringList operator() (const QStringList& init, const FieldInfo<OrigSeq, OrigIdx, RefSeq, RefIdx>&) const
			{
				const auto& thisQualified = OrigSeq::ClassName () + "." + GetFieldName<OrigSeq, OrigIdx::value>::value ();
				return init + QStringList { thisQualified + " = " + GetBoundName<RefSeq, RefIdx::value>::value () };
			}
		};

		template<typename T>
		struct ExtrObj;

		template<typename OrigSeq, typename OrigIdx, typename RefSeq, typename MemberIdx>
		struct ExtrObj<FieldInfo<OrigSeq, OrigIdx, RefSeq, MemberIdx>>
		{
			using type = RefSeq;
		};

		struct SingleBind
		{
			QSqlQuery_ptr Q_;

			template<typename ObjType, typename OrigSeq, typename OrigIdx, typename RefSeq, typename RefIdx>
			void operator() (const boost::fusion::vector2<ObjType, const FieldInfo<OrigSeq, OrigIdx, RefSeq, RefIdx>&>& pair) const
			{
				Q_->bindValue (GetBoundName<RefSeq, RefIdx::value>::value (),
						ToVariant<typename std::decay<typename boost::fusion::result_of::at<RefSeq, RefIdx>::type>::type> () (boost::fusion::at<RefIdx> (boost::fusion::at_c<0> (pair))));
			}
		};

		template<typename T, typename RefSeq>
		struct MakeBinder
		{
			using transform_view = typename boost::mpl::transform<RefSeq, ExtrObj<boost::mpl::_1>>;
			using objects_view = typename transform_view::type;
			using objects_vector = typename boost::fusion::result_of::as_vector<objects_view>::type;

			QSqlQuery_ptr Q_;

			QList<T> operator() (const objects_vector& objs)
			{
				boost::fusion::for_each (boost::fusion::zip (objs, RefSeq {}), SingleBind { Q_ });
				return PerformSelect<T> (Q_);
			}
		};

		template<typename T, typename ObjInfo>
		typename std::enable_if<CollectRefs<T>::type_list::size::value == 1>::type AdaptSelectRef (const CachedFieldsData& data, ObjInfo& info)
		{
			using references_list = typename CollectRefs<T>::type_list;
			const auto& statements = boost::fusion::fold (references_list {}, QStringList {}, Ref2Select {});

			const auto& selectAll = "SELECT " + QStringList { data.Fields_ }.join (", ") +
					" FROM " + data.Table_ +
					(statements.isEmpty () ? "" : " WHERE ") + statements.join (" AND ") +
					";";
			const auto selectQuery = std::make_shared<QSqlQuery> (data.DB_);
			selectQuery->prepare (selectAll);

			info.SelectByFKeysActor_ = MakeBinder<T, references_list> { selectQuery };
		}

		template<typename T, typename Ret>
		struct WrapAsFunc
		{
			using type = std::function<QList<Ret> (T)>;
		};

		template<typename T, typename Ret>
		using WrapAsFunc_t = typename WrapAsFunc<T, Ret>::type;

		template<typename T>
		struct MakeSingleBinder
		{
			const CachedFieldsData Data_;

			template<typename Vec, typename OrigObj, typename OrigIdx, typename RefObj, typename RefIdx>
			auto operator() (Vec vec, const FieldInfo<OrigObj, OrigIdx, RefObj, RefIdx>&) -> decltype (boost::fusion::push_back (vec, WrapAsFunc_t<RefObj, T> {}))
			{
				const auto& boundName = GetBoundName<OrigObj, OrigIdx::value>::value ();
				const auto& query = "SELECT " + QStringList { Data_.Fields_ }.join (", ") +
						" FROM " + Data_.Table_ +
						" WHERE " + GetFieldName<OrigObj, OrigIdx::value>::value () + " = " + boundName +
						";";
				const auto selectQuery = std::make_shared<QSqlQuery> (Data_.DB_);
				selectQuery->prepare (query);

				auto inserter = [selectQuery, boundName] (const RefObj& obj)
				{
					selectQuery->bindValue (boundName,
							ToVariant<ValueAt_t<RefObj, RefIdx>> {} (boost::fusion::at<RefIdx> (obj)));
					return PerformSelect<T> (selectQuery);
				};

				return boost::fusion::push_back (vec, WrapAsFunc_t<RefObj, T> { inserter });
			}
		};

		template<typename T, typename ObjInfo>
		typename std::enable_if<CollectRefs<T>::type_list::size::value >= 2>::type AdaptSelectRef (const CachedFieldsData& data, ObjInfo& info)
		{
			using references_list = typename CollectRefs<T>::type_list;
			const auto& statements = boost::fusion::fold (references_list {}, QStringList {}, Ref2Select {});

			const auto& selectAll = "SELECT " + QStringList { data.Fields_ }.join (", ") +
					" FROM " + data.Table_ +
					(statements.isEmpty () ? "" : " WHERE ") + statements.join (" AND ") +
					";";
			const auto selectQuery = std::make_shared<QSqlQuery> (data.DB_);
			selectQuery->prepare (selectAll);

			info.SelectByFKeysActor_ = MakeBinder<T, references_list> { selectQuery };

			auto singleSelectors = boost::fusion::fold (references_list {}, boost::fusion::vector<> {}, MakeSingleBinder<T> { data });
			info.SingleFKeySelectors_ = boost::fusion::as_vector (singleSelectors);
		}

		template<typename T, typename ObjInfo>
		typename std::enable_if<CollectRefs<T>::type_list::size::value <= 0>::type AdaptSelectRef (const CachedFieldsData&, ObjInfo&)
		{
		}

		template<typename T>
		typename T::Constraints GetConstraintsTypeImpl (int, typename T::Constraints* = nullptr)
		{
			return {};
		}

		template<typename T>
		Constraints<> GetConstraintsTypeImpl (float)
		{
			return {};
		}

		template<typename T>
		using ConstraintsType = decltype (GetConstraintsTypeImpl<T> (0));

		template<typename T>
		struct ConstraintToString;

		template<int... Fields>
		struct ConstraintToString<UniqueSubset<Fields...>>
		{
			QString operator() (const CachedFieldsData& data) const
			{
				return "UNIQUE (" + QStringList { data.Fields_.value (Fields)... }.join (", ") + ")";
			}
		};

		template<typename...>
		struct GetConstraintsStringList;

		template<>
		struct GetConstraintsStringList<Constraints<>>
		{
			QStringList operator() (const CachedFieldsData&) const
			{
				return {};
			}
		};

		template<typename Head, typename... Tail>
		struct GetConstraintsStringList<Constraints<Head, Tail...>>
		{
			QStringList operator() (const CachedFieldsData& data) const
			{
				return QStringList { ConstraintToString<Head> {} (data) } +
						GetConstraintsStringList<Constraints<Tail...>> {} (data);
			}
		};

		template<typename T>
		QString AdaptCreateTable (const CachedFieldsData& data)
		{
			const QList<QString> types = boost::fusion::fold (T {}, QStringList {}, Types {});

			const auto& constraints = GetConstraintsStringList<ConstraintsType<T>> {} (data);
			const auto& constraintsStr = constraints.isEmpty () ?
					QString {} :
					(", " + constraints.join (", "));

			const auto& statements = Util::ZipWith (types, data.Fields_,
					[] (const QString& type, const QString& field) { return field + " " + type; });
			return "CREATE TABLE " +
					data.Table_ +
					" (" +
					statements.join (", ") +
					constraintsStr +
					");";
		}

		template<typename T, typename Enable = void>
		struct ObjectInfoFKeysHelper
		{
		};

		template<typename T>
		struct ObjectInfoFKeysHelper<T, typename std::enable_if<CollectRefs<T>::type_list::size::value == 1, void>::type>
		{
			std::function<QList<T> (typename MakeBinder<T, typename CollectRefs<T>::type_list>::objects_vector)> SelectByFKeysActor_;
		};

		template<typename T>
		struct ObjectInfoFKeysHelper<T, typename std::enable_if<CollectRefs<T>::type_list::size::value >= 2, void>::type>
		{
			using objects_vector = typename MakeBinder<T, typename CollectRefs<T>::type_list>::objects_vector;
			std::function<QList<T> (objects_vector)> SelectByFKeysActor_;

			using transform_view = typename boost::mpl::transform<objects_vector, WrapAsFunc<boost::mpl::_1, T>>::type;
			typename boost::fusion::result_of::as_vector<transform_view>::type SingleFKeySelectors_;
		};
	}

	template<typename T>
	struct ObjectInfo : detail::ObjectInfoFKeysHelper<T>
	{
		std::function<QList<T> ()> DoSelectAll_;
		detail::AdaptInsert<T> DoInsert_;
		std::function<void (T)> DoUpdate_;
		std::function<void (T)> DoDelete_;

		detail::SelectByFieldsWrapper<T> DoSelectByFields_;
		detail::SelectOneByFieldsWrapper<T> DoSelectOneByFields_;
		detail::DeleteByFieldsWrapper<T> DoDeleteByFields_;

		ObjectInfo (decltype (DoSelectAll_) doSel,
				decltype (DoInsert_) doIns,
				decltype (DoUpdate_) doUpdate,
				decltype (DoDelete_) doDelete,
				decltype (DoSelectByFields_) selectByFields,
				decltype (DoSelectOneByFields_) selectOneByFields,
				decltype (DoDeleteByFields_) deleteByFields)
		: DoSelectAll_ (doSel)
		, DoInsert_ (doIns)
		, DoUpdate_ (doUpdate)
		, DoDelete_ (doDelete)
		, DoSelectByFields_ (selectByFields)
		, DoSelectOneByFields_ (selectOneByFields)
		, DoDeleteByFields_ (deleteByFields)
		{
		}
	};

	template<typename T>
	ObjectInfo<T> Adapt (const QSqlDatabase& db)
	{
		const QList<QString> fields = detail::GetFieldsNames<T> {} ();
		const QList<QString> boundFields = Util::Map (fields, [] (const QString& str) { return ':' + str; });

		const auto& table = T::ClassName ();

		const detail::CachedFieldsData cachedData { table, db, fields, boundFields };
		if (db.record (table).isEmpty ())
			RunTextQuery (db, detail::AdaptCreateTable<T> (cachedData));

		const auto& selectr = detail::AdaptSelectAll<T> (cachedData);
		const auto& insertr = detail::AdaptInsert<T> (cachedData);
		const auto& updater = detail::AdaptUpdate<T> (cachedData);
		const auto& deleter = detail::AdaptDelete<T> (cachedData);

		const auto& selectByVal = detail::AdaptSelectFields<T> (cachedData);
		const auto& selectOneByVal = detail::AdaptSelectOneFields<T> (cachedData);
		const auto& deleteByVal = detail::AdaptDeleteFields<T> (cachedData);

		ObjectInfo<T> info
		{
			selectr,
			insertr,
			updater,
			deleter,
			selectByVal,
			selectOneByVal,
			deleteByVal
		};

		detail::AdaptSelectRef<T> (cachedData, info);

		return info;
	}

	template<typename T>
	using ObjectInfo_ptr = std::shared_ptr<ObjectInfo<T>>;

	template<typename T>
	ObjectInfo_ptr<T> AdaptPtr (const QSqlDatabase& db)
	{
		return std::make_shared<ObjectInfo<T>> (Adapt<T> (db));
	}
}
}
}
