<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Newtonsoft.Json.Linq;

namespace Chronological
{
    public class Filter
    {
        private readonly bool _singlular;
        private readonly bool _isPredicateString;

        private readonly string _predicateString;

        private readonly List<Filter> _filters;

        private readonly Property _left;
        private readonly string _rightAsString;
        private readonly double? _rightAsDouble;
        private readonly DateTime? _rightAsDateTime;

        private readonly string _operator;

        private Filter(bool singular, List<Filter> filters, Property left, string right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsString = right;
            _operator = filterOperator;
        }

        private Filter(bool singular, List<Filter> filters, Property left, double right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsDouble = right;
            _operator = filterOperator;
        }

        private Filter(bool singular, List<Filter> filters, Property left, DateTime right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsDateTime = right;
            _operator = filterOperator;
        }

        internal static Filter Create<T>(Expression<Func<T, bool>> predicate) where T : new()
        {
            return new Filter(ExpressionToString(predicate.Body));
        }

        private static string ExpressionToString(Expression expression)
        {
            switch (expression)
            {
                case MemberExpression memberExpression:
                    return MemberExpressionToString(memberExpression);
                case BinaryExpression binaryExpression:
                    return BinaryExpressionToString(binaryExpression);
                case NewExpression newExpression:
                    return NewExpressionToString(newExpression);
                case NewArrayExpression newArrayExpression:
                    return NewArrayExpressionToString(newArrayExpression);
                case ConstantExpression constantExpression:
                    return ConstantExpressionToString(constantExpression);
                case MethodCallExpression methodCallExpression:
                    return MethodCallExpressionToString(methodCallExpression);
                case UnaryExpression unaryExpression:
                    return UnaryExpressionToString(unaryExpression);
                default:
                    throw new NotImplementedException();
            }
        }

        private static string NewArrayExpressionToString(NewArrayExpression newArrayExpression)
        {
            var listItems = newArrayExpression.Expressions.Select(ExpressionToString);
            return $"({string.Join(", ", listItems)})";
        }

        private static string MethodCallExpressionToString(MethodCallExpression methodCallExpression)
        {
            switch (methodCallExpression.Method.Name)
            {
                case "Contains":
                    return ContainsExpressionToString(methodCallExpression);
                case "EndsWith":
                    return EndAndStartWithExpressionToString("endsWith", methodCallExpression);
                case "StartsWith":
                    return EndAndStartWithExpressionToString("startsWith", methodCallExpression);
                default:
                    //TODO: more tests around inline methods
                    object result = Expression.Lambda(methodCallExpression).Compile().DynamicInvoke();
                    return ExpressionToString(Expression.Constant(result));
            }
        }

        private static string ContainsExpressionToString(MethodCallExpression methodCallExpression)
        {
            var values = methodCallExpression.Arguments[0];
            var searchedValue = methodCallExpression.Arguments[1];
            return $"({ExpressionToString(searchedValue)} IN {ExpressionToString(values)})";
        }

        private static string EndAndStartWithExpressionToString(string methodName, MethodCallExpression methodCallExpression)
        {
            var testObject = methodCallExpression.Object;
            var arguments = methodCallExpression.Arguments;
            var testValue = arguments[0];

            bool isCaseSensitive;
            if (arguments.Count < 2)
                return $"({methodName}_cs({ExpressionToString(testObject)}, {ExpressionToString(testValue)}))";

            var stringComparisonExpression = arguments[1];
            var stringComparison = (StringComparison)Expression.Lambda(stringComparisonExpression).Compile().DynamicInvoke();
            isCaseSensitive = stringComparison == StringComparison.CurrentCulture ||
                              stringComparison == StringComparison.Ordinal;
            return
                $"({methodName}{(isCaseSensitive ? "_cs" : string.Empty)}({ExpressionToString(testObject)}, {ExpressionToString(testValue)}))";
        }

        private static string BinaryExpressionToString(BinaryExpression binaryExpression)
        {
            var leftSide = binaryExpression.Left;
            var rightSide = binaryExpression.Right;
            var comparison = binaryExpression.NodeType;

            return $"({ExpressionToString(leftSide)} {ComparisonToString(comparison)} {ExpressionToString(rightSide)})";
        }

        private static string MemberExpressionToString(MemberExpression memberExpression)
        {
            if (memberExpression.Type == typeof(DateTime))
            {
                if (memberExpression.Member.Name == "UtcNow")
                {
                    return BuiltIn.Function.UtcNow;
                }
                // TODO: throw exception for any other DateTime type as not supported
            }

            if (memberExpression.Expression.NodeType == ExpressionType.Parameter)
            {
                var eventFieldMemberExpression = new EventFieldMemberExpression(memberExpression);

                return eventFieldMemberExpression.EscapedEventFieldName;
            }

            if (memberExpression.Expression.NodeType == ExpressionType.Constant)
            {
                object result = Expression.Lambda(memberExpression).Compile().DynamicInvoke();
                return ConvertObjectToString(result);
            }

            throw new NotImplementedException();
        }

        private static string NewExpressionToString(NewExpression newExpression)
        {
            object result = Expression.Lambda(newExpression).Compile().DynamicInvoke();
            return ConvertObjectToString(result);
        }

        private static string UnaryExpressionToString(UnaryExpression unaryExpression)
        {
            object result = Expression.Lambda(unaryExpression).Compile().DynamicInvoke();
            return ConvertObjectToString(result);
        }

        private static string ConstantExpressionToString(ConstantExpression constantExpression)
        {
            return ConvertObjectToString(constantExpression.Value);
        }

        private static string ConvertObjectToString(object toConvert)
        {
            switch (toConvert)
            {
                case double d:
                    return d.ToString();
                case string s:
                    return $"'{s}'";
                case bool b:
                    return b ? "TRUE" : "FALSE";
                case TimeSpan ts:
                    return $"ts'P0Y0M{ts.Days}DT{ts.Hours}H{ts.Minutes}M{ts.Seconds}.{ts.Milliseconds}S'";
                case DateTime dt:
                    return $"dt'{dt:O}'";
                default:
                    throw new NotImplementedException();
            }
        }

        private static string ComparisonToString(ExpressionType comparison)
        {
            switch (comparison)
            {
                case (ExpressionType.LessThan):
                    return "<";
                case (ExpressionType.LessThanOrEqual):
                    return "<=";
                case (ExpressionType.GreaterThan):
                    return ">";
                case (ExpressionType.GreaterThanOrEqual):
                    return ">=";
                case (ExpressionType.Equal):
                    return "=";
                case (ExpressionType.NotEqual):
                    return "!=";<commit_msg>Implemented string.Contains in filter<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Newtonsoft.Json.Linq;

namespace Chronological
{
    public class Filter
    {
        private readonly bool _singlular;
        private readonly bool _isPredicateString;

        private readonly string _predicateString;

        private readonly List<Filter> _filters;

        private readonly Property _left;
        private readonly string _rightAsString;
        private readonly double? _rightAsDouble;
        private readonly DateTime? _rightAsDateTime;

        private readonly string _operator;

        private Filter(bool singular, List<Filter> filters, Property left, string right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsString = right;
            _operator = filterOperator;
        }

        private Filter(bool singular, List<Filter> filters, Property left, double right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsDouble = right;
            _operator = filterOperator;
        }

        private Filter(bool singular, List<Filter> filters, Property left, DateTime right, string filterOperator)
        {
            _isPredicateString = false;
            _singlular = singular;
            _filters = filters;
            _left = left;
            _rightAsDateTime = right;
            _operator = filterOperator;
        }

        internal static Filter Create<T>(Expression<Func<T, bool>> predicate) where T : new()
        {
            return new Filter(ExpressionToString(predicate.Body));
        }

        private static string ExpressionToString(Expression expression)
        {
            switch (expression)
            {
                case MemberExpression memberExpression:
                    return MemberExpressionToString(memberExpression);
                case BinaryExpression binaryExpression:
                    return BinaryExpressionToString(binaryExpression);
                case NewExpression newExpression:
                    return NewExpressionToString(newExpression);
                case NewArrayExpression newArrayExpression:
                    return NewArrayExpressionToString(newArrayExpression);
                case ConstantExpression constantExpression:
                    return ConstantExpressionToString(constantExpression);
                case MethodCallExpression methodCallExpression:
                    return MethodCallExpressionToString(methodCallExpression);
                case UnaryExpression unaryExpression:
                    return UnaryExpressionToString(unaryExpression);
                default:
                    throw new NotImplementedException();
            }
        }

        private static string NewArrayExpressionToString(NewArrayExpression newArrayExpression)
        {
            var listItems = newArrayExpression.Expressions.Select(ExpressionToString);
            return $"({string.Join(", ", listItems)})";
        }

        private static string MethodCallExpressionToString(MethodCallExpression methodCallExpression)
        {
            switch (methodCallExpression.Method.Name)
            {
                case "Contains":
                    return ContainsExpressionToString(methodCallExpression);
                case "EndsWith":
                    return EndAndStartWithExpressionToString("endsWith", methodCallExpression);
                case "StartsWith":
                    return EndAndStartWithExpressionToString("startsWith", methodCallExpression);
                default:
                    //TODO: more tests around inline methods
                    object result = Expression.Lambda(methodCallExpression).Compile().DynamicInvoke();
                    return ExpressionToString(Expression.Constant(result));
            }
        }

        private static string ContainsExpressionToString(MethodCallExpression methodCallExpression)
        {
            if (methodCallExpression.Arguments.Count == 1)
            {
                var value = methodCallExpression.Arguments[0];
                object result = Expression.Lambda(value).Compile().DynamicInvoke();
                if (value.Type == typeof(string))
                {
                    return $"(matchesRegex({ExpressionToString(methodCallExpression.Object)}, '^*{ConvertObjectToString(result, false)}*'))";
                }
                throw new NotSupportedException();
            }

            var values = methodCallExpression.Arguments[0];
            var searchedValue = methodCallExpression.Arguments[1];
            return $"({ExpressionToString(searchedValue)} IN {ExpressionToString(values)})";
        }

        private static string EndAndStartWithExpressionToString(string methodName, MethodCallExpression methodCallExpression)
        {
            var testObject = methodCallExpression.Object;
            var arguments = methodCallExpression.Arguments;
            var testValue = arguments[0];

            bool isCaseSensitive;
            if (arguments.Count < 2)
                return $"({methodName}_cs({ExpressionToString(testObject)}, {ExpressionToString(testValue)}))";

            var stringComparisonExpression = arguments[1];
            var stringComparison = (StringComparison)Expression.Lambda(stringComparisonExpression).Compile().DynamicInvoke();
            isCaseSensitive = stringComparison == StringComparison.CurrentCulture ||
                              stringComparison == StringComparison.Ordinal;
            return
                $"({methodName}{(isCaseSensitive ? "_cs" : string.Empty)}({ExpressionToString(testObject)}, {ExpressionToString(testValue)}))";
        }

        private static string BinaryExpressionToString(BinaryExpression binaryExpression)
        {
            var leftSide = binaryExpression.Left;
            var rightSide = binaryExpression.Right;
            var comparison = binaryExpression.NodeType;

            return $"({ExpressionToString(leftSide)} {ComparisonToString(comparison)} {ExpressionToString(rightSide)})";
        }

        private static string MemberExpressionToString(MemberExpression memberExpression)
        {
            if (memberExpression.Type == typeof(DateTime))
            {
                if (memberExpression.Member.Name == "UtcNow")
                {
                    return BuiltIn.Function.UtcNow;
                }
                // TODO: throw exception for any other DateTime type as not supported
            }

            if (memberExpression.Expression.NodeType == ExpressionType.Parameter)
            {
                var eventFieldMemberExpression = new EventFieldMemberExpression(memberExpression);

                return eventFieldMemberExpression.EscapedEventFieldName;
            }

            if (memberExpression.Expression.NodeType == ExpressionType.Constant)
            {
                object result = Expression.Lambda(memberExpression).Compile().DynamicInvoke();
                return ConvertObjectToString(result);
            }

            throw new NotImplementedException();
        }

        private static string NewExpressionToString(NewExpression newExpression)
        {
            object result = Expression.Lambda(newExpression).Compile().DynamicInvoke();
            return ConvertObjectToString(result);
        }

        private static string UnaryExpressionToString(UnaryExpression unaryExpression)
        {
            object result = Expression.Lambda(unaryExpression).Compile().DynamicInvoke();
            return ConvertObjectToString(result);
        }

        private static string ConstantExpressionToString(ConstantExpression constantExpression)
        {
            return ConvertObjectToString(constantExpression.Value);
        }

        private static string ConvertObjectToString(object toConvert, bool addQuotes = true)
        {
            switch (toConvert)
            {
                case double d:
                    return d.ToString();
                case string s:
                    return addQuotes ? $"'{s}'" : s;
                case bool b:
                    return b ? "TRUE" : "FALSE";
                case TimeSpan ts:
                    return $"ts'P0Y0M{ts.Days}DT{ts.Hours}H{ts.Minutes}M{ts.Seconds}.{ts.Milliseconds}S'";
                case DateTime dt:
                    return $"dt'{dt:O}'";
                default:
                    throw new NotImplementedException();
            }
        }

        private static string ComparisonToString(ExpressionType comparison)
        {
            switch (comparison)
            {
                case (ExpressionType.LessThan):
                    return "<";
                case (ExpressionType.LessThanOrEqual):
                    return "<=";
                case (ExpressionType.GreaterThan):
                    return ">";
                case (ExpressionType.GreaterThanOrEqual):
                    return ">=";
                case (ExpressionType.Equal):
                    return "=";
                case (ExpressionType.NotEqual):
                    return "!=";
{'fraction_non_alphanumeric': 0.07247489987381357, 'fraction_numerical': 0.0008778186207274921, 'mean_word_length': 1.9818419761164732, 'pattern_counts': {'":': 6, '<': 23, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1514304', 'n_tokens_mistral': 3944, 'n_tokens_neox': 3729, 'n_words': 885}