// 
// Copyright (c) Microsoft Corporation.   All rights reserved.
//

using System;
using System.Text;

///////////////////////////////////////////////////////////////////////
//
// The Bartok.msil.Signature* classes represent the signatures read
// from the Blob heap of the input file(s).
// 
// Since the signatures have references to MetaData* objects, the
// signatures are initially just an array of bytes.  When all the
// MetaData objects have been created, the array of bytes is processed
// and the references resolved.
//
//////////////////////////////////////////////////////////////////////  

namespace Bartok.MSIL
{

  public abstract class Signature {

      // Constructor Methods

      internal virtual Signature resolve(MetaDataLoader mdLoader) {
          return this;
      }

      internal virtual Signature resolveTypeSpec(MetaDataLoader mdLoader) {
          return this;
      }

      // Internal Classes

      public enum CallingConventions: byte {
          Default                = 0x00,
              Unmanaged_cdecl    = 0x01,
              Unmanaged_sdtcall  = 0x02,
              Unmanaged_thiscall = 0x03,
              Unmanaged_fastcall = 0x04,
              VarArg             = 0x05,
              Field              = 0x06,
              LocalVar           = 0x07,
              Property           = 0x08,
              Unmanaged          = 0x09,
              Mask               = 0x0f,
              Generic            = 0x10,
              HasThis            = 0x20,
              ExplicitThis       = 0x40
      }

      // REVIEW: Consider memoizing these types.

      public class Type {

          // Constructor Methods

          // elementType \in
          // { VOID, BOOLEAN, CHAR, I1, U1, I2, U2, I4, U4, I8, U8, R4, R8,
          //   U, I, OBJECT, STRING, TYPEDBYREF }
          internal Type(ElementTypes elementType) {
              this.elementType = elementType;
          }

          // elementType \in {VALUETYPE, CLASS}
          internal Type(ElementTypes elementType,
                        MetaDataObject classObject) {
              this.elementType = elementType;
              this.classObject = classObject;
          }

          // elementType \in {SZARRAY, CMOD_OPT, CMOD_REQD, PINNED, PTR, BYREF}
          internal Type(ElementTypes elementType,
                        Type typeObject,
                        Modifier modifierList) {
              this.elementType = elementType;
              this.typeObject = typeObject;
              this.modifierList = modifierList;
          }
          
          // elementType is ARRAY
          internal Type(ElementTypes elementType,
                        Type typeObject,
                        uint[] lowerBounds,
                        uint[] upperBounds) {
              this.elementType = elementType;
              this.typeObject = typeObject;
              this.lowerBounds = lowerBounds;
              this.upperBounds = upperBounds;
          }

          // elementType is FNPTR
          internal Type(ElementTypes elementType,
                        SignatureMethod methodSignature) {
              this.elementType = elementType;
              this.methodSignature = methodSignature;
          }

          // elementType \in {VAR, MVAR}
          internal Type(ElementTypes elementType,
                        uint number) {
              this.elementType = elementType;
              this.number = number;
          }

          // elementType is GENERICINST
          internal Type(ElementTypes elementType,
                        Type typeObject,
                        Type[] typeList) {
              this.elementType = elementType;
              this.typeObject = typeObject;
              this.typeList = typeList;
          }

          // Accessors

          public ElementTypes ElementType {
              get { return elementType; }
          }

          public MetaDataObject ClassObject {
              // REVIEW: throw on inconsistent elementtype?
              get { return classObject; }
          }

          public Type TypeObject {
              // REVIEW: throw on inconsistent elementtype?
              get { return typeObject; }
          }

          public Type[] TypeList {
              // REVIEW: throw on inconsistent elementtype?
              get { return typeList; }
          }

          public Modifier ModifierList {
              // REVIEW: throw on inconsistent elementtype?
              get { return modifierList; }
          }

          public uint[] UpperBounds {
              // REVIEW: throw on inconsistent elementtype?
              get { return upperBounds; }
          }

          public uint[] LowerBounds {
              // REVIEW: throw on inconsistent elementtype?
              get { return lowerBounds; }
          }
          
          public int Rank {
              get { return upperBounds.Length; }
          }

          public SignatureMethod MethodSignature {
              // REVIEW: throw on inconsistent elementtype?
              get { return methodSignature; }
          }

          public uint Number {
              // REVIEW: throw on inconsistent elementtype?
              get { return number; }
          }

#region SyncBlockHack
          // Equality methods

          public override bool Equals(Object o) {
              return this == o;
          }

          public override int GetHashCode() {
              if (hash == 0) {
                  if (nextHash == 0) {
                      nextHash++;
                  }
                  hash = nextHash++;
              }
              return hash;
          }
#endregion
              
          // Debug Methods

          public override string ToString() {
              StringBuilder sb = new StringBuilder("Type(0x");
              // BUGBUG: use symbolic printing when selfhost can do so
              sb.Append(((uint) elementType).ToString("x"));
              sb.Append(",");
              for (Modifier modifier = this.modifierList;
                   modifier != null;
                   modifier = modifier.next) {
                  sb.Append(modifier);
                  sb.Append(",");
              }
              if (this.typeObject != null) {
                  sb.Append(this.typeObject);
                  sb.Append(",");
              }
              if (this.typeList != null) {
                  sb.Append("typelist[");
                  sb.Append(this.typeList[0]);
                  for (int i = 1; i < this.typeList.Length; i++) {
                      sb.Append(",");
                      sb.Append(this.typeList[i]);
                  }
                  sb.Append("],");
              }
              if (this.classObject != null) {
                  sb.Append(this.classObject);
                  sb.Append(",");
              }
              if (this.lowerBounds != null) {
                  sb.Append("lowerbounds[");
                  sb.Append(this.lowerBounds[0]);
                  for (int i = 1; i < this.lowerBounds.Length; i++) {
                      sb.Append(",");
                      sb.Append(this.lowerBounds[i]);
                  }
                  sb.Append("],");
              }
              if (this.upperBounds != null) {
                  sb.Append("upperbounds[");
                  sb.Append(this.upperBounds[0]);
                  for (int i = 1; i < this.upperBounds.Length; i++) {
                      sb.Append(",");
                      sb.Append(this.upperBounds[i]);
                  }
                  sb.Append("],");
              }
              if (this.methodSignature != null) {
                  sb.Append(methodSignature);
                  sb.Append(",");
              }
              if (this.elementType == ElementTypes.VAR
                  || this.elementType == ElementTypes.MVAR) {
                  sb.Append(number);
                  sb.Append(",");
              }
              sb.Remove(sb.Length-1, 1);
              sb.Append(")");
              return sb.ToString();
          }

          // State

          private readonly ElementTypes elementType;
          private readonly MetaDataObject classObject;
          private readonly Type typeObject;
          private readonly Type[] typeList;
          private readonly Modifier modifierList;
          private readonly uint[] lowerBounds;
          private readonly uint[] upperBounds;
          private readonly SignatureMethod methodSignature;
          private readonly uint number;

#region SyncBlockHack
          private int hash;
          private static int nextHash;
#endregion
      }

      public class Param {

          // Constructor Methods

          internal Param(Modifier modifierList, ElementTypes kind, Type type) {
              this.modifierList = modifierList;
              this.kind = kind;
              this.type = type;
          }

          // Access Methods

          public Modifier ModifierList {
              get {
                  return this.modifierList;
              }
          }

          // One of ElementTypes.{END, BYREF, TYPEDBYREF},
          // ElementTypes.END means no flag present
          public ElementTypes Kind {
              get {
                  return this.kind;
              }
          }

          public Type Type {
              get {
                  return this.type;
              }
          }

          // Debug Methods

          public override string ToString() {
              StringBuilder sb = new StringBuilder("Parameter(");
              for (Modifier modifier = this.modifierList;
                   modifier != null;
                   modifier = modifier.next) {
                  sb.Append(modifier);
                  sb.Append(",");
              }
              sb.Append(((uint) kind).ToString("x"));
              sb.Append(",");
              sb.Append(type);
              sb.Append(")");
              return sb.ToString();
          }

          // State

          private readonly Modifier modifierList;
          private readonly ElementTypes kind;
          private readonly Type type;

      }

      public class Modifier {

          // Constructor Methods

          internal Modifier(ElementTypes kind,
                            MetaDataObject type,
                            Modifier next) {
              this.kind = kind;
              this.type = type;
              this.next = next;
          }

          // Accessor
          public ElementTypes Kind {
              get { return kind; }
          }

          public MetaDataObject Type {
              get { return type; }
          }

          public Modifier Next {
              get { return next; }
          }

          // Debug Methods

          public override string ToString() {
              return (((uint) kind).ToString("x")+" "+type);
          }

          // State

          private  readonly ElementTypes   kind;
          private  readonly MetaDataObject type;
          internal readonly Modifier       next;

      }

  }

  internal class SignatureBuffer: Signature {

      // Constructor Methods

      internal SignatureBuffer(byte[] buffer) {
          this.buffer = buffer;
      }

      internal override Signature resolve(MetaDataLoader mdLoader) {
          uint unmaskedCallingConvention =
              uncompressInt(this.buffer, ref this.offset);
          CallingConventions callingConvention = (CallingConventions)
              (unmaskedCallingConvention & (uint) CallingConventions.Mask);
          switch (callingConvention) {
            case CallingConventions.Field: {
                // Section 22.2.4 of ECMA spec, partition II
                Type type = this.parseSignatureType(mdLoader);
                if (this.offset != this.buffer.Length) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("Only read "+this.offset+" bytes of "+this);
                }
                return new SignatureField(type);
            }
            case CallingConventions.LocalVar: {
                // Section 22.2.6 of ECMA spec, partition II
                uint count = uncompressInt(this.buffer, ref this.offset);
                Type[] locals = new Type[count];
                for (int i = 0; i < count; i++) {
                    locals[i] = parseSignatureLocal(mdLoader);
                }
                if (this.offset != this.buffer.Length) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("Only read "+this.offset+" bytes of "+this);
                }
                return new SignatureLocalVar(locals);
            }
            case CallingConventions.Property: {
                // Section 22.2.5 of ECMA spec, partition II
                uint paramCount = uncompressInt(this.buffer, ref this.offset);
                Type returnType = this.parseSignatureType(mdLoader);
                Param[] parameters = new Param[paramCount];
                for (int i = 0; i < paramCount; i++) {
                    parameters[i] = this.parseSignatureParam(mdLoader);
                }
                if (this.offset != this.buffer.Length) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("Only read "+this.offset+" bytes of "+this);
                }
                return new SignatureProperty(returnType, parameters);
            }
            default: {
                // Section2 22.2.1, 22.2.2, and 22.2.3 of ECMA spec, II
                uint genericParamCount = 0;
                if((unmaskedCallingConvention
                    & (uint) CallingConventions.Generic) != 0) {
                    genericParamCount = uncompressInt(this.buffer,
                                                      ref this.offset);
                }
                uint paramCount = uncompressInt(this.buffer, ref this.offset);
                Type returnType = this.parseSignatureType(mdLoader);
                int sentinelLocation = -1;
                Param[] parameters = new Param[paramCount];
                for (int i = 0; i < paramCount; i++) {
                    byte first = this.buffer[this.offset];
                    if (first == (byte) ElementTypes.SENTINEL) {
                        sentinelLocation = i;
                        this.offset++;
                    }
                    parameters[i] = this.parseSignatureParam(mdLoader);
                }
                if (this.offset != this.buffer.Length) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("Only read "+this.offset+" bytes of "+this);
                }
                // pass unmasked convention because user of signature (e.g. memberRef)
                // may need to know the static vs. instance status of the method
                // Erik, 4/13/01
                return new SignatureMethod((CallingConventions)
                                           unmaskedCallingConvention,
                                           genericParamCount,
                                           returnType, parameters,
                                           sentinelLocation);
            }
          }

      }

      internal override Signature resolveTypeSpec(MetaDataLoader mdLoader) {
          Type type = this.parseSignatureType(mdLoader);
          if (this.offset != this.buffer.Length) {
              throw new MetaDataLoader.IllegalMetaDataFormatException("Only read "+this.offset+" bytes of "+this);
          }
          return new SignatureTypeSpec(type);
      }

      internal static uint uncompressInt(byte[] buffer, ref int offset) {
          byte first = buffer[offset];
          if ((first & 0x80) == 0x00) {
              // 0??? ????: one byte encoding
              offset++;
              return first;
          }
          else if ((first & 0xC0) == 0x80) {
              // 10?? ????: two byte encoding
              byte second = buffer[offset+1];
              offset += 2;
              return (((first & 0x3fU) << 8) | second);
          }
          else {
              // 11?? ????: four byte encoding
              byte second = buffer[offset+1];
              byte third = buffer[offset+2];
              byte fourth = buffer[offset+3];
              offset += 4;
              return (((uint) ((first & 0x3fU) << 24)) |
                      ((uint) (second << 16)) |
                      ((uint) (third << 8)) |
                      ((uint) fourth));
          }
      }

      private MetaDataObject uncompressToken(MetaDataLoader mdLoader) {
          uint codedToken = uncompressInt(this.buffer, ref this.offset);
          MetaDataLoader.TokenType tokenType =
              ((((codedToken & 0x2) == 0) ?
                (((codedToken & 0x1) == 0) ?
                 MetaDataLoader.TokenType.TypeDef :
                 MetaDataLoader.TokenType.TypeRef) :
                (((codedToken & 0x1) == 0) ?
                 MetaDataLoader.TokenType.TypeSpec :
                 MetaDataLoader.TokenType.BaseType)));
          int token = (int) (codedToken >> 2) | (int) tokenType;
          return mdLoader.getObjectFromToken(token);
      }

      private Type parseSignatureType(MetaDataLoader mdLoader) {
          ElementTypes elementType = (ElementTypes) this.buffer[this.offset];
          this.offset++;
          switch (elementType) {
            case ElementTypes.VOID:
            case ElementTypes.BOOLEAN:
            case ElementTypes.CHAR:
            case ElementTypes.I1:
            case ElementTypes.U1:
            case ElementTypes.I2:
            case ElementTypes.U2:
            case ElementTypes.I4:
            case ElementTypes.U4:
            case ElementTypes.I8:
            case ElementTypes.U8:
            case ElementTypes.R4:
            case ElementTypes.R8:
            case ElementTypes.U:
            case ElementTypes.I:
            case ElementTypes.OBJECT:
            case ElementTypes.STRING:
            case ElementTypes.TYPEDBYREF: {
                return new Type(elementType);
            }
            case ElementTypes.VALUETYPE:
            case ElementTypes.CLASS: {
                // Followed by: TypeDefOrRefEncoded
                MetaDataObject classObject = uncompressToken(mdLoader);
                return new Type(elementType, classObject);
            }
            case ElementTypes.SZARRAY: {
                // Followed by: CustomMod* Type
                Modifier modifierList = this.parseSignatureModifiers(mdLoader);
                Type type = this.parseSignatureType(mdLoader);
                return new Type(elementType, type, modifierList);
            }
            case ElementTypes.ARRAY: {
                // Followed by: Type ArrayShape
                Type type = this.parseSignatureType(mdLoader);
                uint rank = uncompressInt(this.buffer, ref this.offset);
                if (rank == 0) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("ARRAY with rank 0");
                }
                uint[] upperBounds = new uint[rank];
                uint[] lowerBounds = new uint[rank];
                uint numUpperBounds =
                    uncompressInt(this.buffer, ref this.offset);
                if (numUpperBounds > rank) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("ARRAY with upper bounds > rank");
                }
                for (int i = 0; i < numUpperBounds; i++) {
                    upperBounds[i] =
                        uncompressInt(this.buffer, ref this.offset);
                }
                uint numLowerBounds =
                    uncompressInt(this.buffer, ref this.offset);
                if (numLowerBounds > rank) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("ARRAY with lower bounds > rank");
                }
                for (int i = 0; i < numLowerBounds; i++) {
                    lowerBounds[i] =
                        uncompressInt(this.buffer, ref this.offset);
                }
                return new Type(elementType, type, lowerBounds, upperBounds);
            }
            case ElementTypes.FNPTR: {
                // Followed by: MethodDefSig or MethodRefSig
                uint unmaskedCallingConvention =
                    uncompressInt(this.buffer, ref this.offset);
                CallingConventions callingConvention = (CallingConventions)
                    (unmaskedCallingConvention
                     & (uint) CallingConventions.Mask);
                if (callingConvention == CallingConventions.Field ||
                    callingConvention == CallingConventions.LocalVar ||
                    callingConvention == CallingConventions.Property) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("FNPTR surprised by calling convention "+callingConvention);
                }
                if ((unmaskedCallingConvention
                     & (uint) CallingConventions.Generic) != 0) {
                    throw new MetaDataLoader.IllegalMetaDataFormatException("FNPTR generic unimplemented - check to see if legal");
                }
                uint paramCount = uncompressInt(this.buffer, ref this.offset);
                Type returnType = this.parseSignatureType(mdLoader);
                int sentinelLocation = -1;
                Param[] parameters = new Param[paramCount];
                for (int i = 0; i < paramCount; i++) {
                    byte first = this.buffer[this.offset];
                    if (first == (byte) ElementTypes.SENTINEL) {
                        sentinelLocation = i;
                        this.offset++;
                    }
                    parameters[i] = this.parseSignatureParam(mdLoader);
                }
                SignatureMethod signature =
                    new SignatureMethod(callingConvention, 0, returnType,
                                        parameters, sentinelLocation);
                return new Type(elementType, signature);
            }
            case ElementTypes.VAR:
            case ElementTypes.MVAR: {
                // Generic type variables
                uint number = uncompressInt(this.buffer, ref this.offset);
                return new Type(elementType, number);
            }
            case ElementTypes.GENERICINST: {
                // Generic type instantiation
                Type type = this.parseSignatureType(mdLoader);
                uint typeCount = uncompressInt(this.buffer, ref this.offset);
                Type[] typeParams = new Type[typeCount];
                for (int i = 0; i < typeCount; i++) {
                    Type paramType = this.parseSignatureType(mdLoader);
                    typeParams[i] = paramType;
                }

                return new Type(elementType, type, typeParams);
            }
            case ElementTypes.CMOD_OPT:
            case ElementTypes.CMOD_REQD: {
                // Modifiers
                this.offset--;
                Modifier modifierList = this.parseSignatureModifiers(mdLoader);
                Type type = this.parseSignatureType(mdLoader);
                return new Type(elementType, type, modifierList);
            }
            case ElementTypes.PINNED:
            case ElementTypes.PTR:
            case ElementTypes.BYREF: {
                // Modifiers
                Type type = this.parseSignatureType(mdLoader);
                return new Type(elementType, type, (Modifier) null);
            }
            default: {
                throw new MetaDataLoader.IllegalMetaDataFormatException("Unknown signature type: 0x"+((byte) elementType).ToString("x2")+" at "+this.offset+" in "+this);
            }
          }
      }

      private Modifier parseSignatureModifiers(MetaDataLoader mdLoader) {
          Modifier result = null;
          ElementTypes mod = (ElementTypes) this.buffer[this.offset];
          while (mod == ElementTypes.CMOD_REQD ||
                 mod == ElementTypes.CMOD_OPT) {
              this.offset++;
              uint typeEncoded = uncompressInt(this.buffer, ref this.offset);
              // BUGBUG: type conversion on int.
              MetaDataObject type = mdLoader.getTypeDefOrRef((int) typeEncoded);
              result = new Modifier(mod, type, result);
              if (this.offset < this.buffer.Length) {
                mod = (ElementTypes) this.buffer[this.offset];
              }
              else {
                mod = ElementTypes.VOID;
              }
          }
          return result;
      }

      private Param parseSignatureParam(MetaDataLoader mdLoader) {
          Modifier modifierList = this.parseSignatureModifiers(mdLoader);
          byte first = this.buffer[this.offset];
          if (first == (byte) ElementTypes.BYREF) {
              this.offset++;
              Type type = this.parseSignatureType(mdLoader);
              return new Param(modifierList, (ElementTypes) first, type);
          }
          else if (first == (byte) ElementTypes.TYPEDBYREF) {
              this.offset++;
              return new Param(modifierList, (ElementTypes) first, null);
          }
          else {
              Type type = this.parseSignatureType(mdLoader);
              return new Param(modifierList, ElementTypes.END, type);
          }
      }

      private Type parseSignatureLocal(MetaDataLoader mdLoader) {
          ElementTypes first = (ElementTypes) this.buffer[this.offset];
          if (first == ElementTypes.PINNED) {
              this.offset++;
              Type type = this.parseSignatureType(mdLoader);
              return new Type(first, type, (Modifier) null);
          }
          else {
              return this.parseSignatureType(mdLoader);
          }
      }

      // Debug Methods

      public override string ToString() {
          StringBuilder sb = new StringBuilder("SignatureBuffer(");
          int bufferSize = this.buffer.Length;
          if (bufferSize > 0) {
              sb.Append("0x");
              sb.Append(this.buffer[0].ToString("x2"));
              for (int i = 1; i < bufferSize; i++) {
                  sb.Append(",");
                  sb.Append("0x");
                  sb.Append(this.buffer[i].ToString("x2"));
              }
          }
          sb.Append(")");
          return sb.ToString();
      }

      // State

      private readonly byte[] buffer;
      private int offset;

  }

}
