public static QueryContainer CreateReturnQuery(Action<QueryContainer, BoolBaseQueryDescriptor> modify = null)
		{
			var returnQuery = new QueryContainer();
			var returnBoolQuery = new BoolBaseQueryDescriptor() { };
			((IQueryContainer)returnQuery).Bool = returnBoolQuery;
			if (modify != null)
			{
				modify(returnQuery, returnBoolQuery);
			}
			return returnQuery;
		}
		private static QueryContainer StrictSingleSideAndMerge(QueryContainer targetQuery, QueryContainer mergeQuery)
		{
			//if the target is not strict return
			if (!((IQueryContainer)targetQuery).IsStrict) return null;

			var mergeBoolQuery = ((IQueryContainer)mergeQuery).Bool;

			return CreateReturnQuery((returnQuery, returnBoolQuery) =>
			{
				if (((IBoolQuery)mergeBoolQuery).MustNot.HasAny())
				{
					((IBoolQuery)returnBoolQuery).MustNot = ((IBoolQuery)mergeBoolQuery).MustNot;
					((IBoolQuery)mergeBoolQuery).MustNot = null;
				}

				((IBoolQuery)returnBoolQuery).Must = new[] { targetQuery }.Concat(((IBoolQuery)mergeBoolQuery).Must ?? Empty);
			});
		}
		private static QueryContainer SingleSideAndMerge(QueryContainer targetQuery, QueryContainer mergeQuery)
		{
			var targetBoolQuery = ((IQueryContainer)targetQuery).Bool;
			var mergeBoolQuery = ((IQueryContainer)mergeQuery).Bool;

			if (targetBoolQuery == null) return null;

			var combined = new[] { targetQuery, mergeQuery };
			return CreateReturnQuery((returnQuery, returnBoolQuery) =>
			{
				if (!targetBoolQuery.CanMergeMustAndMustNots() || !mergeBoolQuery.CanMergeMustAndMustNots())
				{
					((IBoolQuery)returnBoolQuery).Must = combined;
					return;
				}

				((IBoolQuery)returnBoolQuery).Must = (((IBoolQuery)targetBoolQuery).Must ?? Empty)
					.Concat(mergeBoolQuery != null
						? (((IBoolQuery)mergeBoolQuery).Must ?? Empty)
						: new[] { mergeQuery })
					.NullIfEmpty();
				((IBoolQuery)returnBoolQuery).MustNot = (((IBoolQuery)targetBoolQuery).MustNot ?? Empty)
					.Concat(mergeBoolQuery != null
						? (((IBoolQuery)mergeBoolQuery).MustNot ?? Empty)
						: Empty
					).NullIfEmpty();

			});
		}
		private static QueryContainer CombineIfNoMergeIsNecessary(
			QueryContainer leftQuery,
			QueryContainer rightQuery,
			QueryContainer[] combined)
		{
			var leftBoolQuery = ((IQueryContainer)leftQuery).Bool;
			var rightBoolQuery = ((IQueryContainer)rightQuery).Bool;
			//if neither side is already a boolquery 
			//  or if all boolqueries are strict.
			//  or if one side is strict and the other is null

			var mergeLeft = !((IQueryContainer)leftQuery).IsStrict && (leftBoolQuery == null || ((IBoolQuery)leftBoolQuery).MinimumShouldMatch == null);
			var mergeRight = !((IQueryContainer)rightQuery).IsStrict && (rightBoolQuery == null || ((IBoolQuery)rightBoolQuery).MinimumShouldMatch == null);

			//no merging is needed just return the combination
			if (
				(leftBoolQuery == null && rightBoolQuery == null)
				|| (!mergeLeft && !mergeRight)
				|| (((IQueryContainer)leftQuery).IsStrict && rightBoolQuery == null)
				|| (((IQueryContainer)rightQuery).IsStrict && leftBoolQuery == null)
				)
			{
				return CreateReturnQuery((returnQuery, returnBoolQuery) => ((IBoolQuery)returnBoolQuery).Must = combined);
			}
			return null;
		}
		private static void JoinShouldOnSide(QueryContainer lbq, QueryContainer rbq, BoolBaseQueryDescriptor bq)
		{
			((IBoolQuery)bq).Should = lbq.MergeShouldQueries(rbq);
		}
		public static QueryContainer operator !(QueryContainer lbq)
		{
			if (lbq == null || ((IQueryContainer)lbq).IsConditionless)
			{
				var newConditionless = new QueryContainer();
				((IQueryContainer)newConditionless).IsConditionless = true;
				return newConditionless;
			}

			var f = new QueryContainer();
			var fq = new BoolBaseQueryDescriptor();
			((IBoolQuery)fq).MustNot = new[] { lbq };

			((IQueryContainer)f).Bool = fq;
			return f;
		}
		public static QueryContainer operator |(QueryContainer leftQuery, QueryContainer rightQuery)
		{
			var defaultQuery = new QueryContainer();
			((IQueryContainer)defaultQuery).IsConditionless = true;
			leftQuery = leftQuery ?? defaultQuery;
			rightQuery = rightQuery ?? defaultQuery;
			var combined = new[] { leftQuery, rightQuery };

			if (combined.Any(bf => ((IQueryContainer)bf).IsConditionless))
				return combined.FirstOrDefault(bf => !((IQueryContainer)bf).IsConditionless) ?? defaultQuery;

			var leftBoolQuery = ((IQueryContainer)leftQuery).Bool;
			var rightBoolQuery = ((IQueryContainer)rightQuery).Bool;


			var f = new QueryContainer();
			var fq = new BoolBaseQueryDescriptor();
			((IBoolQuery)fq).Should = new[] { leftQuery, rightQuery };
			((IQueryContainer)f).Bool = fq;

			var mergeLeft = !((IQueryContainer)leftQuery).IsStrict && (leftBoolQuery == null || ((IBoolQuery)leftBoolQuery).MinimumShouldMatch == null);
			var mergeRight = !((IQueryContainer)rightQuery).IsStrict && (rightBoolQuery == null || ((IBoolQuery)rightBoolQuery).MinimumShouldMatch == null);

			//if neither the left nor the right side represent a bool query join them
			if (((IQueryContainer)leftQuery).Bool == null && ((IQueryContainer)rightQuery).Bool == null)
			{
				((IBoolQuery)fq).Should = leftQuery.MergeShouldQueries(rightQuery);
				return f;
			}
			//if the left or right sight already is a bool query join the non bool query side of the 
			//of the operation onto the other.
			if (((IQueryContainer)leftQuery).Bool != null 
				&& ((IQueryContainer)rightQuery).Bool == null
				&& mergeLeft)
			{
				JoinShouldOnSide(leftQuery, rightQuery, fq);
			}
			else if (((IQueryContainer)rightQuery).Bool != null 
				&& ((IQueryContainer)leftQuery).Bool == null
				&& mergeRight)
			{
				JoinShouldOnSide(rightQuery, leftQuery, fq);
			}
			//both sides already represent a bool query
			else
			{
				//both sides report that we may merge the shoulds
				if (mergeLeft && mergeRight
					&& leftBoolQuery.CanJoinShould()
					&& rightBoolQuery.CanJoinShould())
					((IBoolQuery)fq).Should = leftQuery.MergeShouldQueries(rightQuery);
				else
					//create a new nested bool with two separate should bool sections
					((IBoolQuery)fq).Should = new[] { leftQuery, rightQuery };
			}
			return f;
		}
		/// <summary>
		/// AND's two BaseQueries
		/// </summary>
		/// <returns>A new basequery that represents the AND of the two</returns>
		public static QueryContainer operator &(QueryContainer leftQuery, QueryContainer rightQuery)
		{
			var defaultQuery = new QueryContainer();
			((IQueryContainer)defaultQuery).IsConditionless = true;
			leftQuery = leftQuery ?? defaultQuery;
			rightQuery = rightQuery ?? defaultQuery;
			var combined = new[] { leftQuery, rightQuery };

			//if any of the queries is conditionless return the first one that is not
			//or return the defaultQuery
			if (combined.Any(bf => ((IQueryContainer)bf).IsConditionless))
				return combined.FirstOrDefault(bf => !((IQueryContainer)bf).IsConditionless) ?? defaultQuery;

			//return simple combination of the two if merging is not possible/necessary
			var noMergeQuery = CombineIfNoMergeIsNecessary(leftQuery, rightQuery, combined);
			if (noMergeQuery != null)
				return noMergeQuery;

			//if the left is a strict bool try to merge right on left first 
			var joinStrictLeft = StrictSingleSideAndMerge(leftQuery, rightQuery);
			if (joinStrictLeft != null)
				return joinStrictLeft;

			// if the right side is a strict bool try to merge left on right
			var joinStrictRight = StrictSingleSideAndMerge(rightQuery, leftQuery);
			if (joinStrictRight != null)
				return joinStrictRight;

			// if the left side is a normal bool try to merge right on left
			var joinLeft = SingleSideAndMerge(leftQuery, rightQuery);
			if (joinLeft != null)
				return joinLeft;

			// if the right side is a normal bool try to merge lefft on right
			var joinRight = SingleSideAndMerge(rightQuery, leftQuery);
			return joinRight ?? defaultQuery;
		}