// 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.Structures
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Common.CommandTrees;
    using System.Data.Entity.Core.Common.Utils;
    using System.Text;
    using DomainBoolExpr =
            System.Data.Entity.Core.Common.Utils.Boolean.BoolExpr<Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
    using DomainConstraint = System.Data.Entity.Core.Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>;
    using DomainTermExpr =
            System.Data.Entity.Core.Common.Utils.Boolean.TermExpr<Common.Utils.Boolean.DomainConstraint<BoolLiteral, Constant>>;
    using DomainVariable = System.Data.Entity.Core.Common.Utils.Boolean.DomainVariable<BoolLiteral, Constant>;

    // <summary>
    // A class that ties up all the literals in boolean expressions.
    // Conditions represented by <see cref="BoolLiteral" />s need to be synchronized with <see cref="DomainConstraint" />s,
    // which may be modified upon calling <see cref="BoolExpression.ExpensiveSimplify" />. This is what the method
    // <see
    //     cref="BoolLiteral.FixRange" />
    // is used for.
    // </summary>
    internal abstract class BoolLiteral : InternalBase
    {
        internal static readonly IEqualityComparer<BoolLiteral> EqualityComparer = new BoolLiteralComparer();
        internal static readonly IEqualityComparer<BoolLiteral> EqualityIdentifierComparer = new IdentifierComparer();

        // <summary>
        // Creates a term expression of the form: "<paramref name="literal" /> in <paramref name="range" /> with all possible values being
        // <paramref
        //     name="domain" />
        // ".
        // </summary>
        internal static DomainTermExpr MakeTermExpression(BoolLiteral literal, IEnumerable<Constant> domain, IEnumerable<Constant> range)
        {
            var domainSet = new Set<Constant>(domain, Constant.EqualityComparer);
            var rangeSet = new Set<Constant>(range, Constant.EqualityComparer);
            return MakeTermExpression(literal, domainSet, rangeSet);
        }

        // <summary>
        // Creates a term expression of the form: "<paramref name="literal" /> in <paramref name="range" /> with all possible values being
        // <paramref
        //     name="domain" />
        // ".
        // </summary>
        internal static DomainTermExpr MakeTermExpression(BoolLiteral literal, Set<Constant> domain, Set<Constant> range)
        {
            domain.MakeReadOnly();
            range.MakeReadOnly();

            var variable = new DomainVariable(literal, domain, EqualityIdentifierComparer);
            var constraint = new DomainConstraint(variable, range);
            var result = new DomainTermExpr(EqualityComparer<DomainConstraint>.Default, constraint);
            return result;
        }

        // <summary>
        // Fixes the range of the literal using the new values provided in <paramref name="range" /> and returns a boolean expression corresponding to the new value.
        // </summary>
        internal abstract DomainBoolExpr FixRange(Set<Constant> range, MemberDomainMap memberDomainMap);

        internal abstract DomainBoolExpr GetDomainBoolExpression(MemberDomainMap domainMap);

        // <summary>
        // See <see cref="BoolExpression.RemapBool" />.
        // </summary>
        internal abstract BoolLiteral RemapBool(Dictionary<MemberPath, MemberPath> remap);

        // <summary>
        // See <see cref="BoolExpression.GetRequiredSlots" />.
        // </summary>
        internal abstract void GetRequiredSlots(MemberProjectionIndex projectedSlotMap, bool[] requiredSlots);

        // <summary>
        // See <see cref="BoolExpression.AsEsql" />.
        // </summary>
        internal abstract StringBuilder AsEsql(StringBuilder builder, string blockAlias, bool skipIsNotNull);

        // <summary>
        // See <see cref="BoolExpression.AsCqt" />.
        // </summary>
        internal abstract DbExpression AsCqt(DbExpression row, bool skipIsNotNull);

        internal abstract StringBuilder AsUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull);

        internal abstract StringBuilder AsNegatedUserString(StringBuilder builder, string blockAlias, bool skipIsNotNull);

        // <summary>
        // Checks if the identifier in this is the same as the one in <paramref name="right" />.
        // </summary>
        protected virtual bool IsIdentifierEqualTo(BoolLiteral right)
        {
            return IsEqualTo(right);
        }

        protected abstract bool IsEqualTo(BoolLiteral right);

        // <summary>
        // Get the hash code based on the identifier.
        // </summary>
        protected virtual int GetIdentifierHash()
        {
            return GetHashCode();
        }

        // <summary>
        // This class compares boolean expressions.
        // </summary>
        private sealed class BoolLiteralComparer : IEqualityComparer<BoolLiteral>
        {
            public bool Equals(BoolLiteral left, BoolLiteral right)
            {
                // Quick check with references
                if (ReferenceEquals(left, right))
                {
                    // Gets the Null and Undefined case as well
                    return true;
                }
                // One of them is non-null at least
                if (left == null
                    || right == null)
                {
                    return false;
                }
                // Both are non-null at this point
                return left.IsEqualTo(right);
            }

            public int GetHashCode(BoolLiteral literal)
            {
                return literal.GetHashCode();
            }
        }

        // <summary>
        // This class compares just the identifier in boolean expressions.
        // </summary>
        private sealed class IdentifierComparer : IEqualityComparer<BoolLiteral>
        {
            public bool Equals(BoolLiteral left, BoolLiteral right)
            {
                // Quick check with references
                if (ReferenceEquals(left, right))
                {
                    // Gets the Null and Undefined case as well
                    return true;
                }
                // One of them is non-null at least
                if (left == null
                    || right == null)
                {
                    return false;
                }
                // Both are non-null at this point
                return left.IsIdentifierEqualTo(right);
            }

            public int GetHashCode(BoolLiteral literal)
            {
                return literal.GetIdentifierHash();
            }
        }
    }
}
