using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace InABox.Core
{
    public static class PredicateExtensions
    {
        /// 
        ///     Begin an expression chain
        /// 
        /// 
        /// Default return value if the chanin is ended early
        /// A lambda expression stub
        public static Expression> Begin(bool value = false)
        {
            if (value)
                return parameter => true; //value cannot be used in place of true/false
            return parameter => false;
        }
        public static Expression> And(this Expression> left,
            Expression> right)
        {
            return CombineLambdas(left, right, ExpressionType.AndAlso);
        }
        public static Expression> Or(this Expression> left, Expression> right)
        {
            return CombineLambdas(left, right, ExpressionType.OrElse);
        }
        #region private
        private static Expression> CombineLambdas(this Expression> left,
            Expression> right, ExpressionType expressionType)
        {
            //Remove expressions created with Begin()
            if (IsExpressionBodyConstant(left))
                return right;
            var p = left.Parameters[0];
            var visitor = new SubstituteParameterVisitor();
            visitor.Sub[right.Parameters[0]] = p;
            Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
            return Expression.Lambda>(body, p);
        }
        private static bool IsExpressionBodyConstant(Expression> left)
        {
            return left.Body.NodeType == ExpressionType.Constant;
        }
        internal class SubstituteParameterVisitor : ExpressionVisitor
        {
            public Dictionary Sub = new Dictionary();
            protected override Expression VisitParameter(ParameterExpression node)
            {
                Expression newValue;
                if (Sub.TryGetValue(node, out newValue)) return newValue;
                return node;
            }
        }
        #endregion
    }
}