Expression VisitDistance(Expression left, Expression right)
        {
            var cm = ConstantMemberPair.Create(left, right);

            if (cm != null)
            {
                var value = ((GeographyPoint)cm.ConstantExpression.Value);

                return(new CriteriaExpression(new DistanceCriteria(Mapping.GetFieldName(SourceType, cm.MemberExpression), cm.MemberExpression.Member, value, null)));
            }

            throw new NotSupportedException("Distance must be between a Member and a Constant");
        }
        Expression VisitContains(string methodName, Expression left, Expression right, TermsOperator executionMode)
        {
            var cm = ConstantMemberPair.Create(left, right);

            if (cm != null)
            {
                var values = ((IEnumerable)cm.ConstantExpression.Value).Cast <object>().ToArray();

                return(new CriteriaExpression(TermsCriteria.Build(executionMode, Mapping.GetFieldName(SourceType, cm.MemberExpression), cm.MemberExpression.Member, values)));
            }

            throw new NotSupportedException(methodName + " must be between a Member and a Constant");
        }
        Expression CreateExists(ConstantMemberPair cm, bool positiveTest)
        {
            var fieldName = Mapping.GetFieldName(SourceType, UnwrapNullableMethodExpression(cm.MemberExpression));

            var value = cm.ConstantExpression.Value ?? false;

            if (value.Equals(positiveTest))
            {
                return(new CriteriaExpression(new ExistsCriteria(fieldName)));
            }

            if (value.Equals(!positiveTest))
            {
                return(new CriteriaExpression(new MissingCriteria(fieldName)));
            }

            throw new NotSupportedException("A null test Expression must have a member being compared to a bool or null");
        }
        Expression VisitNotEqual(Expression left, Expression right)
        {
            var booleanEquals = VisitCriteriaEquals(left, right, false);

            if (booleanEquals != null)
            {
                return(booleanEquals);
            }

            var cm = ConstantMemberPair.Create(left, right);

            if (cm == null)
            {
                throw new NotSupportedException("A not-equal expression must be between a constant and a member");
            }

            return(cm.IsNullTest
                ? CreateExists(cm, false)
                : new CriteriaExpression(new ComparisonCriteria(Mapping.GetFieldName(SourceType, cm.MemberExpression),
                                                                cm.MemberExpression.Member, Comparison.NotEqual, cm.ConstantExpression.Value)));
        }
        Expression VisitEquals(Expression left, Expression right)
        {
            var booleanEquals = VisitCriteriaEquals(left, right, true);

            if (booleanEquals != null)
            {
                return(booleanEquals);
            }

            var cm = ConstantMemberPair.Create(left, right);

            if (cm != null)
            {
                return(cm.IsNullTest
                    ? CreateExists(cm, true)
                    : new CriteriaExpression(new ComparisonCriteria(Mapping.GetFieldName(SourceType, cm.MemberExpression),
                                                                    cm.MemberExpression.Member, Comparison.Equal, cm.ConstantExpression.Value)));
            }

            throw new NotSupportedException("Equality must be between a Member and a Constant");
        }
        Expression VisitRange(Comparison rangeComparison, Expression left, Expression right)
        {
            var existingCriteriaExpression = left as CriteriaExpression;

            if (existingCriteriaExpression == null)
            {
                var inverted = left is ConstantExpression;

                var cm = ConstantMemberPair.Create(left, right);

                if (cm == null)
                {
                    throw new NotSupportedException("A {0} must test a constant against a member");
                }

                var field = Mapping.GetFieldName(SourceType, cm.MemberExpression);
                var comparisonCriteria = new ComparisonCriteria(field, cm.MemberExpression.Member, rangeComparison, cm.ConstantExpression.Value);

                return(new CriteriaExpression(inverted ? comparisonCriteria.Negate() : comparisonCriteria));
            }
            else
            {
                var distanceCriteria = existingCriteriaExpression.Criteria as DistanceCriteria;

                if (distanceCriteria != null)
                {
                    var constantExpression = right as ConstantExpression;

                    if (constantExpression == null)
                    {
                        throw new NotSupportedException($"A {right} must test a constant against a member");
                    }

                    distanceCriteria.ReplaceComparison(rangeComparison, constantExpression.Value);
                }

                return(existingCriteriaExpression);
            }
        }