public static Expression Equals(Expression memberExpression, Expression searchFor, SearchIntent searchIntent)
        {
            if (searchIntent == SearchIntent.Undefined)
            {
                throw new ArgumentException($"{nameof(searchIntent)} cannot be Undefined", nameof(searchIntent));
            }

            if (searchFor.Type != typeof(string))
            {
                return(Equal(memberExpression, searchFor));
            }

            return(searchIntent == SearchIntent.Database
                ? GetDatabaseStringEquals(memberExpression, searchFor)
                : GetInMemoryStringEquals(memberExpression, searchFor));
        }
        public static Expression Match(Expression memberExpression, object searchValue, SearchIntent searchIntent)
        {
            string matchText = searchValue?.ToString() ?? string.Empty;

            const char wildcardChar   = '*';
            string     wildcardString = wildcardChar.ToString();

            bool matchTextStartsWithWildCard = matchText.StartsWith(wildcardString);
            bool matchTextEndsWithWildCard   = matchText.EndsWith(wildcardString);

            if (matchTextStartsWithWildCard && matchTextEndsWithWildCard)
            {
                return(Contains(memberExpression, Expression.Constant(matchText.Trim(wildcardChar)), searchIntent));
            }

            if (matchTextStartsWithWildCard)
            {
                return(EndsWith(memberExpression, Expression.Constant(matchText.TrimStart(wildcardChar)), searchIntent));
            }

            if (matchTextEndsWithWildCard)
            {
                return(StartsWith(memberExpression, Expression.Constant(matchText.TrimEnd(wildcardChar)), searchIntent));
            }

            return(Equals(memberExpression, Expression.Constant(matchText), searchIntent));
        }
        public static Expression EndsWith(Expression memberExpression, Expression searchFor, SearchIntent searchIntent)
        {
            if (memberExpression.Type != searchFor.Type)
            {
                throw new ArgumentException($"{nameof(memberExpression)} and {nameof(searchFor)} must be expressions of same type. {nameof(memberExpression)} is of type {memberExpression.Type.ToPrettyString()} and {nameof(searchFor)} is of type {searchFor.Type.ToPrettyString()}.");
            }

            if (memberExpression.Type != typeof(string))
            {
                throw new ArgumentException($"{nameof(Contains)} can be used for strings only.");
            }


            return(searchIntent == SearchIntent.Database
                ? GetDatabaseEndsWith(memberExpression, searchFor)
                : GetInMemoryEndsWith(memberExpression, searchFor));
        }
        private static Expression ExpressionFromFieldComparerGroup <T>(FieldComparerGroup fieldComparerGroup, ParameterExpression param, SearchIntent searchIntent)
            where T : class
        {
            Expression search = null;

            foreach (FieldComparer fieldComparer in fieldComparerGroup.FieldComparers)
            {
                // The expression tree we are building
                PropertyInfo propertyInfo = GetPropertyWithValidation <T>(fieldComparer.FieldName);

                if (propertyInfo == null)
                {
                    throw new Exception($"The column '{fieldComparer.FieldName}' does not exist in my dataclass");
                }

                object convertedSearchValue = GetConvertedSearchValue(propertyInfo, fieldComparer);

                Expression searchWithType;

                // ReSharper disable once PossibleNullReferenceException
                if (propertyInfo.PropertyType == typeof(Guid) && Guid.Empty.Equals((Guid)convertedSearchValue))
                {
                    searchWithType       = Expression.Field(null, typeof(Guid), "Empty");
                    convertedSearchValue = Guid.Empty;
                }
                else
                {
                    searchWithType = Expression.Constant(convertedSearchValue);
                }

                MemberExpression memberExpression = Expression.PropertyOrField(param, fieldComparer.FieldName);

                Expression body;
                switch (fieldComparer.Operator)
                {
                case CompareOp.Equals:
                    body = SearcherExpressionExtensions.Equals(memberExpression, searchWithType, searchIntent);
                    break;

                case CompareOp.NotEquals:
                    body = SearcherExpressionExtensions.NotEqual(memberExpression, searchWithType);
                    break;

                case CompareOp.GreaterThanOrEqual:
                    body = SearcherExpressionExtensions.GreaterThanOrEqual(memberExpression, searchWithType);
                    break;

                case CompareOp.LessThanOrEqual:
                    body = SearcherExpressionExtensions.LessThanOrEqual(memberExpression, searchWithType);
                    break;

                case CompareOp.LessThan:
                    body = SearcherExpressionExtensions.LessThan(memberExpression, searchWithType);
                    break;

                case CompareOp.GreaterThan:
                    body = SearcherExpressionExtensions.GreaterThan(memberExpression, searchWithType);
                    break;

                //case CompareOp.EqualsCaseSensitive:
                //    body = SearcherExpressionExtensions.Equal(memberExpression, searchWithType);
                //    break;
                case CompareOp.StartsWith:
                    body = SearcherExpressionExtensions.StartsWith(memberExpression, searchWithType, searchIntent);
                    break;

                //case CompareOp.StartsWithCaseSensitive:
                //    body = SearcherExpressionExtensions.StartsWith(memberExpression, searchWithType);
                //    break;
                case CompareOp.Contains:
                    body = SearcherExpressionExtensions.Contains(memberExpression, searchWithType, searchIntent);
                    break;

                //case CompareOp.ContainsCaseSensitive:
                //    body = SearcherExpressionExtensions.Contains(memberExpression, searchWithType);
                //    break;
                case CompareOp.EndsWith:
                    body = SearcherExpressionExtensions.EndsWith(memberExpression, searchWithType, searchIntent);
                    break;

                //case CompareOp.EndsWithOrdinalIgnoreCase:
                //    body = SearcherExpressionExtensions.EndsWithOrdinalIgnoreCase(memberExpression, searchWithType);
                //    break;
                case CompareOp.Match:
                    body = SearcherExpressionExtensions.Match(memberExpression, convertedSearchValue, searchIntent);
                    break;

                default:
                    throw new Exception($"Search term {fieldComparer.Operator} not supported");
                }

                if (search == null)
                {
                    search = body;
                }
                else
                {
                    search = fieldComparerGroup.Operator == LogicalOp.And
                        ? Expression.AndAlso(search, body)
                        : Expression.OrElse(search, body);
                }
            }

            foreach (FieldComparerGroup subFieldComparerGroup in fieldComparerGroup.FieldComparerGroups)
            {
                var subExpression = ExpressionFromFieldComparerGroup <T>(subFieldComparerGroup, param, searchIntent);
                if (search == null)
                {
                    search = subExpression;
                }
                else
                {
                    search = fieldComparerGroup.Operator == LogicalOp.And
                        ? Expression.AndAlso(search, subExpression)
                        : Expression.OrElse(search, subExpression);
                }
            }

            return(search ?? Expression.Constant(true));
        }
        public static IEnumerable <T> Filter <T>(this IEnumerable <T> items, SearchQuery searchQuery, SearchIntent searchIntent)
            where T : class
        {
            if (searchQuery.GroupBy.Count > 0)
            {
                throw new NotSupportedException($"{nameof(searchQuery.GroupBy)} is not supported for filtering {nameof(SearchQuery)}.");
            }

            Func <T, bool> predicate = CreateSearchExpression <T>(searchQuery, searchIntent).Compile();

            items = items
                    .Where(predicate)
                    .OrderBy(searchQuery.OrderBy);

            if (searchQuery.Skip > 0)
            {
                items = items.Skip(searchQuery.Skip);
            }

            if (searchQuery.Take > 0)
            {
                items = items.Take(searchQuery.Take);
            }

            return(items);
        }
        public static Expression <Func <T, bool> > CreateSearchExpression <T>(SearchQuery searchQuery, SearchIntent searchIntent)
            where T : class
        {
            ParameterExpression param = Expression.Parameter(typeof(T));

            Expression search = ExpressionFromFieldComparerGroup <T>(searchQuery, param, searchIntent);

            return(Expression.Lambda <Func <T, bool> >(search, param));
        }