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

using System;
using System.IO;

namespace Bartok.MSIL
{

  using System.Reflection;

  public class MetaDataFieldRVA: MetaDataObject {

      // Constructor Methods

      internal MetaDataFieldRVA(int rva, MetaDataField field) {
          this.rva = rva;
          this.field = field;
      }

      internal void resolveReferences(MetaDataLoader loader,
                                      PELoader peLoader,
                                      Stream fileStream) {
          this.field.resolveReferences(this);
          int dataOffset = peLoader.VaToOffsetSafe(this.rva);
          if (dataOffset != -1) {
              fileStream.Seek(dataOffset, SeekOrigin.Begin);
              BinaryReader reader = new BinaryReader(fileStream);
              SignatureField fieldSignature = (SignatureField)
                  this.field.Signature;
              Signature.Type fieldType = fieldSignature.FieldType;
              int dataSize = this.getFieldSize(fieldType);
              byte[] data = new byte[dataSize];
              int count = reader.Read(data, 0, dataSize);
              if (loader.HasVtableFixup() &&
                  loader.RVAHasRelocation(dataOffset) &&
                  dataSize == 4) {
                  // if this corresponds to a vtable label, we need to fix it
                  int location = data[0] | (data[1] << 8) |
                      (data[2] << 16) | (data[3] << 24);
                  int imageBase = peLoader.getImageBase();
                  location -= imageBase;  // use RVA
                  fileStream.Seek(location, SeekOrigin.Begin);
                  BinaryReader binaryReader = new BinaryReader(fileStream);
                  short prefix = binaryReader.ReadInt16();
                  if (prefix == 0x25FF) { // this starts a vtable lable
                      count  = binaryReader.Read(data, 0, 4);
                  }
              }
              this.dataBytes = data;

              if (count != dataSize) {
                  throw new MetaDataLoader.IllegalMetaDataFormatException("Only got "+count+" out of "+dataSize+" data bytes for FieldRVA "+this.rva);
              }
          }
          else {
              Console.WriteLine("Found no data for "+this.field);
          }
      }


      // Access Methods

      public byte[] DataBytes {
          get {
              return this.dataBytes;
          }
      }

      public MetaDataField Field {
          get {
              return this.field;
          }
      }

      public int RVA {
          get {
              return this.rva;
          }
      }

      // Helper Methods

      private int getFieldSize(Signature.Type fieldType) {
          switch (fieldType.ElementType) {
            case ElementTypes.VOID:
                return 0;
            case ElementTypes.BOOLEAN:
            case ElementTypes.I1:
            case ElementTypes.U1:
                return 1;
            case ElementTypes.CHAR:
            case ElementTypes.I2:
            case ElementTypes.U2:
                return 2;
            case ElementTypes.I4:
            case ElementTypes.U4:
            case ElementTypes.R4:
                return 4;
            case ElementTypes.I8:
            case ElementTypes.U8:
            case ElementTypes.R8:
                return 8;
            case ElementTypes.OBJECT:
            case ElementTypes.STRING:
            case ElementTypes.FNPTR:
            case ElementTypes.CLASS:
            case ElementTypes.PTR:
            case ElementTypes.BYREF:
            case ElementTypes.U:
            case ElementTypes.I:
                return machineIntSize;
            case ElementTypes.TYPEDBYREF:
                return 2*machineIntSize;
            case ElementTypes.VALUETYPE: {
                MetaDataObject classObject = fieldType.ClassObject;
                if (!(classObject is MetaDataTypeDefinition)) {
                    return -1;
                }
                MetaDataTypeDefinition typedef = (MetaDataTypeDefinition) classObject;
                if ((typedef.Flags & TypeAttributes.Interface) != 0 ||
                    (typedef.Flags & TypeAttributes.Abstract) != 0) {
                    return -1;
                }
                int classSize = 0;
                int packSize = 0;
                if (typedef.ClassLayout != null) {
                    classSize = typedef.ClassLayout.ClassSize;
                    packSize = typedef.ClassLayout.PackingSize;
                }
                int instanceFieldSize = 0;
                if ((typedef.Flags & TypeAttributes.ExplicitLayout) != 0) {
                    foreach (MetaDataField mdField in typedef.Fields) {
                        if ((mdField.Flags & (int) MetaDataField.FieldAttributes.Static) == 0) {
                            Signature.Type nestedFieldType =
                                ((SignatureField) mdField.Signature).FieldType;
                            int fieldSize = this.getFieldSize(nestedFieldType);
                            int offset = mdField.Layout.Offset;
                            int fieldEnd = fieldSize + offset;
                            if (fieldEnd > instanceFieldSize) {
                                instanceFieldSize = fieldEnd;
                            }
                        }
                    }
                }
                else {
                    foreach (MetaDataField mdField in typedef.Fields) {
                        if ((mdField.Flags & (int) MetaDataField.FieldAttributes.Static) == 0) {
                            Signature.Type nestedFieldType =
                                ((SignatureField) mdField.Signature).FieldType;
                            int fieldSize = this.getFieldSize(nestedFieldType);
                            if (fieldSize == -1) {
                                return -1;
                            }
                            if (packSize > 1) {
                                int delta = instanceFieldSize % packSize;
                                if (delta > 0) {
                                    instanceFieldSize += packSize - delta;
                                }
                            }
                            instanceFieldSize += fieldSize;
                        }
                    }
                }
                if (instanceFieldSize > classSize) {
                    return instanceFieldSize;
                }
                else if (classSize > 0) {
                    return classSize;
                }
                else {
                    return 1;
                }
            }
            case ElementTypes.ARRAY: {
                int elementCount = 1;
                uint[] lowerBounds = fieldType.LowerBounds;
                uint[] upperBounds = fieldType.UpperBounds;
                int rank = upperBounds.Length;
                for (int i = 0; i < rank; i++) {
                    int dimSize = (int) (upperBounds[i] - lowerBounds[i]);
                    if (dimSize == 0) {
                        // Must be an array of pointers to other arrays
                        return elementCount * machineIntSize;
                    }
                    else {
                        elementCount *= dimSize;
                    }
                }
                int elementSize = this.getFieldSize(fieldType.TypeObject);
                return elementCount * elementSize;
            }
            case ElementTypes.CMOD_REQD:
            case ElementTypes.CMOD_OPT:
                return this.getFieldSize(fieldType.TypeObject);
            case ElementTypes.PINNED:
                return this.getFieldSize(fieldType.TypeObject);
            case ElementTypes.SZARRAY:
                // Should be machine dependent!
                return 4;
            case ElementTypes.SENTINEL:
            case ElementTypes.END:
            default:
                return -1;
          }
      }

      // Debug Methods

      public override String ToString() {
          System.Text.StringBuilder sb =
              new System.Text.StringBuilder("MetaDataFieldRVA(");
          sb.Append(this.field);
          sb.Append(",");
          if (this.dataBytes == null) {
              sb.Append(this.rva);
          }
          else {
              sb.Append("[");
              int count = this.dataBytes.Length;
              if (count > 0) {
                  sb.Append(this.dataBytes[0].ToString("x2"));
                  for (int i = 1; i < count; i++) {
                      sb.Append(",");
                      sb.Append(this.dataBytes[i].ToString("x2"));
                  }
              }
              sb.Append("]");
          }
          sb.Append(")");
          return sb.ToString();
      }

      // State

      private readonly int           rva;
      private          byte[]        dataBytes;
      private readonly MetaDataField field;

      // BUGBUG: Should be machine dependent!
      private const int machineIntSize = 4;

  }

}
