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

using System.Collections.Generic;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Mapping.ViewGeneration.QueryRewriting;
using System.Data.Mapping.ViewGeneration.Structures;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace System.Data.Mapping.ViewGeneration.Validation
{
    using CompositeCondition = Dictionary<MemberPath, Set<Constant>>;
    delegate bool LCWComparer(FragmentQuery query1, FragmentQuery query2);

    internal class ErrorPatternMatcher
    {

        private ViewgenContext m_viewgenContext;
        private MemberDomainMap m_domainMap;
        private IEnumerable<MemberPath> m_keyAttributes;
        private ErrorLog m_errorLog;
        private int m_originalErrorCount;
        private const int NUM_PARTITION_ERR_TO_FIND = 5;


        #region Constructor
        private ErrorPatternMatcher(ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog)
        {
            m_viewgenContext = context;
            m_domainMap = domainMap;
            m_keyAttributes = MemberPath.GetKeyMembers(context.Extent, domainMap);
            m_errorLog = errorLog;
            m_originalErrorCount = m_errorLog.Count;
        }

        public static bool FindMappingErrors(ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog)
        {
            //Can't get here if Update Views have validation disabled
            Debug.Assert(context.ViewTarget == ViewTarget.QueryView || context.Config.IsValidationEnabled);

            if (context.ViewTarget == ViewTarget.QueryView && !context.Config.IsValidationEnabled)
            {
                return false; // Rules for QV under no validation are different
            }

            ErrorPatternMatcher matcher = new ErrorPatternMatcher(context, domainMap, errorLog);

            matcher.MatchMissingMappingErrors();
            matcher.MatchConditionErrors();
            matcher.MatchSplitErrors();

            if (matcher.m_errorLog.Count == matcher.m_originalErrorCount)
            {   //this will generate redundant errors if one of the above routine finds an error
                // so execute it only when we dont have any other errors
                matcher.MatchPartitionErrors();
            }

            if (matcher.m_errorLog.Count > matcher.m_originalErrorCount)
            {
                ExceptionHelpers.ThrowMappingException(matcher.m_errorLog, matcher.m_viewgenContext.Config);
            }

            return false;
        }
        #endregion

        #region Error Matching Routines


        /// <summary>
        /// Finds Types (possibly without any members) that have no mapping specified
        /// </summary>
        private void MatchMissingMappingErrors()
        {
            if (m_viewgenContext.ViewTarget == ViewTarget.QueryView)
            {
                //Find all types for the given EntitySet
                Set<EdmType> unmapepdTypesInExtent = new Set<EdmType>(MetadataHelper.GetTypeAndSubtypesOf(m_viewgenContext.Extent.ElementType, m_viewgenContext.EdmItemCollection, false /*isAbstract*/));

                //Figure out which type has no Cell mapped to it
                foreach (var fragment in m_viewgenContext.AllWrappersForExtent)
                {
                    foreach (Cell cell in fragment.Cells)
                    {
                        foreach (var restriction in cell.CQuery.Conditions)
                        {
                            foreach (var cellConst in restriction.Domain.Values)
                            {
                                //if there is a mapping to this type...
                                TypeConstant typeConst = cellConst as TypeConstant;
                                if (typeConst != null)
                                {
                                    unmapepdTypesInExtent.Remove(typeConst.EdmType);
                                }
                            }
                        }
                    }
                }

                //We are left with a type that has no mapping
                if (unmapepdTypesInExtent.Count > 0)
                {
                    //error unmapped type
                    m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternMissingMappingError,
                            Strings.ViewGen_Missing_Type_Mapping(BuildCommaSeparatedErrorString<EdmType>(unmapepdTypesInExtent)), m_viewgenContext.AllWrappersForExtent, ""));
                }
            }
        }

        private static bool HasNotNullCondition(CellQuery cellQuery, MemberPath member)
        {
            foreach (MemberRestriction condition in cellQuery.GetConjunctsFromWhereClause())
            {
                if (condition.RestrictedMemberSlot.MemberPath.Equals(member))
                {
                    if (condition.Domain.Values.Contains(Constant.NotNull))
                    {
                        return true;
                    }

                    //Not Null may have been optimized into NOT(1, 2, NULL). SO look into negated cell constants
                    foreach (NegatedConstant negatedConst in condition.Domain.Values.Select(cellConstant => cellConstant as NegatedConstant).Where(negated => negated != null))
                    {
                        if (negatedConst.Elements.Contains(Constant.Null))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private static bool IsMemberPartOfNotNullCondition(IEnumerable<LeftCellWrapper> wrappers, MemberPath leftMember, ViewTarget viewTarget)
        {

            foreach (var leftCellWrapper in wrappers)
            {
                CellQuery leftCellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(viewTarget);

                if (HasNotNullCondition(leftCellQuery, leftMember))
                {
                    return true;
                }

                //Now figure out corresponding right side MemberPath
                CellQuery rightCellQuery = leftCellWrapper.OnlyInputCell.GetRightQuery(viewTarget);
                int indexOfMemberInProjection = leftCellQuery.GetProjectedMembers().TakeWhile(path => !path.Equals(leftMember)).Count();

                //Member with condition is projected, so check opposite CellQuery's condition
                if (indexOfMemberInProjection < leftCellQuery.GetProjectedMembers().Count())
                {
                    MemberPath rightmember = ((MemberProjectedSlot)rightCellQuery.ProjectedSlotAt(indexOfMemberInProjection)).MemberPath;

                    if (HasNotNullCondition(rightCellQuery, rightmember))
                    {
                        return true;
                    }
                }

            }
            return false;
        }

        /// <summary>
        /// Finds errors related to splitting Conditions
        /// 1. Condition value is repeated across multiple types
        /// 2. A Column/attribute is mapped but also used as a condition
        /// </summary>
        private void MatchConditionErrors()
        {
            List<LeftCellWrapper> leftCellWrappers = m_viewgenContext.AllWrappersForExtent;

            //Stores violating Discriminator (condition member) so that we dont repeat the same error
            Set<MemberPath> mappedConditionMembers = new Set<MemberPath>();

            //Both of these data-structs help in finding duplicate conditions
            Set<CompositeCondition> setOfconditions = new Set<CompositeCondition>(new ConditionComparer());
            Dictionary<CompositeCondition, LeftCellWrapper> firstLCWForCondition = new Dictionary<CompositeCondition, LeftCellWrapper>(new ConditionComparer());

            foreach (var leftCellWrapper in leftCellWrappers)
            {
                CompositeCondition condMembersValues = new CompositeCondition();

                CellQuery cellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(m_viewgenContext.ViewTarget);

                foreach (MemberRestriction condition in cellQuery.GetConjunctsFromWhereClause())
                {
                    MemberPath memberPath = condition.RestrictedMemberSlot.MemberPath;

                    if (!m_domainMap.IsConditionMember(memberPath))
                    {
                        continue;
                    }

                    ScalarRestriction scalarCond = condition as ScalarRestriction;
                    //Check for mapping of Scalar member condition, ignore type conditions
                    if (scalarCond != null &&
                        !mappedConditionMembers.Contains(memberPath) && /* prevents duplicate errors */
                        !leftCellWrapper.OnlyInputCell.CQuery.WhereClause.Equals(leftCellWrapper.OnlyInputCell.SQuery.WhereClause) && /* projection allowed when both conditions are equal */
                        !IsMemberPartOfNotNullCondition(leftCellWrappers, memberPath, m_viewgenContext.ViewTarget))
                    {
                        //This member should not be mapped
                        CheckThatConditionMemberIsNotMapped(memberPath, leftCellWrappers, mappedConditionMembers);
                    }

                    //If a not-null condition is specified on a nullable column,
                    //check that the property it is mapped to in the fragment is non-nullable,
                    //unless there is a not null condition on the property that is being mapped it self.
                    //Otherwise return an error.
                    if (m_viewgenContext.ViewTarget == ViewTarget.UpdateView)
                    {
                        if (scalarCond != null &&
                            memberPath.IsNullable && IsMemberPartOfNotNullCondition(new LeftCellWrapper[] { leftCellWrapper }, memberPath, m_viewgenContext.ViewTarget))                        
                        {
                            MemberPath rightMemberPath = GetRightMemberPath(memberPath, leftCellWrapper);
                            if (rightMemberPath != null && rightMemberPath.IsNullable &&
                                !IsMemberPartOfNotNullCondition(new LeftCellWrapper[] { leftCellWrapper }, rightMemberPath, m_viewgenContext.ViewTarget))
                            {
                                m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
                                        Strings.Viewgen_ErrorPattern_NotNullConditionMappedToNullableMember(
                                                memberPath, rightMemberPath
                                            ), leftCellWrapper.OnlyInputCell, ""));
                            }
                        }
                    }

                    //CheckForDuplicateConditionValue
                    //discover a composite condition of the form {path1=x, path2=y, ...}
                    foreach (var element in condition.Domain.Values)
                    {
                        Set<Constant> values;
                        //if not in the dict, add it
                        if (!condMembersValues.TryGetValue(memberPath, out values))
                        {
                            values = new Set<Constant>(Constant.EqualityComparer);
                            condMembersValues.Add(memberPath, values);
                        }
                        values.Add(element);
                    }

                } //foreach condition

                if (condMembersValues.Count > 0) //it is possible that there are no condition members
                {
                    //Check if the composite condition has been encountered before
                    if (setOfconditions.Contains(condMembersValues))
                    {
                        //Extents may be Equal on right side (e.g: by some form of Refconstraint)
                        if (!RightSideEqual(firstLCWForCondition[condMembersValues], leftCellWrapper))
                        {
                            //error duplicate conditions
                            m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
                                    Strings.Viewgen_ErrorPattern_DuplicateConditionValue(
                                            BuildCommaSeparatedErrorString<MemberPath>(condMembersValues.Keys)
                                        ),
                                    ToIEnum(firstLCWForCondition[condMembersValues].OnlyInputCell, leftCellWrapper.OnlyInputCell), ""));
                        }
                    }
                    else
                    {
                        setOfconditions.Add(condMembersValues);

                        //Remember which cell the condition came from.. used for error reporting
                        firstLCWForCondition.Add(condMembersValues, leftCellWrapper);
                    }
                }
            } //foreach fragment related to the Extent we are working on

        }

        private MemberPath GetRightMemberPath(MemberPath conditionMember,LeftCellWrapper leftCellWrapper)
        {
            CellQuery rightCellQuery = leftCellWrapper.OnlyInputCell.GetRightQuery(ViewTarget.QueryView);
            var projectPositions = rightCellQuery.GetProjectedPositions(conditionMember);
            //Make the case simple. If the member is mapped more than once in the same cell wrapper
            //we are not going try and guess the pattern
            if (projectPositions.Count != 1)
            {
                return null;
            }
            int firstProjectedPosition = projectPositions.First();
            CellQuery leftCellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(ViewTarget.QueryView);
            return ((MemberProjectedSlot)leftCellQuery.ProjectedSlotAt(firstProjectedPosition)).MemberPath;
        }

        /// <summary>
        /// When we are dealing with an update view, this method
        /// finds out if the given Table is mapped to different EntitySets
        /// </summary>
        private void MatchSplitErrors()
        {
            List<LeftCellWrapper> leftCellWrappers = m_viewgenContext.AllWrappersForExtent;

            //Check that the given Table is mapped to only one EntitySet (avoid AssociationSets)
            var nonAssociationWrappers = leftCellWrappers.Where(r => !(r.LeftExtent is AssociationSet) && !(r.RightCellQuery.Extent is AssociationSet));

            if (m_viewgenContext.ViewTarget == ViewTarget.UpdateView && nonAssociationWrappers.Any())
            {
                LeftCellWrapper firstLeftCWrapper = nonAssociationWrappers.First();
                EntitySetBase rightExtent = firstLeftCWrapper.RightCellQuery.Extent;

                foreach (var leftCellWrapper in nonAssociationWrappers)
                {
                    //!(leftCellWrapper.RightCellQuery.Extent is AssociationSet) &&
                    if (!leftCellWrapper.RightCellQuery.Extent.EdmEquals(rightExtent))
                    {
                        //A Table may be mapped to two extents but the extents may be Equal (by some form of Refconstraint)
                        if (!RightSideEqual(leftCellWrapper, firstLeftCWrapper))
                        {
                            //Report Error
                            m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternSplittingError,
                                Strings.Viewgen_ErrorPattern_TableMappedToMultipleES(leftCellWrapper.LeftExtent.ToString(), leftCellWrapper.RightCellQuery.Extent.ToString(), rightExtent.ToString()),
                                leftCellWrapper.Cells.First(), ""));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Finds out whether fragments (partitions) violate constraints that would produce an invalid mapping.
        /// We compare equality/disjointness/containment for all 2-combinations of fragments.
        /// Error is reported if given relationship on S side is not maintained on the C side.
        /// If we know nothing about S-side then any relationship on C side is valid.
        /// </summary>
        private void MatchPartitionErrors()
        {
            List<LeftCellWrapper> mappingFragments = m_viewgenContext.AllWrappersForExtent;

            //for every 2-combination nC2  (n choose 2) 
            int i = 0;
            foreach (var fragment1 in mappingFragments)
            {
                foreach (var fragment2 in mappingFragments.Skip(++i))
                {
                    FragmentQuery rightFragmentQuery1 = CreateRightFragmentQuery(fragment1);
                    FragmentQuery rightFragmentQuery2 = CreateRightFragmentQuery(fragment2);

                    bool isSDisjoint = CompareS(ComparisonOP.IsDisjointFrom, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
                    bool isCDisjoint = CompareC(ComparisonOP.IsDisjointFrom, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);

                    bool is1SubsetOf2_C;
                    bool is2SubsetOf1_C;
                    bool is1SubsetOf2_S;
                    bool is2SubsetOf1_S;
                    bool isSEqual;
                    bool isCEqual;

                    if (isSDisjoint)
                    {
                        if (isCDisjoint)
                        {
                            continue;
                        }
                        else
                        {
                            //Figure out more info for accurate message
                            is1SubsetOf2_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
                            is2SubsetOf1_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);
                            isCEqual = is1SubsetOf2_C && is2SubsetOf1_C;

                            StringBuilder errorString = new StringBuilder();
                            //error
                            if (isCEqual) //equal
                            {
                                //MSG:  These two fragments are disjoint on the S-side but equal on the C-side. 
                                //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
                                //      or by mapping them to the same type but with a C-side discriminator.
                                //TestCase (1)
                                errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Eq);
                            }
                            else if (is1SubsetOf2_C || is2SubsetOf1_C)
                            {
                                //Really overlap is not accurate term (should be contianed in or subset of), but its easiest to read.

                                if (CSideHasDifferentEntitySets(fragment1, fragment2))
                                {
                                    //MSG:  These two fragments are disjoint on the S-side but overlap on the C-side via a Referential constraint.
                                    //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
                                    //      or by mapping them to the same type but with a C-side discriminator.

                                    //TestCase (Not possible because all PKs must be mapped)
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Subs_Ref);
                                }
                                else
                                {
                                    //MSG:  These two fragments are disjoint on the S-side but overlap on the C-side. 
                                    //      Ensure disjointness on C-side. You may be using IsTypeOf() quantifier to 
                                    //      map multiple types within one of these fragments.
                                    //TestCase (2)
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Subs);
                                }
                            }
                            else //relationship is unknown
                            {
                                //MSG:  These two fragments are disjoint on the S-side but not so on the C-side. 
                                //      Ensure disjointness on C-side by mapping them to different types within the same EntitySet
                                //      or by mapping them to the same type but with a C-side discriminator.

                                //TestCase (4)
                                errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Disj_Unk);
                            }

                            m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));

                            if (FoundTooManyErrors())
                            {
                                return;
                            }
                        }
                    }
                    else
                    {
                        is1SubsetOf2_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
                        is2SubsetOf1_C = CompareC(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);
                    }
                    is1SubsetOf2_S = CompareS(ComparisonOP.IsContainedIn, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
                    is2SubsetOf1_S = CompareS(ComparisonOP.IsContainedIn, m_viewgenContext, fragment2, fragment1, rightFragmentQuery2, rightFragmentQuery1);

                    isCEqual = is1SubsetOf2_C && is2SubsetOf1_C;
                    isSEqual = is1SubsetOf2_S && is2SubsetOf1_S;


                    if (isSEqual)
                    {
                        if (isCEqual) //c-side equal
                        {
                            continue;
                        }
                        else
                        {
                            //error
                            StringBuilder errorString = new StringBuilder();

                            if (isCDisjoint)
                            {
                                //MSG:  These two fragments are equal on the S-side but disjoint on the C-side. 
                                //      Either partition the S-side by adding a condition or remove any C-side conditions along with resulting redundant mapping fragments.
                                //      You may also map these two disjoint C-side partitions to different tables.
                                //TestCase (5)
                                errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Disj);
                            }
                            else if (is1SubsetOf2_C || is2SubsetOf1_C)
                            {
                                if (CSideHasDifferentEntitySets(fragment1, fragment2))
                                {
                                    //MSG:  These two fragments are equal on the S-side but overlap on the C-side. 
                                    //      It is likely that you have not added Referential Integrity constriaint for all Key attributes of both EntitySets.
                                    //      Doing so would ensure equality on the C-side.
                                    //TestCase (Not possible, right?)
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Subs_Ref);
                                }
                                else
                                {
                                    //MSG:  These two fragments are equal on the S-side but overlap on the C-side. 
                                    //      If you are using IsTypeOf() quantifier ensure both mapping fragments capture same types on the C-side.
                                    //      Otherwise you may have intended to partition the S-side.
                                    //TestCase (6)

                                    //Check for the specific case
                                    //where there are mapping fragments with different types on C side
                                    //mapped to same table on the Store side but not all the fragments have 
                                    //a condition. Ignore the cases where any of the fragments have C side conditions.
                                    if (fragment1.LeftExtent.Equals(fragment2.LeftExtent))
                                    {
                                        bool firstCellWrapperHasCondition;
                                        List<EdmType> edmTypesForFirstCellWrapper;
                                        bool secondCellWrapperHasCondition;
                                        List<EdmType> edmTypesForSecondCellWrapper;
                                        GetTypesAndConditionForWrapper(fragment1, out firstCellWrapperHasCondition, out edmTypesForFirstCellWrapper);
                                        GetTypesAndConditionForWrapper(fragment2, out secondCellWrapperHasCondition, out edmTypesForSecondCellWrapper);
                                        if (!firstCellWrapperHasCondition && !secondCellWrapperHasCondition)
                                        {
                                            if (((edmTypesForFirstCellWrapper.Except(edmTypesForSecondCellWrapper)).Count() != 0 )
                                                || ((edmTypesForSecondCellWrapper.Except(edmTypesForFirstCellWrapper)).Count() != 0 ))
                                            {
                                                if (!CheckForStoreConditions(fragment1) || !CheckForStoreConditions(fragment2))
                                                {
                                                    IEnumerable<string> edmTypesForErrorString = edmTypesForFirstCellWrapper.Select(it => it.FullName).Union(edmTypesForSecondCellWrapper.Select(it => it.FullName));
                                                    m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError,
                                                Strings.Viewgen_ErrorPattern_Partition_MultipleTypesMappedToSameTable_WithoutCondition(
                                                        StringUtil.ToCommaSeparatedString(edmTypesForErrorString), fragment1.LeftExtent
                                                    ), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));
                                                    return;
                                                }
                                            }
                                        }
                                    }

                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Subs);
                                }
                            }
                            else //unknown
                            {
                                //S-side equal, C-side Unknown
                                if (!IsQueryView() &&
                                    (fragment1.OnlyInputCell.CQuery.Extent is AssociationSet ||
                                     fragment2.OnlyInputCell.CQuery.Extent is AssociationSet))
                                {
                                    //one side is an association set
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Unk_Association);
                                }
                                else
                                {

                                    //MSG:  These two fragments are equal on the S-side but not so on the C-side. 
                                    //      Try adding an Association with Referntial Integrity constraint if they are
                                    //      mapped to different EntitySets in order to make theme equal on the C-side.
                                    //TestCase (no need, Table mapped to multiple ES tests cover this scenario)
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Eq_Unk);
                                }
                            }

                            m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));

                            if (FoundTooManyErrors())
                            {
                                return;
                            }
                        }
                    }
                    else if (is1SubsetOf2_S || is2SubsetOf1_S) //proper subset - note: else if ensures inverse need not be checked
                    {
                        //C-side proper subset (c side must not be equal)
                        if ((is1SubsetOf2_S && is1SubsetOf2_C == true && !(is2SubsetOf1_C == true)) || (is2SubsetOf1_S && is2SubsetOf1_C == true && !(is1SubsetOf2_C == true)))
                        {
                            continue;
                        }
                        else
                        {   //error

                            StringBuilder errorString = new StringBuilder();

                            if (isCDisjoint)
                            {
                                //MSG:  One of the fragments is a subset of the other on the S-side but they are disjoint on the C-side.
                                //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
                                //      You may need to use IsTypeOf() quantifier or loosen conditions in one of the fragments.
                                //TestCase (9, 10)
                                errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Disj);
                            }
                            else if (isCEqual) //equal
                            {
                                //MSG:  One of the fragments is a subset of the other on the S-side but they are equal on the C-side.
                                //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
                                //TestCase (10)


                                if (CSideHasDifferentEntitySets(fragment1, fragment2))
                                {
                                    // If they are equal via a Referential integrity constraint try making one a subset of the other by
                                    // not including all primary keys in the constraint.
                                    //TestCase (Not possible)
                                    errorString.Append(" " + Strings.Viewgen_ErrorPattern_Partition_Sub_Eq_Ref);
                                }
                                else
                                {
                                    //      You may need to modify conditions in one of the fragments.
                                    //TestCase (10)
                                    errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Eq);
                                }
                            }
                            else
                            {   //unknown
                                //MSG:  One of the fragments is a subset of the other on the S-side but they are disjoint on the C-side.
                                //      If you intended overlap on the S-side ensure they have similar relationship on teh C-side.
                                //TestCase (no need, Table mapped to multiple ES tests cover this scenario)
                                errorString.Append(Strings.Viewgen_ErrorPattern_Partition_Sub_Unk);
                            }

                            m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternInvalidPartitionError, errorString.ToString(), ToIEnum(fragment1.OnlyInputCell, fragment2.OnlyInputCell), ""));

                            if (FoundTooManyErrors())
                            {
                                return;
                            }
                        }
                    }
                    //else unknown relationship on the S-side
                }
            }   //end looping over every 2-combination of fragment
        }

        /// <summary>
        /// Gets the types on the Edm side mapped in this fragment wrapper.
        /// It also returns an out parameter indicating whether there were any C side conditions.
        /// </summary>
        private void GetTypesAndConditionForWrapper(LeftCellWrapper wrapper, out bool hasCondition, out List<EdmType> edmTypes)
        {
            hasCondition = false;
            edmTypes = new List<EdmType>();
            //Figure out which type has no Cell mapped to it
            foreach (Cell cell in wrapper.Cells)
            {
                foreach (var restriction in cell.CQuery.Conditions)
                {
                    foreach (var cellConst in restriction.Domain.Values)
                    {
                        //if there is a mapping to this type...
                        TypeConstant typeConst = cellConst as TypeConstant;
                        if (typeConst != null)
                        {
                            edmTypes.Add(typeConst.EdmType);
                        }
                        else
                        {
                            hasCondition = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Return true if there were any Store conditions on this cell wrapper.
        /// </summary>
        /// <param name="wrapper"></param>
        /// <returns></returns>
        private bool CheckForStoreConditions(LeftCellWrapper wrapper)
        {
            return wrapper.Cells.SelectMany(c => c.SQuery.Conditions).Any();
        }


        private void CheckThatConditionMemberIsNotMapped(MemberPath conditionMember, List<LeftCellWrapper> mappingFragments, Set<MemberPath> mappedConditionMembers)
        {

            //Make sure memberPath is not mapped (in any other cells)
            foreach (var anotherFragment in mappingFragments)
            {
                foreach (var anotherCell in anotherFragment.Cells)
                {
                    CellQuery anotherCellQuery = anotherCell.GetLeftQuery(m_viewgenContext.ViewTarget);
                    if (anotherCellQuery.GetProjectedMembers().Contains(conditionMember))
                    {
                        mappedConditionMembers.Add(conditionMember);
                        //error condition memer is projected somewhere
                        m_errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.ErrorPatternConditionError, Strings.Viewgen_ErrorPattern_ConditionMemberIsMapped(conditionMember.ToString()), anotherCell, ""));
                    }
                }
            }
        }

        #endregion

        private bool FoundTooManyErrors()
        {
            return (m_errorLog.Count > m_originalErrorCount + NUM_PARTITION_ERR_TO_FIND);
        }

        #region Private Helpers

        private string BuildCommaSeparatedErrorString<T>(IEnumerable<T> members)
        {
            StringBuilder builder = new StringBuilder();

            var firstMember = members.First();
            foreach (var member in members)
            {
                if (!member.Equals(firstMember))
                {
                    builder.Append(", ");
                }
                builder.Append("'" + member.ToString() + "'");
            }
            return builder.ToString();
        }

        private bool CSideHasDifferentEntitySets(LeftCellWrapper a, LeftCellWrapper b)
        {
            if (IsQueryView())
            {
                return a.LeftExtent == b.LeftExtent;
            }
            else
            {
                return a.RightCellQuery == b.RightCellQuery;
            }
        }

        private bool CompareC(ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
        {
            return Compare(true /*lookingForCSide*/, op, context, leftWrapper1, leftWrapper2, rightQuery1, rightQuery2);
        }

        private bool CompareS(ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
        {
            return Compare(false/*lookingForCSide*/, op, context, leftWrapper1, leftWrapper2, rightQuery1, rightQuery2);
        }

        private bool Compare(bool lookingForC, ComparisonOP op, ViewgenContext context, LeftCellWrapper leftWrapper1, LeftCellWrapper leftWrapper2, FragmentQuery rightQuery1, FragmentQuery rightQuery2)
        {
            LCWComparer comparer;

            if ((lookingForC && IsQueryView()) || (!lookingForC && !IsQueryView()))
            {
                if (op == ComparisonOP.IsContainedIn)
                {
                    comparer = context.LeftFragmentQP.IsContainedIn;
                }
                else if (op == ComparisonOP.IsDisjointFrom)
                {
                    comparer = context.LeftFragmentQP.IsDisjointFrom;
                }
                else
                {
                    Debug.Fail("Unexpected comparison operator, only IsDisjointFrom and IsContainedIn are expected");
                    return false;
                }

                return comparer(leftWrapper1.FragmentQuery, leftWrapper2.FragmentQuery);
            }
            else
            {
                if (op == ComparisonOP.IsContainedIn)
                {
                    comparer = context.RightFragmentQP.IsContainedIn;
                }
                else if (op == ComparisonOP.IsDisjointFrom)
                {
                    comparer = context.RightFragmentQP.IsDisjointFrom;
                }
                else
                {
                    Debug.Fail("Unexpected comparison operator, only IsDisjointFrom and IsContainedIn are expected");
                    return false;
                }

                return comparer(rightQuery1, rightQuery2);
            }
        }

        private bool RightSideEqual(LeftCellWrapper wrapper1, LeftCellWrapper wrapper2)
        {
            FragmentQuery rightFragmentQuery1 = CreateRightFragmentQuery(wrapper1);
            FragmentQuery rightFragmentQuery2 = CreateRightFragmentQuery(wrapper2);

            return m_viewgenContext.RightFragmentQP.IsEquivalentTo(rightFragmentQuery1, rightFragmentQuery2);
        }

        private FragmentQuery CreateRightFragmentQuery(LeftCellWrapper wrapper)
        {
            return FragmentQuery.Create(wrapper.OnlyInputCell.CellLabel.ToString(), wrapper.CreateRoleBoolean(), wrapper.OnlyInputCell.GetRightQuery(m_viewgenContext.ViewTarget));
        }

        private IEnumerable<Cell> ToIEnum(Cell one, Cell two)
        {
            List<Cell> cells = new List<Cell>();
            cells.Add(one);
            cells.Add(two);
            return cells;
        }

        private bool IsQueryView()
        {
            return (m_viewgenContext.ViewTarget == ViewTarget.QueryView);
        }

        #endregion

        enum ComparisonOP
        {
            IsContainedIn,
            IsDisjointFrom
        }
    }

    class ConditionComparer : IEqualityComparer<Dictionary<MemberPath, Set<Constant>>>
    {
        public bool Equals(Dictionary<MemberPath, Set<Constant>> one, Dictionary<MemberPath, Set<Constant>> two)
        {
            Set<MemberPath> keysOfOne = new Set<MemberPath>(one.Keys, MemberPath.EqualityComparer);
            Set<MemberPath> keysOfTwo = new Set<MemberPath>(two.Keys, MemberPath.EqualityComparer);

            if (!keysOfOne.SetEquals(keysOfTwo))
            {
                return false;
            }

            foreach (var member in keysOfOne)
            {
                Set<Constant> constantsOfOne = one[member];
                Set<Constant> constantsOfTwo = two[member];

                if (!constantsOfOne.SetEquals(constantsOfTwo))
                {
                    return false;
                }
            }
            return true;
        }

        public int GetHashCode(Dictionary<MemberPath, Set<Constant>> obj)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var key in obj.Keys)
            {
                builder.Append(key.ToString());
            }

            return builder.ToString().GetHashCode();
        }

    }
}
