//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// Copyright (c) 2008 - 2015 Jb Evain
// Copyright (c) 2008 - 2011 Novell, Inc.
//
// Licensed under the MIT/X11 license.
//

using MonoFN.Cecil.Metadata;
using MonoFN.Collections.Generic;
using System;
using System.Threading;

namespace MonoFN.Cecil
{
    public enum MetadataType : byte
    {
        Void = ElementType.Void,
        Boolean = ElementType.Boolean,
        Char = ElementType.Char,
        SByte = ElementType.I1,
        Byte = ElementType.U1,
        Int16 = ElementType.I2,
        UInt16 = ElementType.U2,
        Int32 = ElementType.I4,
        UInt32 = ElementType.U4,
        Int64 = ElementType.I8,
        UInt64 = ElementType.U8,
        Single = ElementType.R4,
        Double = ElementType.R8,
        String = ElementType.String,
        Pointer = ElementType.Ptr,
        ByReference = ElementType.ByRef,
        ValueType = ElementType.ValueType,
        Class = ElementType.Class,
        Var = ElementType.Var,
        Array = ElementType.Array,
        GenericInstance = ElementType.GenericInst,
        TypedByReference = ElementType.TypedByRef,
        IntPtr = ElementType.I,
        UIntPtr = ElementType.U,
        FunctionPointer = ElementType.FnPtr,
        Object = ElementType.Object,
        MVar = ElementType.MVar,
        RequiredModifier = ElementType.CModReqD,
        OptionalModifier = ElementType.CModOpt,
        Sentinel = ElementType.Sentinel,
        Pinned = ElementType.Pinned
    }

    public class TypeReference : MemberReference, IGenericParameterProvider, IGenericContext
    {
        private string @namespace;
        private bool value_type;
        internal IMetadataScope scope;
        internal ModuleDefinition module;
        internal ElementType etype = ElementType.None;
        private string fullname;
        protected Collection<GenericParameter> generic_parameters;
        public override string Name
        {
            get { return base.Name; }
            set
            {
                if (IsWindowsRuntimeProjection && value != base.Name)
                    throw new InvalidOperationException("Projected type reference name can't be changed.");
                base.Name = value;
                ClearFullName();
            }
        }
        public virtual string Namespace
        {
            get { return @namespace; }
            set
            {
                if (IsWindowsRuntimeProjection && value != @namespace)
                    throw new InvalidOperationException("Projected type reference namespace can't be changed.");
                @namespace = value;
                ClearFullName();
            }
        }
        public virtual bool IsValueType
        {
            get { return value_type; }
            set { value_type = value; }
        }
        public override ModuleDefinition Module
        {
            get
            {
                if (module != null)
                    return module;

                TypeReference declaring_type = DeclaringType;
                if (declaring_type != null)
                    return declaring_type.Module;

                return null;
            }
        }
        internal TypeReferenceProjection WindowsRuntimeProjection
        {
            get { return (TypeReferenceProjection)projection; }
            set { projection = value; }
        }
        IGenericParameterProvider IGenericContext.Type
        {
            get { return this; }
        }
        IGenericParameterProvider IGenericContext.Method
        {
            get { return null; }
        }
        GenericParameterType IGenericParameterProvider.GenericParameterType
        {
            get { return GenericParameterType.Type; }
        }
        public virtual bool HasGenericParameters
        {
            get { return !generic_parameters.IsNullOrEmpty(); }
        }
        public virtual Collection<GenericParameter> GenericParameters
        {
            get
            {
                if (generic_parameters == null)
                    Interlocked.CompareExchange(ref generic_parameters, new GenericParameterCollection(this), null);

                return generic_parameters;
            }
        }
        public virtual IMetadataScope Scope
        {
            get
            {
                TypeReference declaring_type = DeclaringType;
                if (declaring_type != null)
                    return declaring_type.Scope;

                return scope;
            }
            set
            {
                TypeReference declaring_type = DeclaringType;
                if (declaring_type != null)
                {
                    if (IsWindowsRuntimeProjection && value != declaring_type.Scope)
                        throw new InvalidOperationException("Projected type scope can't be changed.");
                    declaring_type.Scope = value;
                    return;
                }

                if (IsWindowsRuntimeProjection && value != scope)
                    throw new InvalidOperationException("Projected type scope can't be changed.");
                scope = value;
            }
        }
        public bool IsNested
        {
            get { return DeclaringType != null; }
        }
        public override TypeReference DeclaringType
        {
            get { return base.DeclaringType; }
            set
            {
                if (IsWindowsRuntimeProjection && value != base.DeclaringType)
                    throw new InvalidOperationException("Projected type declaring type can't be changed.");
                base.DeclaringType = value;
                ClearFullName();
            }
        }
        public override string FullName
        {
            get
            {
                if (fullname != null)
                    return fullname;

                string new_fullname = this.TypeFullName();

                if (IsNested)
                    new_fullname = DeclaringType.FullName + "/" + new_fullname;
                Interlocked.CompareExchange(ref fullname, new_fullname, null);
                return fullname;
            }
        }
        public virtual bool IsByReference
        {
            get { return false; }
        }
        public virtual bool IsPointer
        {
            get { return false; }
        }
        public virtual bool IsSentinel
        {
            get { return false; }
        }
        public virtual bool IsArray
        {
            get { return false; }
        }
        public virtual bool IsGenericParameter
        {
            get { return false; }
        }
        public virtual bool IsGenericInstance
        {
            get { return false; }
        }
        public virtual bool IsRequiredModifier
        {
            get { return false; }
        }
        public virtual bool IsOptionalModifier
        {
            get { return false; }
        }
        public virtual bool IsPinned
        {
            get { return false; }
        }
        public virtual bool IsFunctionPointer
        {
            get { return false; }
        }
        public virtual bool IsPrimitive
        {
            get { return etype.IsPrimitive(); }
        }
        public virtual MetadataType MetadataType
        {
            get
            {
                switch (etype)
                {
                    case ElementType.None:
                        return IsValueType ? MetadataType.ValueType : MetadataType.Class;
                    default:
                        return (MetadataType)etype;
                }
            }
        }

        protected TypeReference(string @namespace, string name) : base(name)
        {
            this.@namespace = @namespace ?? string.Empty;
            token = new(TokenType.TypeRef, 0);
        }

        public TypeReference(string @namespace, string name, ModuleDefinition module, IMetadataScope scope) : this(@namespace, name)
        {
            this.module = module;
            this.scope = scope;
        }

        public TypeReference(string @namespace, string name, ModuleDefinition module, IMetadataScope scope, bool valueType) : this(@namespace, name, module, scope)
        {
            value_type = valueType;
        }

        protected virtual void ClearFullName()
        {
            fullname = null;
        }

        public virtual TypeReference GetElementType()
        {
            return this;
        }

        protected override IMemberDefinition ResolveDefinition()
        {
            return Resolve();
        }

        public new virtual TypeDefinition Resolve()
        {
            ModuleDefinition module = Module;
            if (module == null)
                throw new NotSupportedException();

            return module.Resolve(this);
        }
    }

    internal static partial class Mixin
    {
        public static bool IsPrimitive(this ElementType self)
        {
            switch (self)
            {
                case ElementType.Boolean:
                case ElementType.Char:
                case ElementType.I:
                case ElementType.U:
                case ElementType.I1:
                case ElementType.U1:
                case ElementType.I2:
                case ElementType.U2:
                case ElementType.I4:
                case ElementType.U4:
                case ElementType.I8:
                case ElementType.U8:
                case ElementType.R4:
                case ElementType.R8:
                    return true;
                default:
                    return false;
            }
        }

        public static string TypeFullName(this TypeReference self)
        {
            return string.IsNullOrEmpty(self.Namespace) ? self.Name : self.Namespace + '.' + self.Name;
        }

        public static bool IsTypeOf(this TypeReference self, string @namespace, string name)
        {
            return self.Name == name && self.Namespace == @namespace;
        }

        public static bool IsTypeSpecification(this TypeReference type)
        {
            switch (type.etype)
            {
                case ElementType.Array:
                case ElementType.ByRef:
                case ElementType.CModOpt:
                case ElementType.CModReqD:
                case ElementType.FnPtr:
                case ElementType.GenericInst:
                case ElementType.MVar:
                case ElementType.Pinned:
                case ElementType.Ptr:
                case ElementType.SzArray:
                case ElementType.Sentinel:
                case ElementType.Var:
                    return true;
            }

            return false;
        }

        public static TypeDefinition CheckedResolve(this TypeReference self)
        {
            TypeDefinition type = self.Resolve();
            if (type == null)
                throw new ResolutionException(self);

            return type;
        }
    }
}