/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Runtime.Serialization;
using System.Security.Permissions;
using Microsoft.Contracts;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting {
    /// <summary>
    /// Provides an interned representation of a string which supports both case sensitive and case insensitive
    /// lookups.
    /// 
    /// By default all lookups are case sensitive.  Case insensitive lookups can be performed by first creating
    /// a normal SymbolId for a given string and then accessing the CaseInsensitiveIdentifier property.  Using
    /// the case insensitive identifier during a lookup will cause the lookup to be case insensitive.
    /// </summary>
    [Serializable]
    public struct SymbolId : ISerializable, IComparable, IComparable<SymbolId>, IEquatable<SymbolId> {
        private readonly int _id;

        public SymbolId(int value) {
            _id = value;
        }

        public SymbolId(SymbolId value) {
            _id = value._id;
        }

        public int Id {
            get { return _id; }
        }

        public SymbolId CaseInsensitiveIdentifier {
            get { return new SymbolId(_id & ~SymbolTable.CaseVersionMask); }
        }

        public int CaseInsensitiveId {
            get { return _id & ~SymbolTable.CaseVersionMask; }
        }

        public bool IsCaseInsensitive {
            get {
                return (_id & SymbolTable.CaseVersionMask) == 0;
            }
        }

        [Confined]
        public override bool Equals(object obj) {
            if (!(obj is SymbolId)) return false;
            return Equals((SymbolId)obj);
        }

        [StateIndependent]
        public bool Equals(SymbolId other) {

            if (_id == other._id) {
                return true;
            } else if (IsCaseInsensitive || other.IsCaseInsensitive) {
                return (_id & ~SymbolTable.CaseVersionMask) == (other._id & ~SymbolTable.CaseVersionMask);
            }
            return false;
        }

        [Confined]
        public override int GetHashCode() {
            return _id & ~SymbolTable.CaseVersionMask;
        }

        /// <summary>
        /// Override of ToString.
        /// DO NOT USE THIS METHOD TO RETRIEVE STRING THAT THE SYMBOL REPRESENTS
        /// Use SymbolTable.IdToString(SymbolId) instead.
        /// </summary>
        [Confined]
        public override string ToString() {
            return SymbolTable.IdToString(this);
        }

        public static explicit operator SymbolId(string s) {
            return SymbolTable.StringToId(s);
        }

        public static bool operator ==(SymbolId a, SymbolId b) {
            return a.Equals(b);
        }

        public static bool operator !=(SymbolId a, SymbolId b) {
            return !a.Equals(b);
        }

        #region IComparable Members

        public int CompareTo(object obj) {
            if (!(obj is SymbolId)) {
                return -1;
            }

            return CompareTo((SymbolId)obj);
        }

        public int CompareTo(SymbolId other) {
            // Note that we could just compare _id which will result in a faster comparison. However, that will
            // mean that sorting will depend on the order in which the symbols were interned. This will often
            // not be expected. Hence, we just compare the symbol strings

            string thisString = SymbolTable.IdToString(this);
            string otherString = SymbolTable.IdToString(other);
            return thisString.CompareTo(otherString);
        }

        #endregion

        #region Cross-Domain/Process Serialization Support

#if !SILVERLIGHT // Security, SerializationInfo, StreamingContext
        // When leaving a context we serialize out our ID as a name
        // rather than a raw ID.  When we enter a new context we 
        // consult it's FieldTable to get the ID of the symbol name in
        // the new context.

        private SymbolId(SerializationInfo info, StreamingContext context) {
            ContractUtils.RequiresNotNull(info, "info");

            _id = SymbolTable.StringToId(info.GetString("symbolName"))._id;
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            ContractUtils.RequiresNotNull(info, "info");

            info.AddValue("symbolName", SymbolTable.IdToString(this));
        }
#endif

        #endregion

        public const int EmptyId = 0;
        /// <summary>SymbolId for null string</summary>
        public static readonly SymbolId Empty = new SymbolId(EmptyId);

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")]
        public static readonly SymbolId[] EmptySymbols = new SymbolId[0];

        public bool IsEmpty {
            get { return _id == EmptyId; }
        }

        public const int InvalidId = -1;
        /// <summary>SymbolId to represent invalid value</summary>
        public static readonly SymbolId Invalid = new SymbolId(InvalidId);

        public bool IsInvalid {
            get { return _id == InvalidId; }
        }
    }
}
