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

using System;
using System.Collections.Generic;
using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...

// It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
// to prevent from simple mistakes during development (e.g. method argument validation 
// in cases where it was you who created the variables or the variables had already been validated or 
// in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default 
// "else" block is chosen why the new condition should be treated separately). This kind of asserts are 
// (can be) helpful when developing new code to avoid simple mistakes but have no or little value in 
// the shipped product. 
// PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions 
// about how the tree was built etc. - in these cases we probably want to throw an exception (this is
// what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct 
// or the tree was built/rewritten not the way we thought it was.
// Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
// PlanCompiler.Assert.

using System.Globalization;

using System.Data.Common;
using md = System.Data.Metadata.Edm;
using System.Data.Query.InternalTrees;

namespace System.Data.Query.PlanCompiler
{

    /// <summary>
    /// The kind of type-id in use
    /// </summary>
    internal enum TypeIdKind
    {
        UserSpecified = 0,
        Generated
    }

    /// <summary>
    /// The TypeInfo class encapsulates various pieces of information about a type.
    /// The most important of these include the "flattened" record type - corresponding
    /// to the type, and the TypeId field for nominal types
    /// </summary>
    internal class TypeInfo
    {

        #region private state
        private readonly md.TypeUsage m_type;    // the type
        private object m_typeId;            // the type's Id, assigned by the StructuredTypeInfo processing.
        private List<TypeInfo> m_immediateSubTypes;  // the list of children below this type in it's type hierarchy.
        private readonly TypeInfo m_superType;       // the type one level up in this types type hierarchy -- the base type.
        private readonly RootTypeInfo m_rootType;    // the top-most type in this types type hierarchy
        #endregion

        #region Constructors and factory methods

        /// <summary>
        /// Creates type information for a type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="superTypeInfo"></param>
        /// <returns></returns>
        internal static TypeInfo Create(md.TypeUsage type, TypeInfo superTypeInfo, ExplicitDiscriminatorMap discriminatorMap)
        {
            TypeInfo result;
            if (superTypeInfo == null)
            {
                result = new RootTypeInfo(type, discriminatorMap);
            }
            else
            {
                result = new TypeInfo(type, superTypeInfo);
            }
            return result;
        }

        protected TypeInfo(md.TypeUsage type, TypeInfo superType)
        {
            m_type = type;
            m_immediateSubTypes = new List<TypeInfo>();
            m_superType = superType;
            if (superType != null)
            {
                // Add myself to my supertype's list of subtypes
                superType.m_immediateSubTypes.Add(this);
                // my supertype's root type is mine as well
                m_rootType = superType.RootType;
            }
        }

        #endregion

        #region "public" properties for all types

        /// <summary>
        /// Is this the root type?
        /// True for entity, complex types and ref types, if this is the root of the
        /// hierarchy. 
        /// Always true for Record types
        /// </summary>
        internal bool IsRootType
        {
            get
            {
                return m_rootType == null;
            }
        }

        /// <summary>
        /// the types that derive from this type
        /// </summary>
        internal List<TypeInfo> ImmediateSubTypes
        {
            get
            {
                return m_immediateSubTypes;
            }
        }

        /// <summary>
        /// the immediate parent type of this type.
        /// </summary>
        internal TypeInfo SuperType
        {
            get
            {
                return m_superType;
            }
        }

        /// <summary>
        /// the top most type in the hierarchy.
        /// </summary>
        internal RootTypeInfo RootType
        {
            get
            {
                return m_rootType ?? (RootTypeInfo)this;
            }
        }
        /// <summary>
        /// The metadata type
        /// </summary>
        internal md.TypeUsage Type
        {
            get
            {
                return m_type;
            }
        }

        /// <summary>
        /// The typeid value for this type - only applies to nominal types
        /// </summary>
        internal object TypeId
        {
            get
            {
                return m_typeId;
            }
            set
            {
                m_typeId = value;
            }
        }

        #endregion

        #region "public" properties for root types

        // These properties are actually stored on the RootType but we let
        // let folks use the TypeInfo class as the proxy to get to them.
        // Essentially, they are mostly sugar to simplify coding.
        //
        // For example:
        //
        // You could either write:
        //
        //      typeinfo.RootType.FlattenedType
        //
        // or you can write:
        //
        //      typeinfo.FlattenedType
        //

        /// <summary>
        /// Flattened record version of the type
        /// </summary>
        internal virtual md.RowType FlattenedType
        {
            get
            {
                return RootType.FlattenedType;
            }
        }

        /// <summary>
        /// TypeUsage that encloses the Flattened record version of the type
        /// </summary>
        internal virtual md.TypeUsage FlattenedTypeUsage
        {
            get
            {
                return RootType.FlattenedTypeUsage;
            }
        }

        /// <summary>
        /// Get the property describing the entityset (if any)
        /// </summary>
        internal virtual md.EdmProperty EntitySetIdProperty
        {
            get
            {
                return RootType.EntitySetIdProperty;
            }
        }

        /// <summary>
        /// Does this type have an entitySetId property
        /// </summary>
        internal bool HasEntitySetIdProperty
        {
            get
            {
                return RootType.EntitySetIdProperty != null;
            }
        }

        /// <summary>
        /// Get the nullSentinel property (if any)
        /// </summary>
        internal virtual md.EdmProperty NullSentinelProperty
        {
            get
            {
                return RootType.NullSentinelProperty;
            }
        }

        /// <summary>
        /// Does this type have a nullSentinel property?
        /// </summary>
        internal bool HasNullSentinelProperty
        {
            get
            {
                return RootType.NullSentinelProperty != null;
            }
        }

        /// <summary>
        /// The typeid property in the flattened type - applies only to nominal types
        /// this will be used as the type discriminator column.
        /// </summary>
        internal virtual md.EdmProperty TypeIdProperty
        {
            get
            {
                return RootType.TypeIdProperty;
            }
        }

        /// <summary>
        /// Does this type need a typeid property? (Needed for complex types and entity types in general)
        /// </summary>
        internal bool HasTypeIdProperty
        {
            get
            {
                return RootType.TypeIdProperty != null;
            }
        }

        /// <summary>
        /// All the properties of this type.
        /// </summary>
        internal virtual IEnumerable<PropertyRef> PropertyRefList
        {
            get
            {
                return RootType.PropertyRefList;
            }
        }

        /// <summary>
        /// Get the new property for the supplied propertyRef
        /// </summary>
        /// <param name="propertyRef">property reference (on the old type)</param>
        /// <returns></returns>
        internal md.EdmProperty GetNewProperty(PropertyRef propertyRef)
        {
            md.EdmProperty property;
            bool result = TryGetNewProperty(propertyRef, true, out property);
            Debug.Assert(result, "Should have thrown if the property was not found");
            return property;
        }

        /// <summary>
        /// Try get the new property for the supplied propertyRef
        /// </summary>
        /// <param name="propertyRef">property reference (on the old type)</param>
        /// <param name="throwIfMissing">throw if the property is not found</param>
        /// <param name="newProperty">the corresponding property on the new type</param>
        /// <returns></returns>
        internal bool TryGetNewProperty(PropertyRef propertyRef, bool throwIfMissing, out md.EdmProperty newProperty)
        {
            return this.RootType.TryGetNewProperty(propertyRef, throwIfMissing, out newProperty);
        }

        /// <summary>
        /// Get the list of "key" properties (in the flattened type)
        /// </summary>
        /// <returns>the key property equivalents in the flattened type</returns>
        internal IEnumerable<PropertyRef> GetKeyPropertyRefs()
        {
            md.EntityTypeBase entityType = null;
            md.RefType refType = null;
            if (TypeHelpers.TryGetEdmType<md.RefType>(m_type, out refType))
            {
                entityType = refType.ElementType;
            }
            else
            {
                entityType = TypeHelpers.GetEdmType<md.EntityTypeBase>(m_type);
            }

            // Walk through the list of keys of the entity type, and find their analogs in the
            // "flattened" type
            foreach (md.EdmMember p in entityType.KeyMembers)
            {
                // Eventually this could be RelationshipEndMember, but currently only properties are suppported as key members
                PlanCompiler.Assert(p is md.EdmProperty, "Non-EdmProperty key members are not supported");
                SimplePropertyRef spr = new SimplePropertyRef(p);
                yield return spr;
            }
        }

        /// <summary>
        /// Get the list of "identity" properties in the flattened type.
        /// The identity properties include the entitysetid property, followed by the
        /// key properties
        /// </summary>
        /// <returns>List of identity properties</returns>
        internal IEnumerable<PropertyRef> GetIdentityPropertyRefs()
        {
            if (this.HasEntitySetIdProperty)
            {
                yield return EntitySetIdPropertyRef.Instance;
            }
            foreach (PropertyRef p in this.GetKeyPropertyRefs())
            {
                yield return p;
            }
        }

        /// <summary>
        /// Get the list of all properties in the flattened type
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<PropertyRef> GetAllPropertyRefs()
        {
            foreach (PropertyRef p in this.PropertyRefList)
            {
                yield return p;
            }
        }

        /// <summary>
        /// Get the list of all properties in the flattened type
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<md.EdmProperty> GetAllProperties()
        {
            foreach (md.EdmProperty m in this.FlattenedType.Properties)
            {
                yield return m;
            }
        }

        /// <summary>
        /// Gets all types in the hierarchy rooted at this.
        /// </summary>
        internal List<TypeInfo> GetTypeHierarchy()
        {
            List<TypeInfo> result = new List<TypeInfo>();
            GetTypeHierarchy(result);
            return result;
        }

        /// <summary>
        /// Adds all types in the hierarchy to the given list.
        /// </summary>
        private void GetTypeHierarchy(List<TypeInfo> result)
        {
            result.Add(this);
            foreach (TypeInfo subType in this.ImmediateSubTypes)
            {
                subType.GetTypeHierarchy(result);
            }
        }
        #endregion
    }

    /// <summary>
    /// A subclass of the TypeInfo class above that only represents information
    /// about "root" types
    /// </summary>
    internal class RootTypeInfo : TypeInfo
    {

        #region private state
        private readonly List<PropertyRef> m_propertyRefList;
        private readonly Dictionary<PropertyRef, md.EdmProperty> m_propertyMap;
        private md.EdmProperty m_nullSentinelProperty;
        private md.EdmProperty m_typeIdProperty;
        private TypeIdKind m_typeIdKind;
        private md.TypeUsage m_typeIdType;
        private readonly ExplicitDiscriminatorMap m_discriminatorMap;
        private md.EdmProperty m_entitySetIdProperty;
        private md.RowType m_flattenedType;
        private md.TypeUsage m_flattenedTypeUsage;
        #endregion

        #region Constructor

        /// <summary>
        /// Constructor for a root type
        /// </summary>
        /// <param name="type"></param>
        internal RootTypeInfo(md.TypeUsage type, ExplicitDiscriminatorMap discriminatorMap)
            : base(type, null)
        {
            PlanCompiler.Assert(type.EdmType.BaseType == null, "only root types allowed here");

            m_propertyMap = new Dictionary<PropertyRef, md.EdmProperty>();
            m_propertyRefList = new List<PropertyRef>();
            m_discriminatorMap = discriminatorMap;
            m_typeIdKind = TypeIdKind.Generated;
        }

        #endregion

        #region "public" surface area

        /// <summary>
        /// Kind of the typeid column (if any)
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal TypeIdKind TypeIdKind
        {
            get { return m_typeIdKind; }
            set { m_typeIdKind = value; }
        }

        /// <summary>
        /// Datatype of the typeid column (if any)
        /// </summary>
        internal md.TypeUsage TypeIdType
        {
            get { return m_typeIdType; }
            set { m_typeIdType = value; }
        }
        /// <summary>
        /// Add a mapping from the propertyRef (of the old type) to the 
        /// corresponding property in the new type.
        /// 
        /// NOTE: Only to be used by StructuredTypeInfo
        /// </summary>
        /// <param name="propertyRef"></param>
        /// <param name="newProperty"></param>
        internal void AddPropertyMapping(PropertyRef propertyRef, md.EdmProperty newProperty)
        {
            m_propertyMap[propertyRef] = newProperty;
            if (propertyRef is TypeIdPropertyRef)
            {
                m_typeIdProperty = newProperty;
            }
            else if (propertyRef is EntitySetIdPropertyRef)
            {
                m_entitySetIdProperty = newProperty;
            }
            else if (propertyRef is NullSentinelPropertyRef)
            {
                m_nullSentinelProperty = newProperty;
            }
        }

        /// <summary>
        /// Adds a new property reference to the list of desired properties
        /// NOTE: Only to be used by StructuredTypeInfo
        /// </summary>
        /// <param name="propertyRef"></param>
        internal void AddPropertyRef(PropertyRef propertyRef)
        {
            m_propertyRefList.Add(propertyRef);
        }

        /// <summary>
        /// Flattened record version of the type
        /// </summary>
        internal new md.RowType FlattenedType
        {
            get
            {
                return m_flattenedType;
            }
            set
            {
                m_flattenedType = value;
                m_flattenedTypeUsage = md.TypeUsage.Create(value);
            }
        }

        /// <summary>
        /// TypeUsage that encloses the Flattened record version of the type
        /// </summary>
        internal new md.TypeUsage FlattenedTypeUsage
        {
            get
            {
                return m_flattenedTypeUsage;
            }
        }

        /// <summary>
        /// Gets map information for types mapped using simple discriminator pattern.
        /// </summary>
        internal ExplicitDiscriminatorMap DiscriminatorMap
        {
            get
            {
                return m_discriminatorMap;
            }
        }

        /// <summary>
        /// Get the property describing the entityset (if any)
        /// </summary>
        internal new md.EdmProperty EntitySetIdProperty
        {
            get
            {
                return m_entitySetIdProperty;
            }
        }

        internal new md.EdmProperty NullSentinelProperty
        {
            get
            {
                return m_nullSentinelProperty;
            }
        }

        /// <summary>
        /// Get the list of property refs for this type
        /// </summary>
        internal new IEnumerable<PropertyRef> PropertyRefList
        {
            get
            {
                return m_propertyRefList;
            }
        }

        /// <summary>
        /// Determines the offset for structured types in Flattened type. For instance, if the original type is of the form:
        /// 
        ///     { int X, ComplexType Y }
        ///     
        /// and the flattened type is of the form:
        /// 
        ///     { int X, Y_ComplexType_Prop1, Y_ComplexType_Prop2 }
        ///     
        /// GetNestedStructureOffset(Y) returns 1
        /// </summary>
        /// <param name="property">Complex property.</param>
        /// <returns>Offset.</returns>
        internal int GetNestedStructureOffset(PropertyRef property)
        {
            // m_propertyRefList contains every element of the flattened type
            for (int i = 0; i < m_propertyRefList.Count; i++)
            {
                NestedPropertyRef nestedPropertyRef = m_propertyRefList[i] as NestedPropertyRef;

                // match offset of the first element of the complex type property
                if (null != nestedPropertyRef && nestedPropertyRef.InnerProperty.Equals(property))
                {
                    return i;
                }
            }
            PlanCompiler.Assert(false, "no complex structure " + property + " found in TypeInfo");
            // return something so that the compiler doesn't complain
            return default(int);
        }

        /// <summary>
        /// Try get the new property for the supplied propertyRef
        /// </summary>
        /// <param name="propertyRef">property reference (on the old type)</param>
        /// <param name="throwIfMissing">throw if the property is not found</param>
        /// <param name="property">the corresponding property on the new type</param>
        /// <returns></returns>
        internal new bool TryGetNewProperty(PropertyRef propertyRef, bool throwIfMissing, out md.EdmProperty property)
        {
            bool result = m_propertyMap.TryGetValue(propertyRef, out property);
            if (throwIfMissing && !result)
            {
                {
                    PlanCompiler.Assert(false, "Unable to find property " + propertyRef.ToString() + " in type " + this.Type.EdmType.Identity);
                }
            }
            return result;
        }

        /// <summary>
        /// The typeid property in the flattened type - applies only to nominal types
        /// this will be used as the type discriminator column.
        /// </summary>
        internal new md.EdmProperty TypeIdProperty
        {
            get
            {
                return m_typeIdProperty;
            }
        }

        #endregion
    }
}
