// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Common.CommandTrees.Internal
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder;
    using System.Data.Entity.Core.Common.Utils;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Utilities;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;

    /// <summary>
    ///     Utility class that walks a mapping view and returns a simplified expression with projection
    ///     nodes collapsed. Specifically recognizes the following common pattern in mapping views:
    ///     outerProject(outerBinding(innerProject(innerBinding, innerNew)), outerProjection)
    ///     Recognizes simple disciminator patterns of the form:
    ///     select
    ///     case when Disc = value1 then value Type1(...)
    ///     case when Disc = value2 then value Type2(...)
    ///     ...
    ///     Recognizes redundant case statement of the form:
    ///     select
    ///     case when (case when Predicate1 then true else false) ...
    /// </summary>
    internal class ViewSimplifier
    {
        internal static DbQueryCommandTree SimplifyView(EntitySetBase extent, DbQueryCommandTree view)
        {
            var vs = new ViewSimplifier(extent);
            view = vs.Simplify(view);
            return view;
        }

        private readonly EntitySetBase extent;

        private ViewSimplifier(EntitySetBase viewTarget)
        {
            extent = viewTarget;
        }

        private DbQueryCommandTree Simplify(DbQueryCommandTree view)
        {
            var simplifier = PatternMatchRuleProcessor.Create(
                // determines if an expression is of the form outerProject(outerProjection(innerProject(innerNew)))
                PatternMatchRule.Create(_patternCollapseNestedProjection, CollapseNestedProjection),
                // A case statement can potentially be simplified
                PatternMatchRule.Create(_patternCase, SimplifyCaseStatement),
                // Nested TPH discriminator pattern can be converted to the expected TPH discriminator pattern
                PatternMatchRule.Create(_patternNestedTphDiscriminator, SimplifyNestedTphDiscriminator),
                // Entity constructors may be augmented with FK-based related entity refs
                PatternMatchRule.Create(_patternEntityConstructor, AddFkRelatedEntityRefs)
                );

            var queryExpression = view.Query;
            queryExpression = simplifier(queryExpression);

            view = DbQueryCommandTree.FromValidExpression(view.MetadataWorkspace, view.DataSpace, queryExpression);
            return view;
        }

        #region Navigation simplification support by adding FK-based related entity refs

        private static readonly Func<DbExpression, bool> _patternEntityConstructor =
            Patterns.MatchProject(
                Patterns.AnyExpression,
                Patterns.And(
                    Patterns.MatchEntityType,
                    Patterns.Or
                        (
                            Patterns.MatchNewInstance(),
                            Patterns.MatchCase(
                                Patterns.AnyExpressions, Patterns.MatchForAll(Patterns.MatchNewInstance()), Patterns.MatchNewInstance())
                        )
                    )
                );

        private bool doNotProcess;

        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        private DbExpression AddFkRelatedEntityRefs(DbExpression viewConstructor)
        {
            // If the extent being simplified is not a C-Space entity set, or if it has already
            // been processed by the simplifier, then keep the original expression by returning
            // null.
            //
            if (doNotProcess)
            {
                return null;
            }

            if (extent.BuiltInTypeKind != BuiltInTypeKind.EntitySet
                ||
                extent.EntityContainer.DataSpace != DataSpace.CSpace)
            {
                doNotProcess = true;
                return null;
            }

            // Get a reference to the entity set being simplified, and find all the foreign key
            // (foreign key) associations for which the association set references that entity set,
            // with either association end. 
            //
            var targetSet = (EntitySet)extent;
            var relSets =
                targetSet.EntityContainer.BaseEntitySets
                         .Where(es => es.BuiltInTypeKind == BuiltInTypeKind.AssociationSet)
                         .Cast<AssociationSet>()
                         .Where(
                             assocSet =>
                             assocSet.ElementType.IsForeignKey &&
                             assocSet.AssociationSetEnds.Any(se => se.EntitySet == targetSet)
                    )
                         .ToList();

            // If no foreign key association sets that reference the entity set are present, then
            // no further processing is necessary, because FK-based related entity references cannot
            // be computed and added to the entities constructed for the entity set.
            if (relSets.Count == 0)
            {
                doNotProcess = true;
                return null;
            }

            // For every relationship set that references this entity set, the relationship type and
            // foreign key constraint are used to determine if the entity set is the dependent set.
            // If it is the dependent set, then it is possible to augment the view definition with a
            // related entity ref that represents the navigation of the relationship set's relationship
            // from the dependent end (this entity set) to the the principal end (the entity set that
            // is referenced by the other association set end of the relationship set).
            //
            var principalSetsAndDependentTypes = new HashSet<Tuple<EntityType, AssociationSetEnd, ReferentialConstraint>>();
            foreach (var relSet in relSets)
            {
                // Retrieve the single referential constraint from the foreign key association, and
                // use it to determine whether the association set end that represents the dependent
                // end of the association references this entity set.
                //
                var fkConstraint = relSet.ElementType.ReferentialConstraints[0];
                var dependentSetEnd = relSet.AssociationSetEnds[fkConstraint.ToRole.Name];

                if (dependentSetEnd.EntitySet == targetSet)
                {
                    var requiredSourceNavType =
                        (EntityType)TypeHelpers.GetEdmType<RefType>(dependentSetEnd.CorrespondingAssociationEndMember.TypeUsage).ElementType;
                    var principalSetEnd = relSet.AssociationSetEnds[fkConstraint.FromRole.Name];

                    // Record the entity type that an element of this dependent entity set must have in order
                    // to be a valid navigation source for the relationship set's relationship, along with the
                    // association set end for the destination (principal) end of the navigation and the FK
                    // constraint that is associated with the relationship type. This information may be used
                    // later to construct a related entity ref for any entity constructor expression in the view
                    // that produces an entity of the required source type or a subtype.
                    //
                    principalSetsAndDependentTypes.Add(Tuple.Create(requiredSourceNavType, principalSetEnd, fkConstraint));
                }
            }

            // If no foreign key association sets that use the entity set as the dependent set are present,
            // then no further processing is possible, since FK-based related entity refs can only be added
            // to the view definition for navigations from the dependent end of the relationship to the principal.
            //
            if (principalSetsAndDependentTypes.Count == 0)
            {
                doNotProcess = true;
                return null;
            }

            // This rule supports a view that is capped with a projection of the form
            // (input).Project(x => new Entity()) 
            // or
            // (input).Project(x => CASE WHEN (condition1) THEN new Entity1() ELSE WHEN (condition2) THEN new Entity2()... ELSE new EntityN())
            // where every new instance expression Entity1()...EntityN() constructs an entity of a type
            // that is compatible with the entity set's element type.
            // Here, the list of all DbNewInstanceExpressions contained in the projection is remembered,
            // along with any CASE statement conditions, if present. These expressions will be updated
            // if necessary and used to build a new capping projection if any of the entity constructors
            // are augmented with FK-based related entity references.
            //
            var entityProject = (DbProjectExpression)viewConstructor;
            var constructors = new List<DbNewInstanceExpression>();
            List<DbExpression> conditions = null;
            if (entityProject.Projection.ExpressionKind
                == DbExpressionKind.Case)
            {
                // If the projection is a DbCaseExpression, then every result must be a DbNewInstanceExpression
                var discriminatedConstructor = (DbCaseExpression)entityProject.Projection;
                conditions = new List<DbExpression>(discriminatedConstructor.When.Count);
                for (var idx = 0; idx < discriminatedConstructor.When.Count; idx++)
                {
                    conditions.Add(discriminatedConstructor.When[idx]);
                    constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Then[idx]);
                }
                constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Else);
            }
            else
            {
                // Otherwise, the projection must be a single DbNewInstanceExpression
                constructors.Add((DbNewInstanceExpression)entityProject.Projection);
            }

            var rebuildView = false;
            for (var idx = 0; idx < constructors.Count; idx++)
            {
                var entityConstructor = constructors[idx];
                var constructedEntityType = TypeHelpers.GetEdmType<EntityType>(entityConstructor.ResultType);

                var relatedRefs =
                    principalSetsAndDependentTypes
                        .Where(psdt => constructedEntityType == psdt.Item1 || constructedEntityType.IsSubtypeOf(psdt.Item1))
                        .Select(
                            psdt => RelatedEntityRefFromAssociationSetEnd(constructedEntityType, entityConstructor, psdt.Item2, psdt.Item3))
                        .ToList();

                if (relatedRefs.Count > 0)
                {
                    if (entityConstructor.HasRelatedEntityReferences)
                    {
                        relatedRefs = entityConstructor.RelatedEntityReferences.Concat(relatedRefs).ToList();
                    }

                    entityConstructor = DbExpressionBuilder.CreateNewEntityWithRelationshipsExpression(
                        constructedEntityType, entityConstructor.Arguments, relatedRefs);
                    constructors[idx] = entityConstructor;
                    rebuildView = true;
                }
            }

            // Default to returning null to indicate that this rule did not produce a modified expression
            //
            DbExpression result = null;
            if (rebuildView)
            {
                // rebuildView is true, so entity constructing DbNewInstanceExpression(s) were encountered
                // and updated with additional related entity refs. The DbProjectExpression that caps the
                // view definition therefore needs to be rebuilt and returned as the result of this rule.
                //
                if (conditions != null)
                {
                    // The original view definition projection was a DbCaseExpression.
                    // The new expression is also a DbCaseExpression that uses the conditions from the 
                    // original expression together with the updated result expressions to produce the
                    // new capping projection.
                    //
                    var whens = new List<DbExpression>(conditions.Count);
                    var thens = new List<DbExpression>(conditions.Count);
                    for (var idx = 0; idx < conditions.Count; idx++)
                    {
                        whens.Add(conditions[idx]);
                        thens.Add(constructors[idx]);
                    }

                    result = entityProject.Input.Project(DbExpressionBuilder.Case(whens, thens, constructors[conditions.Count]));
                }
                else
                {
                    // Otherwise, the capping projection consists entirely of the updated DbNewInstanceExpression.
                    //
                    result = entityProject.Input.Project(constructors[0]);
                }
            }

            // Regardless of whether or not the view was updated, this rule should not be applied again during rule processing
            doNotProcess = true;
            return result;
        }

        private static DbRelatedEntityRef RelatedEntityRefFromAssociationSetEnd(
            EntityType constructedEntityType, DbNewInstanceExpression entityConstructor, AssociationSetEnd principalSetEnd,
            ReferentialConstraint fkConstraint)
        {
            var principalEntityType = (EntityType)TypeHelpers.GetEdmType<RefType>(fkConstraint.FromRole.TypeUsage).ElementType;
            IList<DbExpression> principalKeyValues = null;

            // Create Entity Property/DbExpression value pairs from the entity constructor DbExpression,
            // then join these with the principal/dependent property pairs from the FK constraint
            // to produce principal property name/DbExpression value pairs from which to create the principal ref.
            //
            // Ideally the code would be as below, but anonymous types break asmmeta:
            //var keyPropAndValue =
            //    from pv in constructedEntityType.Properties.Select((p, idx) => new { DependentProperty = p, Value = entityConstructor.Arguments[idx] })
            //    join ft in fkConstraint.FromProperties.Select((fp, idx) => new { PrincipalProperty = fp, DependentProperty = fkConstraint.ToProperties[idx] })
            //    on pv.DependentProperty equals ft.DependentProperty
            //    select new { PrincipalProperty = ft.PrincipalProperty.Name, Value = pv.Value };
            //
            var keyPropAndValue =
                from pv in constructedEntityType.Properties.Select((p, idx) => Tuple.Create(p, entityConstructor.Arguments[idx]))
                // new { DependentProperty = p, Value = entityConstructor.Arguments[idx] })
                join ft in fkConstraint.FromProperties.Select((fp, idx) => Tuple.Create(fp, fkConstraint.ToProperties[idx]))
                    //new { PrincipalProperty = fp, DependentProperty = fkConstraint.ToProperties[idx] })
                    on pv.Item1 equals ft.Item2
                //pv.DependentProperty equals ft.DependentProperty
                select Tuple.Create(ft.Item1.Name, pv.Item2); // new { PrincipalProperty = ft.PrincipalProperty.Name, Value = pv.Value };

            // If there is only a single property in the principal's key, then there is no ordering concern.
            // Otherwise, create a dictionary of principal key property name to DbExpression value so that
            // when used as the arguments to the ref expression, the dependent property values - used here
            // as principal key property values - are in the correct order, which is the same order as the
            // key members themselves.
            //
            if (fkConstraint.FromProperties.Count == 1)
            {
                var singleKeyNameAndValue = keyPropAndValue.Single();
                Debug.Assert(singleKeyNameAndValue.Item1 == fkConstraint.FromProperties[0].Name, "Unexpected single key property name");
                principalKeyValues = new[] { singleKeyNameAndValue.Item2 };
            }
            else
            {
                var keyValueMap = keyPropAndValue.ToDictionary(pav => pav.Item1, pav => pav.Item2, StringComparer.Ordinal);
                principalKeyValues = principalEntityType.KeyMemberNames.Select(memberName => keyValueMap[memberName]).ToList();
            }

            // Create the ref to the principal entity based on the (now correctly ordered) key value expressions.
            //
            var principalRef = principalSetEnd.EntitySet.CreateRef(principalEntityType, principalKeyValues);
            var result = DbExpressionBuilder.CreateRelatedEntityRef(fkConstraint.ToRole, fkConstraint.FromRole, principalRef);

            return result;
        }

        #endregion

        #region Nested TPH Discriminator simplification

        /// <summary>
        ///     Matches the nested TPH discriminator pattern produced by view generation
        /// </summary>
        private static readonly Func<DbExpression, bool> _patternNestedTphDiscriminator =
            Patterns.MatchProject(
                Patterns.MatchFilter(
                    Patterns.MatchProject(
                        Patterns.MatchFilter(
                            Patterns.AnyExpression,
                            Patterns.Or(
                                Patterns.MatchKind(DbExpressionKind.Equals),
                                Patterns.MatchKind(DbExpressionKind.Or)
                                )
                            ),
                        Patterns.And(
                            Patterns.MatchRowType,
                            Patterns.MatchNewInstance(
                                Patterns.MatchForAll(
                                    Patterns.Or(
                                        Patterns.And(
                                            Patterns.MatchNewInstance(),
                                            Patterns.MatchComplexType
                                            ),
                                        Patterns.MatchKind(DbExpressionKind.Property),
                                        Patterns.MatchKind(DbExpressionKind.Case)
                                        )
                                    )
                                )
                            )
                        ),
                    Patterns.Or(
                        Patterns.MatchKind(DbExpressionKind.Property),
                        Patterns.MatchKind(DbExpressionKind.Or)
                        )
                    ),
                Patterns.And(
                    Patterns.MatchEntityType,
                    Patterns.MatchCase(
                        Patterns.MatchForAll(Patterns.MatchKind(DbExpressionKind.Property)),
                        Patterns.MatchForAll(Patterns.MatchKind(DbExpressionKind.NewInstance)),
                        Patterns.MatchKind(DbExpressionKind.NewInstance)
                        )
                    )
                );

        /// <summary>
        ///     Converts the DbExpression equivalent of:
        ///     SELECT CASE
        ///     WHEN a._from0 THEN SUBTYPE1()
        ///     ...
        ///     WHEN a._from[n-2] THEN SUBTYPE_n-1()
        ///     ELSE SUBTYPE_n
        ///     FROM
        ///     SELECT
        ///     b.C1..., b.Cn
        ///     CASE WHEN b.Discriminator = SUBTYPE1_Value THEN true ELSE false AS _from0
        ///     ...
        ///     CASE WHEN b.Discriminator = SUBTYPE_n_Value THEN true ELSE false AS _from[n-1]
        ///     FROM TSet AS b
        ///     WHERE b.Discriminator = SUBTYPE1_Value... OR x.Discriminator = SUBTYPE_n_Value
        ///     AS a
        ///     WHERE a._from0... OR a._from[n-1]
        ///     into the DbExpression equivalent of the following, which is matched as a TPH discriminator
        ///     by the <see cref="System.Data.Entity.Core.Mapping.ViewGeneration.GeneratedView" /> class and so allows a
        ///     <see
        ///         cref="System.Data.Entity.Core.Mapping.ViewGeneration.DiscriminatorMap" />
        ///     to be produced for the view, which would not otherwise be possible. Note that C1 through Cn
        ///     are only allowed to be scalars or complex type constructors based on direct property references
        ///     to the store entity set's scalar properties.
        ///     SELECT CASE
        ///     WHEN y.Discriminator = SUBTTYPE1_Value THEN SUBTYPE1()
        ///     ...
        ///     WHEN y.Discriminator = SUBTYPE_n-1_Value THEN SUBTYPE_n-1()
        ///     ELSE SUBTYPE_n()
        ///     FROM
        ///     SELECT x.C1..., x.Cn, Discriminator FROM TSet AS x
        ///     WHERE x.Discriminator = SUBTYPE1_Value... OR x.Discriminator = SUBTYPE_n_Value
        ///     AS y
        /// </summary>
        [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private static DbExpression SimplifyNestedTphDiscriminator(DbExpression expression)
        {
            var entityProjection = (DbProjectExpression)expression;
            var booleanColumnFilter = (DbFilterExpression)entityProjection.Input.Expression;
            var rowProjection = (DbProjectExpression)booleanColumnFilter.Input.Expression;
            var discriminatorFilter = (DbFilterExpression)rowProjection.Input.Expression;

            var predicates = FlattenOr(booleanColumnFilter.Predicate).ToList();
            var propertyPredicates =
                predicates.OfType<DbPropertyExpression>()
                          .Where(
                              px => px.Instance.ExpressionKind == DbExpressionKind.VariableReference &&
                                    ((DbVariableReferenceExpression)px.Instance).VariableName == booleanColumnFilter.Input.VariableName)
                          .ToList();
            if (predicates.Count
                != propertyPredicates.Count)
            {
                return null;
            }

            var predicateColumnNames = propertyPredicates.Select(px => px.Property.Name).ToList();

            var discriminatorPredicates = new Dictionary<object, DbComparisonExpression>();
            if (!TypeSemantics.IsEntityType(discriminatorFilter.Input.VariableType)
                ||
                !TryMatchDiscriminatorPredicate(discriminatorFilter, (compEx, discValue) => discriminatorPredicates.Add(discValue, compEx)))
            {
                return null;
            }

            var discriminatorProp = (EdmProperty)((DbPropertyExpression)(discriminatorPredicates.First().Value).Left).Property;
            var rowConstructor = (DbNewInstanceExpression)rowProjection.Projection;
            var resultRow = TypeHelpers.GetEdmType<RowType>(rowConstructor.ResultType);
            var inputPredicateMap = new Dictionary<string, DbComparisonExpression>();
            var selectorPredicateMap = new Dictionary<string, DbComparisonExpression>();
            var columnValues = new Dictionary<string, DbExpression>(rowConstructor.Arguments.Count);
            for (var idx = 0; idx < rowConstructor.Arguments.Count; idx++)
            {
                var propName = resultRow.Properties[idx].Name;
                var columnVal = rowConstructor.Arguments[idx];
                if (predicateColumnNames.Contains(propName))
                {
                    if (columnVal.ExpressionKind
                        != DbExpressionKind.Case)
                    {
                        return null;
                    }
                    var casePredicate = (DbCaseExpression)columnVal;
                    if (casePredicate.When.Count != 1
                        ||
                        !TypeSemantics.IsBooleanType(casePredicate.Then[0].ResultType)
                        || !TypeSemantics.IsBooleanType(casePredicate.Else.ResultType)
                        ||
                        casePredicate.Then[0].ExpressionKind != DbExpressionKind.Constant
                        || casePredicate.Else.ExpressionKind != DbExpressionKind.Constant
                        ||
                        (bool)((DbConstantExpression)casePredicate.Then[0]).Value != true
                        || (bool)((DbConstantExpression)casePredicate.Else).Value)
                    {
                        return null;
                    }

                    DbPropertyExpression comparedProp;
                    object constValue;
                    if (
                        !TryMatchPropertyEqualsValue(
                            casePredicate.When[0], rowProjection.Input.VariableName, out comparedProp, out constValue)
                        ||
                        comparedProp.Property != discriminatorProp
                        ||
                        !discriminatorPredicates.ContainsKey(constValue))
                    {
                        return null;
                    }

                    inputPredicateMap.Add(propName, discriminatorPredicates[constValue]);
                    selectorPredicateMap.Add(propName, (DbComparisonExpression)casePredicate.When[0]);
                }
                else
                {
                    columnValues.Add(propName, columnVal);
                }
            }

            // Build a new discriminator-based filter that only includes the same rows allowed by the higher '_from0' column-based filter
            var newDiscriminatorPredicate = Helpers.BuildBalancedTreeInPlace(
                new List<DbExpression>(inputPredicateMap.Values), (left, right) => left.Or(right));
            discriminatorFilter = discriminatorFilter.Input.Filter(newDiscriminatorPredicate);

            var entitySelector = (DbCaseExpression)entityProjection.Projection;
            var newWhens = new List<DbExpression>(entitySelector.When.Count);
            var newThens = new List<DbExpression>(entitySelector.Then.Count);

            for (var idx = 0; idx < entitySelector.When.Count; idx++)
            {
                var propWhen = (DbPropertyExpression)entitySelector.When[idx];
                var entityThen = (DbNewInstanceExpression)entitySelector.Then[idx];

                DbComparisonExpression discriminatorWhen;
                if (!selectorPredicateMap.TryGetValue(propWhen.Property.Name, out discriminatorWhen))
                {
                    return null;
                }
                newWhens.Add(discriminatorWhen);

                var inputBoundEntityConstructor = ValueSubstituter.Substitute(entityThen, entityProjection.Input.VariableName, columnValues);
                newThens.Add(inputBoundEntityConstructor);
            }

            var newElse = ValueSubstituter.Substitute(entitySelector.Else, entityProjection.Input.VariableName, columnValues);
            var newEntitySelector = DbExpressionBuilder.Case(newWhens, newThens, newElse);

            DbExpression result = discriminatorFilter.BindAs(rowProjection.Input.VariableName).Project(newEntitySelector);
            return result;
        }

        private class ValueSubstituter : DefaultExpressionVisitor
        {
            internal static DbExpression Substitute(
                DbExpression original, string referencedVariable, Dictionary<string, DbExpression> propertyValues)
            {
                DebugCheck.NotNull(original);
                var visitor = new ValueSubstituter(referencedVariable, propertyValues);
                return visitor.VisitExpression(original);
            }

            private readonly string variableName;
            private readonly Dictionary<string, DbExpression> replacements;

            private ValueSubstituter(string varName, Dictionary<string, DbExpression> replValues)
            {
                DebugCheck.NotNull(varName);
                DebugCheck.NotNull(replValues);

                variableName = varName;
                replacements = replValues;
            }

            public override DbExpression Visit(DbPropertyExpression expression)
            {
                Check.NotNull(expression, "expression");

                DbExpression result = null;

                DbExpression replacementValue;
                if (expression.Instance.ExpressionKind == DbExpressionKind.VariableReference
                    &&
                    (((DbVariableReferenceExpression)expression.Instance).VariableName == variableName)
                    &&
                    replacements.TryGetValue(expression.Property.Name, out replacementValue))
                {
                    result = replacementValue;
                }
                else
                {
                    result = base.Visit(expression);
                }
                return result;
            }
        }

        #endregion

        #region Case Statement Simplification

        /// <summary>
        ///     Matches any Case expression
        /// </summary>
        private static readonly Func<DbExpression, bool> _patternCase = Patterns.MatchKind(DbExpressionKind.Case);

        private static DbExpression SimplifyCaseStatement(DbExpression expression)
        {
            var caseExpression = (DbCaseExpression)expression;

            // try simplifying predicates
            var predicateSimplified = false;
            var rewrittenPredicates = new List<DbExpression>(caseExpression.When.Count);
            foreach (var when in caseExpression.When)
            {
                DbExpression simplifiedPredicate;
                if (TrySimplifyPredicate(when, out simplifiedPredicate))
                {
                    rewrittenPredicates.Add(simplifiedPredicate);
                    predicateSimplified = true;
                }
                else
                {
                    rewrittenPredicates.Add(when);
                }
            }

            if (!predicateSimplified)
            {
                return null;
            }

            caseExpression = DbExpressionBuilder.Case(rewrittenPredicates, caseExpression.Then, caseExpression.Else);
            return caseExpression;
        }

        private static bool TrySimplifyPredicate(DbExpression predicate, out DbExpression simplified)
        {
            simplified = null;
            if (predicate.ExpressionKind
                != DbExpressionKind.Case)
            {
                return false;
            }
            var caseExpression = (DbCaseExpression)predicate;
            if (caseExpression.Then.Count != 1
                && caseExpression.Then[0].ExpressionKind == DbExpressionKind.Constant)
            {
                return false;
            }
            var then = (DbConstantExpression)caseExpression.Then[0];
            if (!true.Equals(then.Value))
            {
                return false;
            }
            if (caseExpression.Else != null)
            {
                if (caseExpression.Else.ExpressionKind
                    != DbExpressionKind.Constant)
                {
                    return false;
                }
                var when = (DbConstantExpression)caseExpression.Else;
                if (true.Equals(when.Value))
                {
                    return false;
                }
            }
            simplified = caseExpression.When[0];
            return true;
        }

        #endregion

        #region Nested Projection Collapsing

        /// <summary>
        ///     Determines if an expression is of the form outerProject(outerProjection(innerProject(innerNew)))
        /// </summary>
        private static readonly Func<DbExpression, bool> _patternCollapseNestedProjection =
            Patterns.MatchProject(
                Patterns.MatchProject(
                    Patterns.AnyExpression,
                    Patterns.MatchKind(DbExpressionKind.NewInstance)
                    ),
                Patterns.AnyExpression
                );

        /// <summary>
        ///     Collapses outerProject(outerProjection(innerProject(innerNew)))
        /// </summary>
        private static DbExpression CollapseNestedProjection(DbExpression expression)
        {
            var outerProject = (DbProjectExpression)expression;
            var outerProjection = outerProject.Projection;
            var innerProject = (DbProjectExpression)outerProject.Input.Expression;
            var innerNew = (DbNewInstanceExpression)innerProject.Projection;

            // get membername -> expression bindings for the inner select so that we know how map property
            // references to the inner projection
            var bindings = new Dictionary<string, DbExpression>(innerNew.Arguments.Count);
            var innerResultTypeUsage = innerNew.ResultType;
            var innerResultType = (RowType)innerResultTypeUsage.EdmType;

            for (var ordinal = 0; ordinal < innerResultType.Members.Count; ordinal++)
            {
                bindings[innerResultType.Members[ordinal].Name] = innerNew.Arguments[ordinal];
            }

            // initialize an expression visitor that knows how to map arguments to the outer projection
            // to the inner projection source
            var collapser = new ProjectionCollapser(bindings, outerProject.Input);

            // replace all property references to the inner projection
            var replacementOuterProjection = collapser.CollapseProjection(outerProjection);

            // make sure the collapsing was successful; if not, give up on simplification
            if (collapser.IsDoomed)
            {
                return null;
            }

            // set replacement value so that the expression replacer infrastructure can substitute
            // the collapsed projection in the expression tree
            // continue collapsing projection until the pattern no longer matches
            var replacementOuterProject = innerProject.Input.Project(replacementOuterProjection);
            return replacementOuterProject;
        }

        /// <summary>
        ///     This expression visitor supports collapsing a nested projection matching the pattern described above.
        ///     For instance:
        ///     select T.a as x, T.b as y, true as z from (select E.a as x, E.b as y from Extent E)
        ///     resolves to:
        ///     select E.a, E.b, true as z from Extent E
        ///     In general,
        ///     outerProject(
        ///     outerBinding(
        ///     innerProject(innerBinding, innerNew)
        ///     ),
        ///     outerNew)
        ///     resolves to:
        ///     replacementOuterProject(
        ///     innerBinding,
        ///     replacementOuterNew)
        ///     The outer projection is bound to the inner input source (outerBinding -> innerBinding) and
        ///     the outer new instance expression has its properties remapped to the inner new instance
        ///     expression member expressions.
        ///     This replacer is used to simplify argument value in a new instance expression OuterNew
        ///     from an expression of the form:
        ///     outerProject(outerBinding(innerProject(innerBinding, innerNew)), outerProjection)
        ///     The replacer collapses the outer project terms to point at the innerNew expression.
        ///     Where possible, VarRef_outer.Property_outer is collapsed to VarRef_inner.Property.
        /// </summary>
        private class ProjectionCollapser : DefaultExpressionVisitor
        {
            // the replacer context keeps track of member bindings for var refs and the expression
            // binding for the outer projection being remapped
            private readonly Dictionary<string, DbExpression> m_varRefMemberBindings;
            private readonly DbExpressionBinding m_outerBinding;
            private bool m_doomed;

            internal ProjectionCollapser(
                Dictionary<string, DbExpression> varRefMemberBindings,
                DbExpressionBinding outerBinding)
            {
                m_varRefMemberBindings = varRefMemberBindings;
                m_outerBinding = outerBinding;
            }

            // Visit and identify the Property(VarRef "Outer binding") pattern,
            // remapping the property to the appropriate inner projection member
            internal DbExpression CollapseProjection(DbExpression expression)
            {
                return VisitExpression(expression);
            }

            public override DbExpression Visit(DbPropertyExpression property)
            {
                Check.NotNull(property, "property");

                // check for a property of the outer projection binding (that can be remapped)
                if (property.Instance.ExpressionKind == DbExpressionKind.VariableReference
                    &&
                    IsOuterBindingVarRef((DbVariableReferenceExpression)property.Instance))
                {
                    return m_varRefMemberBindings[property.Property.Name];
                }
                return base.Visit(property);
            }

            public override DbExpression Visit(DbVariableReferenceExpression varRef)
            {
                Check.NotNull(varRef, "varRef");

                // if we encounter an unsubstitutued var ref, give up...
                if (IsOuterBindingVarRef(varRef))
                {
                    m_doomed = true;
                }
                return base.Visit(varRef);
            }

            /// <summary>
            ///     Heuristic check to make sure the var ref is the one we're supposed to be replacing.
            /// </summary>
            private bool IsOuterBindingVarRef(DbVariableReferenceExpression varRef)
            {
                return varRef.VariableName == m_outerBinding.VariableName;
            }

            /// <summary>
            ///     Returns a value indicating that the transformation has failed.
            /// </summary>
            internal bool IsDoomed
            {
                get { return m_doomed; }
            }
        }

        #endregion

        #region Utility Methods

        internal static IEnumerable<DbExpression> FlattenOr(DbExpression expression)
        {
            return Helpers.GetLeafNodes(
                expression,
                exp => (exp.ExpressionKind != DbExpressionKind.Or),
                exp =>
                    {
                        var orExp = (DbOrExpression)exp;
                        return new[] { orExp.Left, orExp.Right };
                    });
        }

        internal static bool TryMatchDiscriminatorPredicate(
            DbFilterExpression filter, Action<DbComparisonExpression, object> onMatchedComparison)
        {
            EdmProperty discriminatorProperty = null;

            // check each assignment in predicate
            foreach (var term in FlattenOr(filter.Predicate))
            {
                DbPropertyExpression currentDiscriminator;
                object discriminatorValue;
                if (!TryMatchPropertyEqualsValue(term, filter.Input.VariableName, out currentDiscriminator, out discriminatorValue))
                {
                    return false;
                }

                // must be the same discriminator in every case
                if (null == discriminatorProperty)
                {
                    discriminatorProperty = (EdmProperty)currentDiscriminator.Property;
                }
                else if (discriminatorProperty != currentDiscriminator.Property)
                {
                    return false;
                }

                onMatchedComparison((DbComparisonExpression)term, discriminatorValue);
            }

            return true;
        }

        internal static bool TryMatchPropertyEqualsValue(
            DbExpression expression, string propertyVariable, out DbPropertyExpression property, out object value)
        {
            property = null;
            value = null;
            // make sure when is of the form Discriminator = Constant
            if (expression.ExpressionKind
                != DbExpressionKind.Equals)
            {
                return false;
            }
            var equals = (DbBinaryExpression)expression;
            if (equals.Left.ExpressionKind
                != DbExpressionKind.Property)
            {
                return false;
            }
            property = (DbPropertyExpression)equals.Left;
            if (!TryMatchConstant(equals.Right, out value))
            {
                return false;
            }

            // verify the property is a property of the input variable
            if (property.Instance.ExpressionKind != DbExpressionKind.VariableReference
                ||
                ((DbVariableReferenceExpression)property.Instance).VariableName != propertyVariable)
            {
                return false;
            }

            return true;
        }

        private static bool TryMatchConstant(DbExpression expression, out object value)
        {
            if (expression.ExpressionKind
                == DbExpressionKind.Constant)
            {
                value = ((DbConstantExpression)expression).Value;
                return true;
            }
            if (expression.ExpressionKind == DbExpressionKind.Cast
                &&
                expression.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType)
            {
                var castExpression = (DbCastExpression)expression;
                if (TryMatchConstant(castExpression.Argument, out value))
                {
                    // convert the value
                    var primitiveType = (PrimitiveType)expression.ResultType.EdmType;

                    // constant literals have already been validated by view gen...
                    value = Convert.ChangeType(value, primitiveType.ClrEquivalentType, CultureInfo.InvariantCulture);
                    return true;
                }
            }
            value = null;
            return false;
        }

        #endregion
    }
}
