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

using System;
using System.Collections;
using System.IO;
using Bartok.DebugInfo;

namespace Bartok.MSIL
{

  public class MetaDataMethod: MetaDataObject {

      // Constructor Methods

      internal MetaDataMethod(int rva, short implFlags, short flags,
                              string name, Signature signature,
                              int paramIndex) {
          this.rva = rva;
          this.implFlags = implFlags;
          this.flags = flags;
          this.name = name;
          this.signature = signature;
          this.paramIndex = paramIndex;
      }

      // These are technically not constructor methods, but they are meant to
      // be used to set up the object

      internal void AddGenericParam(MetaDataGenericParam genericParam) {
          if (this.genericParamList == null) {
              this.genericParamList = new ArrayList(2);
          }

          if (genericParam.Number != this.genericParamList.Count) {
              throw new MetaDataLoader.IllegalMetaDataFormatException
                  ("Generic parameters out of order - is this allowed?");
          }

          this.genericParamList.Add(genericParam);
      }

      internal void registerReferences(int paramCount,
                                       MetaDataMethod[] paramOwners) {
          if (this.paramIndex <= paramCount) {
              paramOwners[this.paramIndex] = this;
          }
      }

      internal void resolveReferences(MetaDataLoader loader,
                                      MetaDataMethod[] paramOwners) {
          this.signature = signature.resolve(loader);
          // paramOwners[paramOwners.Length-1] == null, so this is safe
          int paramEndIndex = this.paramIndex;
          while (paramOwners[paramEndIndex] == this) {
              paramEndIndex++;
          }
          int paramCount = paramEndIndex - this.paramIndex;
          this.paramArray = loader.getParams(this.paramIndex, paramCount);
          int limit = this.paramArray.Length;
          if (paramCount != limit) {
              MetaDataParam resultParam = loader.getParam(this.paramIndex);
              resultParam.resolveReferences(this);
          }
          for (int i = 0; i < limit; i++) {
              this.paramArray[i].resolveReferences(this);
          }
      }

      internal void loadInstructions(MetaDataLoader mdLoader,
                                     PELoader peLoader,
                                     Stream fileStream,
                                     int[] lines,
                                     int[] columns,
                                     int[] offsets,
                                     String srcFileName,
                                     int count) {
          if (this.rva != 0) {
              if ((this.flags & (short) MethodAttributes.PinvokeImpl) == 0 ||
                  (this.flags & (short) MethodAttributes.UnmanagedExport) != 0) {
                  int codeOffset = peLoader.VaToOffset(rva);
                  this.instructions =
                      Instruction.getInstructions(mdLoader, fileStream,
                                                  codeOffset, lines, columns,
                                                  offsets, srcFileName, count,
                                                  out this.ehTable,
                                                  out this.bbTable,
                                                  out this.maxStack,
                                                  out this.locals,
                                                  out this.initLocals);
              }
              else {
                  Console.WriteLine("Not loading embedded native code for "+
                                    this);
              }
          }
      }

      internal void setParent(MetaDataTypeDefinition parent) {
          this.parent = parent;
      }

      // Access Methods

      // of MetaDataGenericParam
      public ArrayList GenericParamList {
          get {
              return this.genericParamList;
          }
      }

      public Instruction[] Instructions {
          get {
              return this.instructions;
          }
      }

      public EHClause[] EHTable {
          get {
              return this.ehTable;
          }
      }

      // An array of basic block start indices in "Instructions"
      public int[] BBTable {
          get {
              return this.bbTable;
          }
      }

      public int MaxStack {
          get {
              return this.maxStack;
          }
      }

      public Signature.Type[] Locals {
          get {
              return this.locals;
          }
      }

      // Bitmask described by MethodImplAttributes
      public short ImplFlags {
          get {
              return this.implFlags;
          }
      }

      // Bitmask described by MethodAttributes
      public short Flags {
          get {
              return this.flags;
          }
      }

      public string FullName {
          get {
              if (this.parent != null) {
                  return this.parent.FullName + "." + this.Name;
              }
              else {
                  return this.Name;
              }
          }
      }


      public string Name {
          get {
              return this.name;
          }
      }

      public SignatureMethod Signature {
          get {
              return (SignatureMethod) this.signature;
          }
      }

      public MetaDataParam[] Parameters {
          get {
              return this.paramArray;
          }
      }

      public MetaDataTypeDefinition Parent {
          get {
              return this.parent;
          }
      }

      public int Rva {
          get { return rva; }
      }

      public bool IsEmpty {
          get { return isEmpty; }
          set { isEmpty = value; }
      }

      public bool InitLocals {
          get { return initLocals; }
      }

      public DebugLineNumber BaseLineNumber {
          get { return baseLineNumber; }
          set { baseLineNumber = value; }
      }

      public DebugLineNumber LastLineNumber {
          get { return lastLineNumber; }
          set { lastLineNumber = value; }
      }

      public int NumOfLines {
          get { return numOfLines; }
          set { numOfLines = value; }
      }

      public String SrcFileName {
          get { return srcFileName; }
          set { srcFileName = value; }
      }

      public bool HasDebugInfo {
          get { return hasDebugInfo; }
          set { hasDebugInfo = value; }
      }

      public String[] LocalVarNames {
          get { return localVarNames; }
          set { localVarNames = value; }
      }

      // Debug Methods

      public override string ToString() {
          return ("MetaDataMethod("+this.FullName+")");
      }

      public override string ToStringLong() {
          System.Text.StringBuilder sb =
              new System.Text.StringBuilder("MetaDataMethod(");
          if (this.genericParamList != null
              && this.genericParamList.Count > 0) {
              sb.Append("GenericParams<");
              foreach (MetaDataGenericParam param in this.genericParamList) {
                  sb.Append(param.ToString());
                  sb.Append(",");
              }
              sb.Remove(sb.Length-1, 1);
              sb.Append(">,");
          }
          sb.Append(this.rva);
          sb.Append(",");
          sb.Append(this.implFlags.ToString("x"));
          sb.Append(",");
          sb.Append(this.flags.ToString("x"));
          sb.Append(",");
          if (this.parent != null) {
              sb.Append(this.parent.FullName);
              sb.Append(".");
          }
          sb.Append(this.name);
          sb.Append(",");
          sb.Append(this.signature);
          sb.Append(",");
          if (this.paramArray == null) {
              sb.Append(this.paramIndex);
          }
          else if (this.paramArray.Length == 0) {
              sb.Append("No parameters");
          }
          else {
              sb.Append("parameters(");
              foreach (MetaDataParam param in this.paramArray) {
                  sb.Append(param.ToString());
                  sb.Append(",");
              }
              sb.Remove(sb.Length-1, 1);
              sb.Append(")");
          }
          sb.Append(")");
          return sb.ToString();
      }

      // State

      private readonly int                    rva;
      private          ArrayList              genericParamList;
      private          Instruction[]          instructions;
      private          EHClause[]             ehTable;
      private          int[]                  bbTable;
      private          int                    maxStack;
      private          Signature.Type[]       locals;
      private readonly short                  implFlags;
      private readonly short                  flags;
      private readonly string                 name;
      private          Signature              signature;
      private readonly int                    paramIndex;
      private          MetaDataParam[]        paramArray;
      private          MetaDataTypeDefinition parent;
      private          bool                   isEmpty;
      private          bool                   initLocals;

      // information for debugging
      private DebugLineNumber  baseLineNumber;
      private DebugLineNumber lastLineNumber;
      private int numOfLines;
      private String srcFileName;
      private bool hasDebugInfo;
      private String[] localVarNames;

      // Internal Classes

      // Section 22.1.7 of ECMA spec, Section II
      public enum MethodImplAttributes
      {
          // code impl mask
          CodeTypeMask         = 0x0003, // Flags about code type.
              IL               = 0x0000, // Method impl is IL.
              Native           = 0x0001, // Method impl is native.
              OPTIL            = 0x0002, // Method impl is OPTIL
              Runtime          = 0x0003, // Method impl is provided by the runtime.
              // managed mask
              ManagedMask      = 0x0004, // Flags specifying whether the code is managed or unmanaged.
              Unmanaged        = 0x0004, // Method impl is unmanaged, otherwise managed.
              Managed          = 0x0000, // Method impl is managed.
              // implementation info and interop
              ForwardRef       = 0x0010, // Indicates method is defined; used primarily in merge scenarios.
              PreserveSig      = 0x0080, // Indicates method sig is not to be mangled to do HRESULT conversion.
              InternalCall     = 0x1000, // Reserved for internal use.
              Synchronized     = 0x0020, // Method is single threaded through the body.
              NoInlining       = 0x0008, // Method may not be inlined.
              MaxMethodImplVal = 0xffff // Range check value
      }

      public enum MethodAttributes {
          // member access attributes
          MemberAccessMask     = 0x0007, // Use this mask to retrieve accessibility information.
              PrivateScope     = 0x0000, // Member not referenceable.
              Private          = 0x0001, // Accessible only by the parent type.
              FamANDAssem      = 0x0002, // Accessible by sub-types only in this Assembly.
              Assem            = 0x0003, // Accessibly by anyone in the Assembly.
              Family           = 0x0004, // Accessible only by type and sub-types.
              FamORAssem       = 0x0005, // Accessibly by sub-types anywhere, plus anyone in assembly.
              Public           = 0x0006, // Accessibly by anyone who has visibility to this scope.
              // method contract attributes.
              Static           = 0x0010, // Defined on type, else per instance.
              Final            = 0x0020, // Method may not be overridden.
              Virtual          = 0x0040, // Method virtual.
              HideBySig        = 0x0080, // Method hides by name+sig, else just by name.
              // vtable layout mask - Use this mask to retrieve vtable attributes.
              VtableLayoutMask = 0x0100,
              ReuseSlot        = 0x0000, // The default.
              NewSlot          = 0x0100, // Method always gets a new slot in the vtable.
              // method implementation attributes.
              Abstract         = 0x0400, // Method does not provide an implementation.
              SpecialName      = 0x0800, // Method is special.  Name describes how.
              // interop attributes
              PinvokeImpl      = 0x2000, // Implementation is forwarded through pinvoke.
              UnmanagedExport  = 0x0008, // Managed method exported via thunk to unmanaged code.
              // Reserved flags for runtime use only.
              ReservedMask     = 0xd000,
              RTSpecialName    = 0x1000, // Runtime should check name encoding.
              HasSecurity      = 0x4000, // Method has security associate with it.
              RequireSecObject = 0x8000 // Method calls another method containing security code.
      }

  }

}
