//---------------------------------------------------------------------
// <copyright file="ViewSimplifier.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner Microsoft, Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------

using System.Data.Common.CommandTrees;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Data.Common.Utils;
using System.Linq;
using System.Globalization;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.CommandTrees.Internal;

namespace System.Data.Common.CommandTrees.Internal
{

    /// <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)
        {
            ViewSimplifier vs = new ViewSimplifier(view.MetadataWorkspace, extent);
            view = vs.Simplify(view);
            return view;
        }

        private readonly MetadataWorkspace metadata;
        private readonly EntitySetBase extent;

        private ViewSimplifier(MetadataWorkspace mws, EntitySetBase viewTarget)
        {
            this.metadata = mws;
            this.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(Pattern_CollapseNestedProjection, ViewSimplifier.CollapseNestedProjection),
                
                // A case statement can potentially be simplified
                PatternMatchRule.Create(Pattern_Case, ViewSimplifier.SimplifyCaseStatement),

                // Nested TPH discriminator pattern can be converted to the expected TPH discriminator pattern
                PatternMatchRule.Create(Pattern_NestedTphDiscriminator, ViewSimplifier.SimplifyNestedTphDiscriminator),

                // Entity constructors may be augmented with FK-based related entity refs
                PatternMatchRule.Create(Pattern_EntityConstructor, this.AddFkRelatedEntityRefs)
            );
                
            DbExpression 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> Pattern_EntityConstructor =
            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;

        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 (this.doNotProcess)
            {
                return null;
            }

            if(this.extent.BuiltInTypeKind != BuiltInTypeKind.EntitySet ||
               this.extent.EntityContainer.DataSpace != DataSpace.CSpace)
            {
                this.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. 
            //
            EntitySet targetSet = (EntitySet)this.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)
            {
                this.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 (AssociationSet 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)
                {
                    EntityType 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)
            {
                this.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.
            //
            DbProjectExpression entityProject = (DbProjectExpression)viewConstructor;
            List<DbNewInstanceExpression> 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
                DbCaseExpression discriminatedConstructor = (DbCaseExpression)entityProject.Projection;
                conditions = new List<DbExpression>(discriminatedConstructor.When.Count);
                for (int 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);
            }
                        
            bool rebuildView = false;
            for (int idx = 0; idx < constructors.Count; idx++)
            {
                DbNewInstanceExpression entityConstructor = constructors[idx];
                EntityType constructedEntityType = TypeHelpers.GetEdmType<EntityType>(entityConstructor.ResultType);

                List<DbRelatedEntityRef> 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.
                    //
                    List<DbExpression> whens = new List<DbExpression>(conditions.Count);
                    List<DbExpression> thens = new List<DbExpression>(conditions.Count);
                    for (int 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
            this.doNotProcess = true;
            return result;
        }

        private static DbRelatedEntityRef RelatedEntityRefFromAssociationSetEnd(EntityType constructedEntityType, DbNewInstanceExpression entityConstructor, AssociationSetEnd principalSetEnd, ReferentialConstraint fkConstraint)
        {
            EntityType 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.
            //
            DbRefExpression principalRef = principalSetEnd.EntitySet.CreateRef(principalEntityType, principalKeyValues);
            DbRelatedEntityRef 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> Pattern_NestedTphDiscriminator =
            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.Mapping.ViewGeneration.GeneratedView"/> class and so allows a <see cref="System.Data.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>
        private static DbExpression SimplifyNestedTphDiscriminator(DbExpression expression)
        {
            DbProjectExpression entityProjection = (DbProjectExpression)expression;
            DbFilterExpression booleanColumnFilter = (DbFilterExpression)entityProjection.Input.Expression;
            DbProjectExpression rowProjection = (DbProjectExpression)booleanColumnFilter.Input.Expression;
            DbFilterExpression discriminatorFilter = (DbFilterExpression)rowProjection.Input.Expression;
            
            List<DbExpression> predicates = FlattenOr(booleanColumnFilter.Predicate).ToList();
            List<DbPropertyExpression> 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;
            }

            List<string> predicateColumnNames = propertyPredicates.Select(px => px.Property.Name).ToList();
                           
            Dictionary<object, DbComparisonExpression> discriminatorPredicates = new Dictionary<object, DbComparisonExpression>();
            if (!TypeSemantics.IsEntityType(discriminatorFilter.Input.VariableType) ||
                !TryMatchDiscriminatorPredicate(discriminatorFilter, (compEx, discValue) => discriminatorPredicates.Add(discValue, compEx)))
            {
                return null;
            }

            EdmProperty discriminatorProp = (EdmProperty)((DbPropertyExpression)((DbComparisonExpression)discriminatorPredicates.First().Value).Left).Property;
            DbNewInstanceExpression rowConstructor = (DbNewInstanceExpression)rowProjection.Projection;
            RowType resultRow = TypeHelpers.GetEdmType<RowType>(rowConstructor.ResultType);
            Dictionary<string, DbComparisonExpression> inputPredicateMap = new Dictionary<string, DbComparisonExpression>();
            Dictionary<string, DbComparisonExpression> selectorPredicateMap = new Dictionary<string, DbComparisonExpression>();
            Dictionary<string, DbExpression> columnValues = new Dictionary<string, DbExpression>(rowConstructor.Arguments.Count);
            for (int idx = 0; idx < rowConstructor.Arguments.Count; idx++)
            {
                string propName = resultRow.Properties[idx].Name;
                DbExpression columnVal = rowConstructor.Arguments[idx];
                if (predicateColumnNames.Contains(propName))
                {
                    if(columnVal.ExpressionKind != DbExpressionKind.Case)
                    {
                        return null;
                    }
                    DbCaseExpression 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 != false)
                    {
                        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
            DbExpression newDiscriminatorPredicate = Helpers.BuildBalancedTreeInPlace<DbExpression>(new List<DbExpression>(inputPredicateMap.Values), (left, right) => DbExpressionBuilder.Or(left, right));
            discriminatorFilter = discriminatorFilter.Input.Filter(newDiscriminatorPredicate);

            DbCaseExpression entitySelector = (DbCaseExpression)entityProjection.Projection;
            List<DbExpression> newWhens = new List<DbExpression>(entitySelector.When.Count);
            List<DbExpression> newThens = new List<DbExpression>(entitySelector.Then.Count);
            
            for (int idx = 0; idx < entitySelector.When.Count; idx++)
            {
                DbPropertyExpression propWhen = (DbPropertyExpression)entitySelector.When[idx];
                DbNewInstanceExpression entityThen = (DbNewInstanceExpression)entitySelector.Then[idx];

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

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

            DbExpression newElse = ValueSubstituter.Substitute(entitySelector.Else, entityProjection.Input.VariableName, columnValues);
            DbCaseExpression 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)
            {
                Debug.Assert(original != null, "Original expression cannot be null");
                ValueSubstituter 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)
            {
                Debug.Assert(varName != null, "Variable name cannot be null");
                Debug.Assert(replValues != null, "Replacement values cannot be null");

                this.variableName = varName;
                this.replacements = replValues;
            }

            public override DbExpression Visit(DbPropertyExpression expression)
            {
                DbExpression result = null;

                DbExpression replacementValue;
                if (expression.Instance.ExpressionKind == DbExpressionKind.VariableReference &&
                    (((DbVariableReferenceExpression)expression.Instance).VariableName == this.variableName) &&
                    this.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> Pattern_Case = Patterns.MatchKind(DbExpressionKind.Case);

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

            // try simplifying predicates
            bool predicateSimplified = false;
            List<DbExpression> 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 (!false.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> Pattern_CollapseNestedProjection =
            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)
        {
            DbProjectExpression outerProject = (DbProjectExpression)expression;
            DbExpression outerProjection = outerProject.Projection;
            DbProjectExpression innerProject = (DbProjectExpression)outerProject.Input.Expression;
            DbNewInstanceExpression innerNew = (DbNewInstanceExpression)innerProject.Projection;

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

            for (int 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
            ProjectionCollapser 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
            DbProjectExpression 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 Dictionary<string, DbExpression> m_varRefMemberBindings;
            private DbExpressionBinding m_outerBinding;
            private bool m_doomed;
            internal ProjectionCollapser(Dictionary<string, DbExpression> varRefMemberBindings,
                DbExpressionBinding outerBinding)
                : base()
            {
                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 this.VisitExpression(expression);
            }

            public override DbExpression Visit(DbPropertyExpression 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)
            {
                // 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 => { DbOrExpression 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
    }
}