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

namespace System.Data.Entity.Core.Query.InternalTrees
{
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Diagnostics;
    using System.Globalization;

#if DEBUG
    /// <summary>
    /// The BasicValidator validates the shape of the IQT. It ensures that the
    /// various Ops in the tree have the right kinds and number of arguments.
    /// </summary>
    internal class BasicValidator : BasicOpVisitor
    {
        #region constructors

        protected BasicValidator(Command command)
        {
            m_command = command;
        }

        #endregion

        #region private surface

        protected void Validate(Node node)
        {
            VisitNode(node);
        }

        #region AssertHelpers

        protected static void Assert(bool condition, string format, int arg0)
        {
            if (!condition)
            {
                Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, format, arg0));
            }
        }

        protected static void Assert(bool condition, string format, OpType op)
        {
            if (!condition)
            {
                Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, format, Dump.AutoString.ToString(op)));
            }
        }

        protected static void Assert(bool condition, string format, OpType op, object arg1)
        {
            if (!condition)
            {
                Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, format, Dump.AutoString.ToString(op), arg1));
            }
        }

        protected static void Assert(bool condition, string format, OpType op, object arg1, object arg2)
        {
            if (!condition)
            {
                Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, format, Dump.AutoString.ToString(op), arg1, arg2));
            }
        }

        protected static void Assert(bool condition, string format, params object[] args)
        {
            if (!condition)
            {
                Debug.Assert(false, String.Format(CultureInfo.InvariantCulture, format, args));
            }
        }

        protected static void AssertArity(Node n, int arity)
        {
            Assert(
                arity == n.Children.Count, "Op Arity mismatch for Op {0}: Expected {1} arguments; found {2} arguments", n.Op.OpType, arity,
                n.Children.Count);
        }

        protected static void AssertMinimumArity(Node n, int minArity)
        {
            Assert(
                minArity <= n.Children.Count, "Op Arity mismatch for Op {0}: Expected minimum {1} arguments; found {2} arguments", n.Op.OpType, minArity,
                n.Children.Count);
        }

        protected static void AssertArity(Node n)
        {
            if (n.Op.Arity
                != Op.ArityVarying)
            {
                AssertArity(n, n.Op.Arity);
            }
        }

        protected static void AssertBoolean(TypeUsage type)
        {
            Assert(
                TypeSemantics.IsPrimitiveType(type, PrimitiveTypeKind.Boolean), "Type Mismatch: Expected Boolean; found {0} instead",
                type.ToString());
        }

        protected static void AssertCollectionType(TypeUsage type)
        {
            Assert(
                TypeSemantics.IsCollectionType(type), "Type Mismatch: Expected Collection type: Found {0}", type.ToString());
        }

        protected static void AssertEqualTypes(TypeUsage type1, TypeUsage type2)
        {
            Assert(
                Command.EqualTypes(type1, type2),
                "Type mismatch: " + type1.Identity + ", " + type2.Identity);
        }

        protected static void AssertEqualTypes(TypeUsage type1, EdmType type2)
        {
            AssertEqualTypes(type1, TypeUsage.Create(type2));
        }

        protected static void AssertBooleanOp(Op op)
        {
            AssertBoolean(op.Type);
        }

        protected static void AssertRelOp(Op op)
        {
            Assert(op.IsRelOp, "OpType Mismatch: Expected RelOp; found {0}", op.OpType);
        }

        protected static void AssertRelOpOrPhysicalOp(Op op)
        {
            Assert(op.IsRelOp || op.IsPhysicalOp, "OpType Mismatch: Expected RelOp or PhysicalOp; found {0}", op.OpType);
        }

        protected static void AssertScalarOp(Op op)
        {
            Assert(op.IsScalarOp, "OpType Mismatch: Expected ScalarOp; found {0}", op.OpType);
        }

        protected static void AssertOpType(Op op, OpType opType)
        {
            Assert(op.OpType == opType, "OpType Mismatch: Expected {0}; found {1}", op.OpType, Dump.AutoString.ToString(opType));
        }

        protected static void AssertUnexpectedOp(Op op)
        {
            Assert(false, "Unexpected OpType {0}", op.OpType);
        }

        #endregion

        #region Visitors

        protected override void VisitDefault(Node n)
        {
            Assert(n.Id >= 0, "Bad node id {0}", n.Id);
            VisitChildren(n);
            AssertArity(n);
        }

        #region ScalarOps

        protected override void VisitScalarOpDefault(ScalarOp op, Node n)
        {
            VisitDefault(n);
            Assert(op.Type != null, "ScalarOp {0} with no datatype!", op.OpType);
            if (op.OpType != OpType.Element
                &&
                op.OpType != OpType.Exists
                &&
                op.OpType != OpType.Collect)
            {
                foreach (var chi in n.Children)
                {
                    AssertScalarOp(chi.Op);
                }
            }
        }

        public override void Visit(AggregateOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
        }

        public override void Visit(CaseOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            Assert(
                (n.Children.Count >= 3 && n.Children.Count % 2 == 1),
                "CaseOp: Expected odd number of arguments, and at least 3; found {0}", n.Children.Count);

            // Validate that each when statement is of type Boolean
            for (var i = 0; i < n.Children.Count - 1; i += 2)
            {
                Assert(TypeSemantics.IsBooleanType(n.Children[i].Op.Type), "Encountered a when node with a non-boolean return type");
            }

            // Ensure that the then clauses, the else clause and the result type are all the same
            for (var i = 1; i < n.Children.Count - 1; i += 2)
            {
                AssertEqualTypes(n.Op.Type, n.Children[i].Op.Type);
            }
            AssertEqualTypes(n.Op.Type, n.Children[n.Children.Count - 1].Op.Type);
        }

        public override void Visit(ComparisonOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            AssertBooleanOp(op);
            AssertEqualTypes(n.Child0.Op.Type, n.Child1.Op.Type);
        }

        public override void Visit(ConditionalOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            switch (op.OpType)
            {
                case OpType.And:
                case OpType.Or:
                    AssertArity(n, 2);
                    AssertBooleanOp(n.Child0.Op);
                    AssertBooleanOp(n.Child1.Op);
                    break;
                case OpType.In:
                    AssertMinimumArity(n, 2);
                    break;
                case OpType.Not:
                    AssertArity(n, 1);
                    AssertBooleanOp(n.Child0.Op);
                    break;
                case OpType.IsNull:
                    AssertArity(n, 1);
                    break;
                default:
                    AssertUnexpectedOp(op);
                    break;
            }
            AssertBooleanOp(op);
        }

        public override void Visit(ArithmeticOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            switch (op.OpType)
            {
                case OpType.Plus:
                case OpType.Minus:
                case OpType.Multiply:
                case OpType.Divide:
                case OpType.Modulo:
                    AssertEqualTypes(n.Child0.Op.Type, n.Child1.Op.Type);
                    AssertEqualTypes(n.Op.Type, n.Child0.Op.Type);
                    AssertArity(n, 2);
                    break;
                case OpType.UnaryMinus:
                    AssertArity(n, 1);
                    break;
                default:
                    AssertUnexpectedOp(op);
                    break;
            }
        }

        public override void Visit(ElementOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            AssertRelOp(n.Child0.Op);
        }

        public override void Visit(CollectOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            AssertOpType(n.Child0.Op, OpType.PhysicalProject);
            AssertCollectionType(op.Type);
        }

        public override void Visit(DerefOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            Assert(TypeSemantics.IsEntityType(op.Type), "Expected an entity type. Found " + op.Type);
            Assert(TypeSemantics.IsReferenceType(n.Child0.Op.Type), "Expected a ref type. Found " + n.Child0.Op.Type);
            var r = n.Child0.Op.Type.EdmType as RefType;
            Assert(r.ElementType.EdmEquals(op.Type.EdmType), "Inconsistent types");
        }

        public override void Visit(ExistsOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            AssertRelOp(n.Child0.Op);
            AssertBooleanOp(op);
        }

        public override void Visit(PropertyOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            AssertEqualTypes(n.Child0.Op.Type, op.PropertyInfo.DeclaringType);
        }

        public override void Visit(RelPropertyOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            Assert(m_command.IsRelPropertyReferenced(op.PropertyInfo), "no such rel property:", op.PropertyInfo);
            Assert(
                TypeSemantics.IsEntityType(n.Child0.Op.Type), "argument to RelPropertyOp must be an entity type. Found: ", n.Child0.Op.Type);
        }

        public override void Visit(FunctionOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            Assert(
                op.Function.Parameters.Count == n.Children.Count, "FunctionOp: Argument count ({0}) does not match parameter count ({1})",
                n.Children.Count, op.Function.Parameters.Count);
            for (var idx = 0; idx < n.Children.Count; idx++)
            {
                AssertEqualTypes(n.Children[idx].Op.Type, op.Function.Parameters[idx].TypeUsage);
            }
        }

        public override void Visit(SoftCastOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
            // Aconrad 9/21/06 - temporarily removing check here 
            //  because the assert wrongly fails in some cases where the types are promotable,
            //  but the facets are not.  Put this back when that issue is solved.
            // Assert(TypeSemantics.IsEquivalentOrPromotableTo(n.Child0.Op.Type, op.Type), "Illegal SoftCastOp: Cannot promote input type {0} to target type {1}", n.Child0.Op.Type.Identity, op.Type.Identity);
        }

        public override void Visit(NavigateOp op, Node n)
        {
            VisitScalarOpDefault(op, n);
        }

        #endregion

        #region AncillaryOps

        protected override void VisitAncillaryOpDefault(AncillaryOp op, Node n)
        {
            VisitDefault(n);
        }

        public override void Visit(VarDefOp op, Node n)
        {
            VisitAncillaryOpDefault(op, n);
            AssertScalarOp(n.Child0.Op);
            var varDefOp = op;
            AssertEqualTypes(varDefOp.Var.Type, n.Child0.Op.Type);
        }

        public override void Visit(VarDefListOp op, Node n)
        {
            VisitDefault(n);
            foreach (var chi in n.Children)
            {
                AssertOpType(chi.Op, OpType.VarDef);
            }
        }

        #endregion

        #region RelOps

        protected override void VisitRelOpDefault(RelOp op, Node n)
        {
            VisitDefault(n);
        }

        protected override void VisitJoinOp(JoinBaseOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            if (op.OpType
                == OpType.CrossJoin)
            {
                Assert(n.Children.Count >= 2, "CrossJoinOp needs at least 2 arguments; found only {0}", n.Children.Count);
                return;
            }
            AssertRelOpOrPhysicalOp(n.Child0.Op);
            AssertRelOpOrPhysicalOp(n.Child1.Op);
            AssertScalarOp(n.Child2.Op);
            AssertBooleanOp(n.Child2.Op);
        }

        protected override void VisitApplyOp(ApplyBaseOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
            AssertRelOpOrPhysicalOp(n.Child1.Op);
        }

        protected override void VisitSetOp(SetOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
            AssertRelOpOrPhysicalOp(n.Child1.Op);
            //
            // Ensure that the corresponding setOp Vars are all of the same
            // type
            //
            foreach (var varMap in op.VarMap)
            {
                foreach (var kv in varMap)
                {
                    AssertEqualTypes(kv.Key.Type, kv.Value.Type);
                }
            }
        }

        protected override void VisitSortOp(SortBaseOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
        }

        public override void Visit(ConstrainedSortOp op, Node n)
        {
            base.Visit(op, n);
            AssertScalarOp(n.Child1.Op);
            Assert(
                TypeSemantics.IsIntegerNumericType(n.Child1.Op.Type), "ConstrainedSortOp Skip Count Node must have an integer result type");
            AssertScalarOp(n.Child2.Op);
            Assert(TypeSemantics.IsIntegerNumericType(n.Child2.Op.Type), "ConstrainedSortOp Limit Node must have an integer result type");
        }

        public override void Visit(ScanTableOp op, Node n)
        {
            VisitRelOpDefault(op, n);
        }

        public override void Visit(ScanViewOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOp(n.Child0.Op);
        }

        public override void Visit(FilterOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
            AssertScalarOp(n.Child1.Op);
            AssertBooleanOp(n.Child1.Op);
        }

        public override void Visit(ProjectOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
            AssertOpType(n.Child1.Op, OpType.VarDefList);
        }

        public override void Visit(UnnestOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertOpType(n.Child0.Op, OpType.VarDef);
        }

        protected override void VisitGroupByOp(GroupByBaseOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);

            for (var i = 1; i < n.Children.Count; i++)
            {
                AssertOpType(n.Children[i].Op, OpType.VarDefList);
            }
        }

        public override void Visit(GroupByIntoOp op, Node n)
        {
            VisitGroupByOp(op, n);
            Assert(n.Child3.Children.Count > 0, "GroupByInto with no group aggregate vars");
        }

        public override void Visit(DistinctOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOp(n.Child0.Op);
        }

        public override void Visit(SingleRowTableOp op, Node n)
        {
            VisitRelOpDefault(op, n);
        }

        public override void Visit(SingleRowOp op, Node n)
        {
            VisitRelOpDefault(op, n);
            AssertRelOpOrPhysicalOp(n.Child0.Op);
        }

        #endregion

        #region PhysicalOps

        protected override void VisitPhysicalOpDefault(PhysicalOp op, Node n)
        {
            VisitDefault(n);
        }

        public override void Visit(PhysicalProjectOp op, Node n)
        {
            VisitPhysicalOpDefault(op, n);
            Assert(n.Children.Count >= 1, "PhysicalProjectOp needs at least 1 arg: found {0}", n.Children.Count);
            foreach (var chi in n.Children)
            {
                AssertRelOpOrPhysicalOp(chi.Op);
            }
        }

        public override void Visit(SingleStreamNestOp op, Node n)
        {
            VisitPhysicalOpDefault(op, n);
            AssertRelOp(n.Child0.Op);
        }

        public override void Visit(MultiStreamNestOp op, Node n)
        {
            VisitPhysicalOpDefault(op, n);
            Assert(n.Children.Count > 1, "MultiStreamNestOp needs at least 2 arguments: found {0}", n.Children.Count);
            foreach (var chi in n.Children)
            {
                AssertRelOpOrPhysicalOp(chi.Op);
            }
        }

        #endregion

        #endregion

        #endregion

        #region private state

        private readonly Command m_command;

        #endregion
    }
#endif
    // DEBUG
}
