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

namespace Bartok.MSIL
{

  using System;

  public abstract class MarshalSpec {

      // Constructor Methods

      internal static MarshalSpec Create(byte[] buffer) {
          int offset = 0;
          NativeTypes kind = (NativeTypes)
              SignatureBuffer.uncompressInt(buffer, ref offset);
          switch (kind) {
            case NativeTypes.BOOLEAN:
            case NativeTypes.I1:
            case NativeTypes.U1:
            case NativeTypes.I2:
            case NativeTypes.U2:
            case NativeTypes.I4:
            case NativeTypes.U4:
            case NativeTypes.I8:
            case NativeTypes.U8:
            case NativeTypes.R4:
            case NativeTypes.R8:
            case NativeTypes.CURRENCY:
            case NativeTypes.BSTR:
            case NativeTypes.LPSTR:
            case NativeTypes.LPWSTR:
            case NativeTypes.LPTSTR:
            case NativeTypes.IUNKNOWN:
            case NativeTypes.IDISPATCH:
            case NativeTypes.STRUCT:
            case NativeTypes.INTF:
            case NativeTypes.INT:
            case NativeTypes.UINT:
            case NativeTypes.BYVALSTR:
            case NativeTypes.ANSIBSTR:
            case NativeTypes.TBSTR:
            case NativeTypes.VARIANTBOOL:
            case NativeTypes.FUNC:
            case NativeTypes.ASANY:
            case NativeTypes.LPSTRUCT:
            case NativeTypes.ERROR:
            case NativeTypes.MAX: {
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecNative(kind);
            }
            case NativeTypes.SAFEARRAY: {
                VariantTypes elemType;
                if (offset < buffer.Length) {
                    elemType = (VariantTypes)
                        SignatureBuffer.uncompressInt(buffer, ref offset);
                }
                else {
                    elemType = VariantTypes.VT_EMPTY;
                }
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecSafeArray(elemType);
            }
            case NativeTypes.FIXEDSYSSTRING: {
                uint elemCount =
                    SignatureBuffer.uncompressInt(buffer, ref offset);
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecFixedString(elemCount);
            }
            case NativeTypes.FIXEDARRAY: {
                uint elemCount =
                    SignatureBuffer.uncompressInt(buffer, ref offset);
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecFixedArray(elemCount);
            }
            case NativeTypes.CUSTOMMARSHALER: {
                String guid = uncompressString(buffer, ref offset);
                String unmanagedType = uncompressString(buffer, ref offset);
                String managedType = uncompressString(buffer, ref offset);
                String cookie = uncompressString(buffer, ref offset);
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecCustom(guid, unmanagedType,
                                             managedType, cookie);
            }
            case NativeTypes.ARRAY: {
                NativeTypes elemType = (NativeTypes)
                    SignatureBuffer.uncompressInt(buffer, ref offset);
                uint paramNumber =
                    ((offset < buffer.Length) ?
                     SignatureBuffer.uncompressInt(buffer, ref offset) :
                     0); 
                uint extras =
                    ((offset < buffer.Length) ?
                     SignatureBuffer.uncompressInt(buffer, ref offset) :
                     0);
                if (offset != buffer.Length) {
                    Console.WriteLine("WARNING: extra bytes in "+new MetaDataBlob(buffer));
                }
                return new MarshalSpecArray(elemType, paramNumber, extras);
            }
            default: {
                throw new Exception("Unknown marshal spec kind "+kind);
            }
          }
      }

      private static String uncompressString(byte[] buffer, ref int offset) {
          uint length = SignatureBuffer.uncompressInt(buffer, ref offset);
          int oldOffset = offset;
          offset += (int) length;
          return MetaDataLoader.stringEncoding.GetString(buffer, oldOffset,
                                                         (int) length);
      }

      public override abstract String ToString();

  }

  public class MarshalSpecNative: MarshalSpec {

      // Constructor Methods

      public MarshalSpecNative(NativeTypes kind) {
          this.kind = kind;
      }

      // Accessors

      public NativeTypes Kind {
          get { return this.kind; }
      }

      public override String ToString() {
          return "MarshalSpecNative("+kind+")";
      }

      // Private State

      private readonly NativeTypes kind;

  }

  public class MarshalSpecSafeArray: MarshalSpec {

      // Constructor Methods

      public MarshalSpecSafeArray(VariantTypes elemType) {
          this.elemType = elemType;
      }

      // Accessors

      public VariantTypes ElementType {
          get { return this.elemType; }
      }

      public override String ToString() {
          return "MarshalSpecSafeArray("+elemType+")";
      }

      // Private State

      private readonly VariantTypes elemType;

  }

  public class MarshalSpecFixedString: MarshalSpec {

      // Constructor Methods

      public MarshalSpecFixedString(uint elemCount) {
          this.elemCount = elemCount;
      }

      // Accessors

      public uint ElementCount {
          get { return this.elemCount; }
      }

      public override String ToString() {
          return "MarshalSpecFixedString("+elemCount+")";
      }

      // Private State

      private readonly uint elemCount;

  }

  public class MarshalSpecFixedArray: MarshalSpec {

      // Constructor Methods

      public MarshalSpecFixedArray(uint elemCount) {
          this.elemCount = elemCount;
      }

      // Accessors

      public uint ElementCount {
          get { return this.elemCount; }
      }

      public override String ToString() {
          return "MarshalSpecFixedArray("+elemCount+")";
      }

      // Private State

      private readonly uint elemCount;

  }

  public class MarshalSpecCustom: MarshalSpec {

      // Constructor Methods

      public MarshalSpecCustom(String guid, String unmanagedType,
                               String managedType, String cookie) {
          this.guid = guid;
          this.unmanagedType = unmanagedType;
          this.managedType = managedType;
          this.cookie = cookie;
      }

      // Accessors

      public String Guid {
          get { return this.guid; }
      }

      public String UnmanagedType {
          get { return this.unmanagedType; }
      }

      public String ManagedType {
          get { return this.managedType; }
      }

      public String Cookie {
          get { return this.cookie; }
      }

      public override String ToString() {
          return ("MarshalSpecCustom("+guid+","+
                  managedType+"->"+unmanagedType+","+cookie+")");
      }

      // Private State

      private readonly String guid;
      private readonly String unmanagedType;
      private readonly String managedType;
      private readonly String cookie;

  }

  public class MarshalSpecArray: MarshalSpec {

      // Constructor Methods

      public MarshalSpecArray(NativeTypes elemType, uint paramNumber,
                              uint extras) {
          this.elemType = elemType;
          this.paramNumber = paramNumber;
          this.extras = extras;
      }

      // Accessors

      public NativeTypes ElementType {
          get { return this.elemType; }
      }

      public uint ParameterNumber {
          get { return this.paramNumber; }
      }

      public uint ExtraCount {
          get { return this.extras; }
      }

      public override String ToString() {
          return "MarshalSpecArray("+elemType+","+paramNumber+","+extras+")";
      }

      // Private State

      private readonly NativeTypes elemType;
      private readonly uint paramNumber;
      private readonly uint extras;

  }

}
