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

namespace System.Data.Entity.Core.Mapping.ViewGeneration.Validation
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.Utils;
    using System.Data.Entity.Core.Mapping.ViewGeneration.QueryRewriting;
    using System.Data.Entity.Core.Mapping.ViewGeneration.Structures;
    using System.Data.Entity.Core.Mapping.ViewGeneration.Utils;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Resources;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Text;
    using CompositeCondition = System.Collections.Generic.Dictionary<Structures.MemberPath, Common.Utils.Set<Structures.Constant>>;

    internal delegate bool LCWComparer(FragmentQuery query1, FragmentQuery query2);

    internal class ErrorPatternMatcher
    {
        private readonly ViewgenContext m_viewgenContext;
        private readonly MemberDomainMap m_domainMap;
        private readonly ErrorLog m_errorLog;
        private readonly int m_originalErrorCount;
        private const int NUM_PARTITION_ERR_TO_FIND = 5;

        private ErrorPatternMatcher(ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog)
        {
            m_viewgenContext = context;
            m_domainMap = domainMap;
            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
            }

            var 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;
        }

        // <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
                var 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 (var 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...
                                var 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(
                            ViewGenErrorCode.ErrorPatternMissingMappingError,
                            Strings.ViewGen_Missing_Type_Mapping(BuildCommaSeparatedErrorString(unmapepdTypesInExtent)),
                            m_viewgenContext.AllWrappersForExtent, ""));
                }
            }
        }

        private static bool HasNotNullCondition(CellQuery cellQuery, MemberPath member)
        {
            foreach (var 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 (
                        var 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)
            {
                var leftCellQuery = leftCellWrapper.OnlyInputCell.GetLeftQuery(viewTarget);

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

                //Now figure out corresponding right side MemberPath
                var rightCellQuery = leftCellWrapper.OnlyInputCell.GetRightQuery(viewTarget);
                var 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())
                {
                    var 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()
        {
            var leftCellWrappers = m_viewgenContext.AllWrappersForExtent;

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

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

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

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

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

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

                    var 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 }, memberPath, m_viewgenContext.ViewTarget))
                        {
                            var rightMemberPath = GetRightMemberPath(memberPath, leftCellWrapper);
                            if (rightMemberPath != null
                                && rightMemberPath.IsNullable
                                &&
                                !IsMemberPartOfNotNullCondition(new[] { leftCellWrapper }, rightMemberPath, m_viewgenContext.ViewTarget))
                            {
                                m_errorLog.AddEntry(
                                    new ErrorLog.Record(
                                        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(
                                    ViewGenErrorCode.ErrorPatternConditionError,
                                    Strings.Viewgen_ErrorPattern_DuplicateConditionValue(
                                        BuildCommaSeparatedErrorString(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 static MemberPath GetRightMemberPath(MemberPath conditionMember, LeftCellWrapper leftCellWrapper)
        {
            var 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;
            }
            var firstProjectedPosition = projectPositions.First();
            var 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()
        {
            var 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())
            {
                var firstLeftCWrapper = nonAssociationWrappers.First();
                var 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(
                                    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>
        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private void MatchPartitionErrors()
        {
            var mappingFragments = m_viewgenContext.AllWrappersForExtent;

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

                    var isSDisjoint = CompareS(
                        ComparisonOP.IsDisjointFrom, m_viewgenContext, fragment1, fragment2, rightFragmentQuery1, rightFragmentQuery2);
                    var 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;

                            var 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(
                                    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
                            var 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))
                                                {
                                                    var edmTypesForErrorString =
                                                        edmTypesForFirstCellWrapper.Select(it => it.FullName).Union(
                                                            edmTypesForSecondCellWrapper.Select(it => it.FullName));
                                                    m_errorLog.AddEntry(
                                                        new ErrorLog.Record(
                                                            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(
                                    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 && !is2SubsetOf1_C)
                            || (is2SubsetOf1_S && is2SubsetOf1_C && !is1SubsetOf2_C))
                        {
                            continue;
                        }
                        else
                        {
                            //error

                            var 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(
                                    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 static 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 (var 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...
                        var 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>
        private static 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)
                {
                    var 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(
                                ViewGenErrorCode.ErrorPatternConditionError,
                                Strings.Viewgen_ErrorPattern_ConditionMemberIsMapped(conditionMember.ToString()), anotherCell, ""));
                    }
                }
            }
        }

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

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

            var firstMember = members.First();
            foreach (var member in members)
            {
                if (!member.Equals(firstMember))
                {
                    builder.Append(", ");
                }
                builder.Append("'" + member + "'");
            }
            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)
        {
            var rightFragmentQuery1 = CreateRightFragmentQuery(wrapper1);
            var 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 static IEnumerable<Cell> ToIEnum(Cell one, Cell two)
        {
            var cells = new List<Cell>();
            cells.Add(one);
            cells.Add(two);
            return cells;
        }

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

        private enum ComparisonOP
        {
            IsContainedIn,
            IsDisjointFrom
        }
    }
}
