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

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

    /// <summary>
    ///     Class for representing a Facet object
    ///     This object is Immutable (not just set to readonly) and
    ///     some parts of the system are depending on that behavior
    /// </summary>
    [DebuggerDisplay("{Name,nq}={Value}")]
    public class Facet : MetadataItem
    {
        internal Facet()
        {
        }

        /// <summary>
        ///     The constructor for constructing a Facet object with the facet description and a value
        /// </summary>
        /// <param name="facetDescription"> The object describing this facet </param>
        /// <param name="value"> The value of the facet </param>
        /// <exception cref="System.ArgumentNullException">Thrown if facetDescription argument is null</exception>
        private Facet(FacetDescription facetDescription, object value)
            : base(MetadataFlags.Readonly)
        {
            Check.NotNull(facetDescription, "facetDescription");

            _facetDescription = facetDescription;
            _value = value;
        }

        /// <summary>
        ///     Creates a Facet instance with the specified value for the given
        ///     facet description.
        /// </summary>
        /// <param name="facetDescription"> The object describing this facet </param>
        /// <param name="value"> The value of the facet </param>
        /// <exception cref="System.ArgumentNullException">Thrown if facetDescription argument is null</exception>
        internal static Facet Create(FacetDescription facetDescription, object value)
        {
            return Create(facetDescription, value, false);
        }

        /// <summary>
        ///     Creates a Facet instance with the specified value for the given
        ///     facet description.
        /// </summary>
        /// <param name="facetDescription"> The object describing this facet </param>
        /// <param name="value"> The value of the facet </param>
        /// <param name="bypassKnownValues"> true to bypass caching and known values; false otherwise. </param>
        /// <exception cref="System.ArgumentNullException">Thrown if facetDescription argument is null</exception>
        internal static Facet Create(FacetDescription facetDescription, object value, bool bypassKnownValues)
        {
            DebugCheck.NotNull(facetDescription);

            if (!bypassKnownValues)
            {
                // Reuse facets with a null value.
                if (ReferenceEquals(value, null))
                {
                    return facetDescription.NullValueFacet;
                }

                // Reuse facets with a default value.
                if (Equals(facetDescription.DefaultValue, value))
                {
                    return facetDescription.DefaultValueFacet;
                }

                // Special case boolean facets.
                if (facetDescription.FacetType.Identity == "Edm.Boolean")
                {
                    var boolValue = (bool)value;
                    return facetDescription.GetBooleanFacet(boolValue);
                }
            }

            var result = new Facet(facetDescription, value);

            // Check the type of the value only if we know what the correct CLR type is
            if (value != null
                && !Helper.IsUnboundedFacetValue(result)
                && !Helper.IsVariableFacetValue(result)
                && result.FacetType.ClrType != null)
            {
                var valueType = value.GetType();
                Debug.Assert(
                    valueType == result.FacetType.ClrType
                    || result.FacetType.ClrType.IsAssignableFrom(valueType),
                    string.Format(
                        CultureInfo.CurrentCulture, "The facet {0} has type {1}, but a value of type {2} was supplied.", result.Name,
                        result.FacetType.ClrType, valueType)
                    );
            }

            return result;
        }

        /// <summary>
        ///     The object describing this facet.
        /// </summary>
        private readonly FacetDescription _facetDescription;

        /// <summary>
        ///     The value assigned to this facet.
        /// </summary>
        private readonly object _value;

        /// <summary>
        ///     Returns the kind of the type
        /// </summary>
        public override BuiltInTypeKind BuiltInTypeKind
        {
            get { return BuiltInTypeKind.Facet; }
        }

        /// <summary>
        ///     Gets the description object for describing the facet
        /// </summary>
        public FacetDescription Description
        {
            get { return _facetDescription; }
        }

        /// <summary>
        ///     Gets/Sets the name of the facet
        /// </summary>
        [MetadataProperty(PrimitiveTypeKind.String, false)]
        public virtual String Name
        {
            get { return _facetDescription.FacetName; }
        }

        /// <summary>
        ///     Gets/Sets the type of the facet
        /// </summary>
        [MetadataProperty(BuiltInTypeKind.EdmType, false)]
        public EdmType FacetType
        {
            get { return _facetDescription.FacetType; }
        }

        /// <summary>
        ///     Gets/Sets the value of the facet
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the Facet instance is in ReadOnly state</exception>
        [MetadataProperty(typeof(Object), false)]
        public virtual Object Value
        {
            get { return _value; }
        }

        /// <summary>
        ///     Gets the identity for this item as a string
        /// </summary>
        internal override string Identity
        {
            get { return _facetDescription.FacetName; }
        }

        /// <summary>
        ///     Indicates whether the value of the facet is unbounded
        /// </summary>
        public bool IsUnbounded
        {
            get { return ReferenceEquals(Value, EdmConstants.UnboundedValue); }
        }

        /// <summary>
        ///     Overriding System.Object.ToString to provide better String representation
        ///     for this type.
        /// </summary>
        public override string ToString()
        {
            return Name;
        }
    }
}
