#ifndef PREDICATE_AWESOME_H
#define PREDICATE_AWESOME_H

#include "PredicateAnd.h"
#include "PredicateDefaultSpec.h"
#include "Vector.h"

namespace PsimagLite
{

/* PSIDOC PredicateAwesome
 Comma-separated list of items, don't leave spaces:
 item0,item1,item2,...

 Each item is either a setting or a predicate

 At least one item is a predicate

 If multiple predicates are present, they are ORed.

 Setting item is of the form
 S1   /\[a-zA-Z]+/
 S2  /\[a-zA-Z]+=\[a-zA-Z\d\.\-]/

 Predicate is a &-separated list of simple predicates,

 SimplePredicate0&SimplePredicate1&...

 where & means to AND the predicates.

 where SimplePredicate is of the form
 word operator word
 where operator is in {==, <, >, <=, >=, %%}
 with the expected meaning, and %% means divisible by.
 So l%%2 means that the simple predicate is true if l is divisible by 2.

 From this rules, the list of items
 M=2,l%%2&l!=0,l>=7,l<11
 will set M=2 and define a predicate that is true if l
 is in the set {2, 4, 6, 7, 8, 9, 10, 12, 14, 16, 18 ...}
 and false otherwise.

 API via SpecType

 S1 calls SpecType::set(String word)
 S2 calls SpecType::set(String word1, String word2)

 The predicate call is

 template<typename T>
 bool isPredicateTrue(String name, T val)

 template<typename T1, typename T2>
 bool isPredicateTrue(String name1, T1 val2, String name2, T2 val2)

 etc. Can we do this with varargs templates?? FIXME TODO
 */

template <typename SpecType = PredicateDefaultSpec>
class PredicateAwesome
{

public:

	typedef Vector<PredicateAnd>::Type VectorPredicateAndType;
	typedef PredicateAnd::VectorStringType VectorStringType;

	PredicateAwesome(String pred, String orSeparator = ",", String andSeparator = "&", SpecType* spec = nullptr)
	    : pred_(pred)
	{
		if (pred_ == "")
			return;
		VectorStringType tokens;
		split(tokens, pred, orSeparator);
		const SizeType n = tokens.size();
		for (SizeType i = 0; i < n; ++i) {
			if (tokens[i].length() > 0 && tokens[i][0] == '@') {
				if (!spec)
					throw RuntimeError(
					    String("PredicateAwesome:: with "
						   "default spec")
					    + " cannot handle special options\n");
				spec->operator()(tokens[i]);
				continue;
			}

			predicateAnd_.push_back(
			    PredicateAnd(tokens[i], andSeparator));
		}
	}

	template <typename T>
	bool isTrue(String name, T val)
	{
		if (pred_ == "")
			return false;
		SizeType n = predicateAnd_.size();
		for (SizeType i = 0; i < n; ++i)
			if (predicateAnd_[i].isTrue(name, val))
				return true;
		return false;
	}

	template <typename T1, typename T2>
	bool isTrue(String name1, T1 val1, String name2, T2 val2)
	{
		if (pred_ == "")
			return false;
		SizeType n = predicateAnd_.size();
		for (SizeType i = 0; i < n; ++i)
			if (predicateAnd_[i].isTrue(name1, val1, name2, val2))
				return true;
		return false;
	}

	template <typename T1, typename T2>
	bool isTrue(String name1, T1 val1, String name2, T2 val2, String name3, T1 val3)
	{
		if (pred_ == "")
			return false;
		SizeType n = predicateAnd_.size();
		for (SizeType i = 0; i < n; ++i)
			if (predicateAnd_[i].isTrue(name1, val1, name2, val2, name3, val3))
				return true;
		return false;
	}

	template <typename T1, typename T2>
	bool isTrue(String name1, T1 val1, String name2, T2 val2, String name3, T1 val3, String name4, T2 val4)
	{
		if (pred_ == "")
			return false;
		SizeType n = predicateAnd_.size();
		for (SizeType i = 0; i < n; ++i)
			if (predicateAnd_[i].isTrue(name1, val1, name2, val2, name3, val3, name4, val4))
				return true;
		return false;
	}

private:

	String pred_;
	VectorPredicateAndType predicateAnd_;
};
} // namespace PsimagLite
#endif // PREDICATE_AWESOME_H
