//
// AggressiveRefletionReader.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace Mono.Cecil {

	using System;

	using Mono.Cecil.Metadata;
	using Mono.Cecil.Signatures;

	internal sealed class AggressiveReflectionReader : ReflectionReader {

		public AggressiveReflectionReader (ModuleDefinition module) : base (module)
		{
		}

		public override void VisitTypeDefinitionCollection (TypeDefinitionCollection types)
		{
			base.VisitTypeDefinitionCollection (types);

			ReadGenericParameterConstraints ();
			ReadClassLayoutInfos ();
			ReadFieldLayoutInfos ();
			ReadPInvokeInfos ();
			ReadProperties ();
			ReadEvents ();
			ReadSemantics ();
			ReadInterfaces ();
			ReadOverrides ();
			ReadSecurityDeclarations ();
			ReadCustomAttributes ();
			ReadConstants ();
			ReadExternTypes ();
			ReadMarshalSpecs ();
			ReadInitialValues ();

			m_events = null;
			m_properties = null;
			m_parameters = null;
		}

		void ReadGenericParameterConstraints ()
		{
			if (!m_tHeap.HasTable (GenericParamConstraintTable.RId))
				return;

			GenericParamConstraintTable gpcTable = m_tableReader.GetGenericParamConstraintTable ();
			for (int i = 0; i < gpcTable.Rows.Count; i++) {
				GenericParamConstraintRow gpcRow = gpcTable [i];
				GenericParameter gp = GetGenericParameterAt (gpcRow.Owner);

				gp.Constraints.Add (GetTypeDefOrRef (gpcRow.Constraint, new GenericContext (gp.Owner)));
			}
		}

		void ReadClassLayoutInfos ()
		{
			if (!m_tHeap.HasTable (ClassLayoutTable.RId))
				return;

			ClassLayoutTable clTable = m_tableReader.GetClassLayoutTable ();
			for (int i = 0; i < clTable.Rows.Count; i++) {
				ClassLayoutRow clRow = clTable [i];
				TypeDefinition type = GetTypeDefAt (clRow.Parent);
				type.PackingSize = clRow.PackingSize;
				type.ClassSize = clRow.ClassSize;
			}
		}

		void ReadFieldLayoutInfos ()
		{
			if (!m_tHeap.HasTable (FieldLayoutTable.RId))
				return;

			FieldLayoutTable flTable = m_tableReader.GetFieldLayoutTable ();
			for (int i = 0; i < flTable.Rows.Count; i++) {
				FieldLayoutRow flRow = flTable [i];
				FieldDefinition field = GetFieldDefAt (flRow.Field);
				field.Offset = flRow.Offset;
			}
		}

		void ReadPInvokeInfos ()
		{
			if (!m_tHeap.HasTable (ImplMapTable.RId))
				return;

			ImplMapTable imTable = m_tableReader.GetImplMapTable ();
			for (int i = 0; i < imTable.Rows.Count; i++) {
				ImplMapRow imRow = imTable [i];
				if (imRow.MemberForwarded.TokenType == TokenType.Method) { // should always be true
					MethodDefinition meth = GetMethodDefAt (imRow.MemberForwarded.RID);
					meth.PInvokeInfo = new PInvokeInfo (
						meth, imRow.MappingFlags, MetadataRoot.Streams.StringsHeap [imRow.ImportName],
						Module.ModuleReferences [(int) imRow.ImportScope - 1]);
				}
			}
		}

		void ReadProperties ()
		{
			if (!m_tHeap.HasTable (PropertyTable.RId)) {
				m_properties = new PropertyDefinition [0];
				return;
			}

			PropertyTable propsTable = m_tableReader.GetPropertyTable ();
			PropertyMapTable pmapTable = m_tableReader.GetPropertyMapTable ();
			m_properties = new PropertyDefinition [propsTable.Rows.Count];
			for (int i = 0; i < pmapTable.Rows.Count; i++) {
				PropertyMapRow pmapRow = pmapTable [i];
				if (pmapRow.Parent == 0)
					continue;

				TypeDefinition owner = GetTypeDefAt (pmapRow.Parent);

				GenericContext context = new GenericContext (owner);

				int start = (int) pmapRow.PropertyList, last = propsTable.Rows.Count + 1, end;
				if (i < pmapTable.Rows.Count - 1)
					end = (int) pmapTable [i + 1].PropertyList;
				else
					end = last;

				if (end > last)
					end = last;

				for (int j = start; j < end; j++) {
					PropertyRow prow = propsTable [j - 1];
					PropertySig psig = m_sigReader.GetPropSig (prow.Type);
					PropertyDefinition pdef = new PropertyDefinition (
						m_root.Streams.StringsHeap [prow.Name],
						GetTypeRefFromSig (psig.Type, context),
					prow.Flags);
					pdef.MetadataToken = MetadataToken.FromMetadataRow (TokenType.Property, j - 1);

					pdef.PropertyType = GetModifierType (psig.CustomMods, pdef.PropertyType);

					if (!IsDeleted (pdef))
						owner.Properties.Add (pdef);

					m_properties [j - 1] = pdef;
				}
			}
		}

		void ReadEvents ()
		{
			if (!m_tHeap.HasTable (EventTable.RId)) {
				m_events = new EventDefinition [0];
				return;
			}

			EventTable evtTable = m_tableReader.GetEventTable ();
			EventMapTable emapTable = m_tableReader.GetEventMapTable ();
			m_events = new EventDefinition [evtTable.Rows.Count];
			for (int i = 0; i < emapTable.Rows.Count; i++) {
				EventMapRow emapRow = emapTable [i];
				if (emapRow.Parent == 0)
					continue;

				TypeDefinition owner = GetTypeDefAt (emapRow.Parent);
				GenericContext context = new GenericContext (owner);

				int start = (int) emapRow.EventList, last = evtTable.Rows.Count + 1, end;
				if (i < (emapTable.Rows.Count - 1))
					end = (int) emapTable [i + 1].EventList;
				else
					end = last;

				if (end > last)
					end = last;

				for (int j = start; j < end; j++) {
					EventRow erow = evtTable [j - 1];
					EventDefinition edef = new EventDefinition (
						m_root.Streams.StringsHeap [erow.Name],
						GetTypeDefOrRef (erow.EventType, context), erow.EventFlags);
					edef.MetadataToken = MetadataToken.FromMetadataRow (TokenType.Event, j - 1);

					if (!IsDeleted (edef))
						owner.Events.Add (edef);

					m_events [j - 1] = edef;
				}
			}
		}

		void ReadSemantics ()
		{
			if (!m_tHeap.HasTable (MethodSemanticsTable.RId))
				return;

			MethodSemanticsTable semTable = m_tableReader.GetMethodSemanticsTable ();
			for (int i = 0; i < semTable.Rows.Count; i++) {
				MethodSemanticsRow semRow = semTable [i];
				MethodDefinition semMeth = GetMethodDefAt (semRow.Method);
				semMeth.SemanticsAttributes = semRow.Semantics;
				switch (semRow.Association.TokenType) {
				case TokenType.Event :
					EventDefinition evt = GetEventDefAt (semRow.Association.RID);
					if ((semRow.Semantics & MethodSemanticsAttributes.AddOn) != 0)
						evt.AddMethod = semMeth;
					else if ((semRow.Semantics & MethodSemanticsAttributes.Fire) != 0)
						evt.InvokeMethod = semMeth;
					else if ((semRow.Semantics & MethodSemanticsAttributes.RemoveOn) != 0)
						evt.RemoveMethod = semMeth;
					break;
				case TokenType.Property :
					PropertyDefinition prop = GetPropertyDefAt (semRow.Association.RID);
					if ((semRow.Semantics & MethodSemanticsAttributes.Getter) != 0)
						prop.GetMethod = semMeth;
					else if ((semRow.Semantics & MethodSemanticsAttributes.Setter) != 0)
						prop.SetMethod = semMeth;
					break;
				}
			}
		}

		void ReadInterfaces ()
		{
			if (!m_tHeap.HasTable (InterfaceImplTable.RId))
				return;

			InterfaceImplTable intfsTable = m_tableReader.GetInterfaceImplTable ();
			for (int i = 0; i < intfsTable.Rows.Count; i++) {
				InterfaceImplRow intfsRow = intfsTable [i];
				TypeDefinition owner = GetTypeDefAt (intfsRow.Class);
				owner.Interfaces.Add (GetTypeDefOrRef (intfsRow.Interface, new GenericContext (owner)));
			}
		}

		void ReadOverrides ()
		{
			if (!m_tHeap.HasTable (MethodImplTable.RId))
				return;

			MethodImplTable implTable = m_tableReader.GetMethodImplTable ();
			for (int i = 0; i < implTable.Rows.Count; i++) {
				MethodImplRow implRow = implTable [i];
				if (implRow.MethodBody.TokenType == TokenType.Method) {
					MethodDefinition owner = GetMethodDefAt (implRow.MethodBody.RID);
					switch (implRow.MethodDeclaration.TokenType) {
					case TokenType.Method :
						owner.Overrides.Add (
							GetMethodDefAt (implRow.MethodDeclaration.RID));
						break;
					case TokenType.MemberRef :
						owner.Overrides.Add (
							(MethodReference) GetMemberRefAt (
								implRow.MethodDeclaration.RID, new GenericContext (owner)));
						break;
					}
				}
			}
		}

		void ReadSecurityDeclarations ()
		{
			if (!m_tHeap.HasTable (DeclSecurityTable.RId))
				return;

			DeclSecurityTable dsTable = m_tableReader.GetDeclSecurityTable ();
			for (int i = 0; i < dsTable.Rows.Count; i++) {
				DeclSecurityRow dsRow = dsTable [i];
				SecurityDeclaration dec = BuildSecurityDeclaration (dsRow);

				if (dsRow.Parent.RID == 0)
					continue;

				IHasSecurity owner = null;
				switch (dsRow.Parent.TokenType) {
				case TokenType.Assembly :
					owner = this.Module.Assembly;
					break;
				case TokenType.TypeDef :
					owner = GetTypeDefAt (dsRow.Parent.RID);
					break;
				case TokenType.Method :
					owner = GetMethodDefAt (dsRow.Parent.RID);
					break;
				}

				owner.SecurityDeclarations.Add (dec);
			}
		}

		void ReadCustomAttributes ()
		{
			if (!m_tHeap.HasTable (CustomAttributeTable.RId))
				return;

			CustomAttributeTable caTable = m_tableReader.GetCustomAttributeTable ();
			for (int i = 0; i < caTable.Rows.Count; i++) {
				CustomAttributeRow caRow = caTable [i];
				MethodReference ctor;

				if (caRow.Type.RID == 0)
					continue;

				if (caRow.Type.TokenType == TokenType.Method)
					ctor = GetMethodDefAt (caRow.Type.RID);
				else
					ctor = GetMemberRefAt (caRow.Type.RID, new GenericContext ()) as MethodReference;

				CustomAttrib ca = m_sigReader.GetCustomAttrib (caRow.Value, ctor);
				CustomAttribute cattr = BuildCustomAttribute (ctor, m_root.Streams.BlobHeap.Read (caRow.Value), ca);

				if (caRow.Parent.RID == 0)
					continue;

				ICustomAttributeProvider owner = null;
				switch (caRow.Parent.TokenType) {
				case TokenType.Assembly :
					owner = this.Module.Assembly;
					break;
				case TokenType.Module :
					owner = this.Module;
					break;
				case TokenType.TypeDef :
					owner = GetTypeDefAt (caRow.Parent.RID);
					break;
				case TokenType.TypeRef :
					owner = GetTypeRefAt (caRow.Parent.RID);
					break;
				case TokenType.Field :
					owner = GetFieldDefAt (caRow.Parent.RID);
					break;
				case TokenType.Method :
					owner = GetMethodDefAt (caRow.Parent.RID);
					break;
				case TokenType.Property :
					owner = GetPropertyDefAt (caRow.Parent.RID);
					break;
				case TokenType.Event :
					owner = GetEventDefAt (caRow.Parent.RID);
					break;
				case TokenType.Param :
					owner = GetParamDefAt (caRow.Parent.RID);
					break;
				case TokenType.GenericParam :
					owner = GetGenericParameterAt (caRow.Parent.RID);
					break;
				default :
					//TODO: support other ?
					break;
				}

				if (owner != null)
					owner.CustomAttributes.Add (cattr);
			}
		}

		void ReadConstants ()
		{
			if (!m_tHeap.HasTable (ConstantTable.RId))
				return;

			ConstantTable csTable = m_tableReader.GetConstantTable ();
			for (int i = 0; i < csTable.Rows.Count; i++) {
				ConstantRow csRow = csTable [i];

				object constant = GetConstant (csRow.Value, csRow.Type);

				IHasConstant owner = null;
				switch (csRow.Parent.TokenType) {
				case TokenType.Field :
					owner = GetFieldDefAt (csRow.Parent.RID);
					break;
				case TokenType.Property :
					owner = GetPropertyDefAt (csRow.Parent.RID);
					break;
				case TokenType.Param :
					owner = GetParamDefAt (csRow.Parent.RID);
					break;
				}

				owner.Constant = constant;
			}
		}

		void ReadExternTypes ()
		{
			base.VisitExternTypeCollection (Module.ExternTypes);
		}

		void ReadMarshalSpecs ()
		{
			if (!m_tHeap.HasTable (FieldMarshalTable.RId))
				return;

			FieldMarshalTable fmTable = m_tableReader.GetFieldMarshalTable ();
			for (int i = 0; i < fmTable.Rows.Count; i++) {
				FieldMarshalRow fmRow = fmTable [i];

				if (fmRow.Parent.RID == 0)
					continue;

				IHasMarshalSpec owner = null;
				switch (fmRow.Parent.TokenType) {
				case TokenType.Field:
					owner = GetFieldDefAt (fmRow.Parent.RID);
					break;
				case TokenType.Param:
					owner = GetParamDefAt (fmRow.Parent.RID);
					break;
				}

				owner.MarshalSpec = BuildMarshalDesc (
					m_sigReader.GetMarshalSig (fmRow.NativeType), owner);
			}
		}

		void ReadInitialValues ()
		{
			if (!m_tHeap.HasTable (FieldRVATable.RId))
				return;

			FieldRVATable frTable = m_tableReader.GetFieldRVATable ();
			for (int i = 0; i < frTable.Rows.Count; i++) {
				FieldRVARow frRow = frTable [i];
				FieldDefinition field = GetFieldDefAt (frRow.Field);
				field.RVA = frRow.RVA;
				SetInitialValue (field);
			}
		}
	}
}
