//
// parameter.cs: Parameter definition.
//
// Author: Miguel de Icaza (miguel@gnu.org)
//         Marek Safar (marek.safar@seznam.cz)
//
// Dual licensed under the terms of the MIT X11 or GNU GPL
//
// Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
// Copyright 2003-2008 Novell, Inc. 
// Copyright 2011 Xamarin Inc
//
//
using System;
using System.Text;

#if STATIC
using MetaType = IKVM.Reflection.Type;
using IKVM.Reflection;
using IKVM.Reflection.Emit;
#else
using MetaType = System.Type;
using System.Reflection;
using System.Reflection.Emit;
#endif

namespace Mono.CSharp {

	/// <summary>
	///   Abstract Base class for parameters of a method.
	/// </summary>
	public abstract class ParameterBase : Attributable
	{
		protected ParameterBuilder builder;

		public override void ApplyAttributeBuilder (Attribute a, MethodSpec ctor, byte[] cdata, PredefinedAttributes pa)
		{
#if false
			if (a.Type == pa.MarshalAs) {
				UnmanagedMarshal marshal = a.GetMarshal (this);
				if (marshal != null) {
					builder.SetMarshal (marshal);
				}
				return;
			}
#endif
			if (a.HasSecurityAttribute) {
				a.Error_InvalidSecurityParent ();
				return;
			}

			if (a.Type == pa.Dynamic) {
				a.Error_MisusedDynamicAttribute ();
				return;
			}

			builder.SetCustomAttribute ((ConstructorInfo) ctor.GetMetaInfo (), cdata);
		}

		public ParameterBuilder Builder {
			get {
				return builder;
			}
		}

		public override bool IsClsComplianceRequired()
		{
			return false;
		}
	}

	/// <summary>
	/// Class for applying custom attributes on the return type
	/// </summary>
	public class ReturnParameter : ParameterBase
	{
		MemberCore method;

		// TODO: merge method and mb
		public ReturnParameter (MemberCore method, MethodBuilder mb, Location location)
		{
			this.method = method;
			try {
				builder = mb.DefineParameter (0, ParameterAttributes.None, "");			
			}
			catch (ArgumentOutOfRangeException) {
				method.Compiler.Report.RuntimeMissingSupport (location, "custom attributes on the return type");
			}
		}

		public override void ApplyAttributeBuilder (Attribute a, MethodSpec ctor, byte[] cdata, PredefinedAttributes pa)
		{
			if (a.Type == pa.CLSCompliant) {
				method.Compiler.Report.Warning (3023, 1, a.Location,
					"CLSCompliant attribute has no meaning when applied to return types. Try putting it on the method instead");
			}

			// This occurs after Warning -28
			if (builder == null)
				return;

			base.ApplyAttributeBuilder (a, ctor, cdata, pa);
		}

		public override AttributeTargets AttributeTargets {
			get {
				return AttributeTargets.ReturnValue;
			}
		}

		/// <summary>
		/// Is never called
		/// </summary>
		public override string[] ValidAttributeTargets {
			get {
				return null;
			}
		}
	}

	public class ImplicitLambdaParameter : Parameter
	{
		public ImplicitLambdaParameter (string name, Location loc)
			: base (null, name, Modifier.NONE, null, loc)
		{
		}

		public override TypeSpec Resolve (IMemberContext ec, int index)
		{
			if (parameter_type == null)
				throw new InternalErrorException ("A type of implicit lambda parameter `{0}' is not set",
					Name);

			base.idx = index;
			return parameter_type;
		}

		public void SetParameterType (TypeSpec type)
		{
			parameter_type = type;
		}
	}

	public class ParamsParameter : Parameter {

		bool ParamsAttributeEmit;

		public ParamsParameter (FullNamedExpression type, string name, Attributes attrs, Location loc):
			base (type, name, Parameter.Modifier.PARAMS, attrs, loc)
		{
		}

		public override TypeSpec Resolve (IMemberContext ec, int index)
		{
			if (base.Resolve (ec, index) == null)
				return null;

			var ac = parameter_type as ArrayContainer;
			if (ac == null || ac.Rank != 1) {
				ec.Module.Compiler.Report.Error (225, Location, "The params parameter must be a single dimensional array");
				return null;
			}

			var mc = ec as MemberCore;
			ParamsAttributeEmit = mc == null || (mc.ModFlags & Modifiers.OVERRIDE) == 0;

			return parameter_type;
		}

		public override void ApplyAttributes (MethodBuilder mb, ConstructorBuilder cb, int index, PredefinedAttributes pa)
		{
			base.ApplyAttributes (mb, cb, index, pa);

			if (ParamsAttributeEmit)
				pa.ParamArray.EmitAttribute (builder);
		}
	}

	public class ArglistParameter : Parameter {
		// Doesn't have proper type because it's never chosen for better conversion
		public ArglistParameter (Location loc) :
			base (null, String.Empty, Parameter.Modifier.NONE, null, loc)
		{
			parameter_type = InternalType.Arglist;
		}

		public override void  ApplyAttributes (MethodBuilder mb, ConstructorBuilder cb, int index, PredefinedAttributes pa)
		{
			// Nothing to do
		}

		public override bool CheckAccessibility (InterfaceMemberBase member)
		{
			return true;
		}

		public override TypeSpec Resolve (IMemberContext ec, int index)
		{
			return parameter_type;
		}
	}

	public interface IParameterData
	{
		Expression DefaultValue { get; }
		bool HasExtensionMethodModifier { get; }
		bool HasDefaultValue { get; }
		Parameter.Modifier ModFlags { get; }
		string Name { get; }
	}

	//
	// Parameter information created by parser
	//
	public class Parameter : ParameterBase, IParameterData, ILocalVariable // TODO: INamedBlockVariable
	{
		[Flags]
		public enum Modifier : byte {
			NONE    = 0,
			PARAMS  = 1 << 0,
			REF = 1 << 1,
			OUT = 1 << 2,
			This = 1 << 3,
			ReadOnly = 1 << 4,
			CallerMemberName = 1 << 5,
			CallerLineNumber = 1 << 6,
			CallerFilePath = 1 << 7,

			RefOutMask = REF | OUT,
			ModifierMask = PARAMS | REF | OUT | This | ReadOnly,
			CallerMask = CallerMemberName | CallerLineNumber | CallerFilePath
		}

		static readonly string[] attribute_targets = new [] { "param" };

		FullNamedExpression texpr;
		Modifier modFlags;
		string name;
		Expression default_expr;
		protected TypeSpec parameter_type;
		readonly Location loc;
		protected int idx;
		public bool HasAddressTaken;

		TemporaryVariableReference expr_tree_variable;

		HoistedParameter hoisted_variant;

		public Parameter (FullNamedExpression type, string name, Modifier mod, Attributes attrs, Location loc)
		{
			this.name = name;
			modFlags = mod;
			this.loc = loc;
			texpr = type;

			// Only assign, attributes will be attached during resolve
			base.attributes = attrs;
		}

		#region Properties

		public Expression DefaultExpression {
			get {
				return default_expr;
			}
		}

		public DefaultParameterValueExpression DefaultValue {
			get {
				return default_expr as DefaultParameterValueExpression;
			}
			set {
				default_expr = value;
			}
		}

		Expression IParameterData.DefaultValue {
			get {
				var expr = default_expr as DefaultParameterValueExpression;
				return expr == null ? default_expr : expr.Child;
			}
		}

		bool HasOptionalExpression {
			get {
				return default_expr is DefaultParameterValueExpression;
			}
		}

		public Location Location {
			get {
				return loc;
			}
		}

		public Modifier ParameterModifier {
			get {
				return modFlags;
			}
		}

		public TypeSpec Type {
			get {
				return parameter_type;
			}
			set {
				parameter_type = value;
			}
		}

		public FullNamedExpression TypeExpression  {
			get {
				return texpr;
			}
		}

		public override string[] ValidAttributeTargets {
			get {
				return attribute_targets;
			}
		}

		#endregion

		public override void ApplyAttributeBuilder (Attribute a, MethodSpec ctor, byte[] cdata, PredefinedAttributes pa)
		{
			if (a.Type == pa.In && ModFlags == Modifier.OUT) {
				a.Report.Error (36, a.Location, "An out parameter cannot have the `In' attribute");
				return;
			}

			if (a.Type == pa.ParamArray) {
				a.Report.Error (674, a.Location, "Do not use `System.ParamArrayAttribute'. Use the `params' keyword instead");
				return;
			}

			if (a.Type == pa.Out && (ModFlags & Modifier.REF) != 0 &&
			    !OptAttributes.Contains (pa.In)) {
				a.Report.Error (662, a.Location,
					"Cannot specify only `Out' attribute on a ref parameter. Use both `In' and `Out' attributes or neither");
				return;
			}

			if (a.Type == pa.CLSCompliant) {
				a.Report.Warning (3022, 1, a.Location, "CLSCompliant attribute has no meaning when applied to parameters. Try putting it on the method instead");
			} else if (a.Type == pa.DefaultParameterValue || a.Type == pa.OptionalParameter) {
				if (HasOptionalExpression) {
					a.Report.Error (1745, a.Location,
						"Cannot specify `{0}' attribute on optional parameter `{1}'",
						a.Type.GetSignatureForError ().Replace ("Attribute", ""), Name);
				}

				if (a.Type == pa.DefaultParameterValue)
					return;
			} else if (a.Type == pa.CallerMemberNameAttribute) {
				if ((modFlags & Modifier.CallerMemberName) == 0) {
					a.Report.Error (4022, a.Location,
						"The CallerMemberName attribute can only be applied to parameters with default value");
				}
			} else if (a.Type == pa.CallerLineNumberAttribute) {
				if ((modFlags & Modifier.CallerLineNumber) == 0) {
					a.Report.Error (4020, a.Location,
						"The CallerLineNumber attribute can only be applied to parameters with default value");
				}
			} else if (a.Type == pa.CallerFilePathAttribute) {
				if ((modFlags & Modifier.CallerFilePath) == 0) {
					a.Report.Error (4021, a.Location,
						"The CallerFilePath attribute can only be applied to parameters with default value");
				}
			}

			base.ApplyAttributeBuilder (a, ctor, cdata, pa);
		}
		
		public virtual bool CheckAccessibility (InterfaceMemberBase member)
		{
			if (parameter_type == null)
				return true;

			return member.IsAccessibleAs (parameter_type);
		}

		bool IsValidCallerContext (MemberCore memberContext)
		{
			var m = memberContext as Method;
			if (m != null)
				return !m.IsPartialImplementation;

			return true;
		}

		// <summary>
		//   Resolve is used in method definitions
		// </summary>
		public virtual TypeSpec Resolve (IMemberContext rc, int index)
		{
			if (parameter_type != null)
				return parameter_type;

			if (attributes != null)
				attributes.AttachTo (this, rc);

			parameter_type = texpr.ResolveAsType (rc);
			if (parameter_type == null)
				return null;

			this.idx = index;

			if ((modFlags & Parameter.Modifier.RefOutMask) != 0 && parameter_type.IsSpecialRuntimeType) {
				rc.Module.Compiler.Report.Error (1601, Location, "Method or delegate parameter cannot be of type `{0}'",
					GetSignatureForError ());
				return null;
			}

			VarianceDecl.CheckTypeVariance (parameter_type,
				(modFlags & Parameter.Modifier.RefOutMask) != 0 ? Variance.None : Variance.Contravariant,
				rc);

			if (parameter_type.IsStatic) {
				rc.Module.Compiler.Report.Error (721, Location, "`{0}': static types cannot be used as parameters",
					texpr.GetSignatureForError ());
				return parameter_type;
			}

			if ((modFlags & Modifier.This) != 0 && (parameter_type.IsPointer || parameter_type.BuiltinType == BuiltinTypeSpec.Type.Dynamic)) {
				rc.Module.Compiler.Report.Error (1103, Location, "The extension method cannot be of type `{0}'",
					parameter_type.GetSignatureForError ());
			}

			return parameter_type;
		}

		void ResolveCallerAttributes (ResolveContext rc)
		{
			var pa = rc.Module.PredefinedAttributes;
			TypeSpec caller_type;
			Attribute callerMemberName = null, callerFilePath = null;

			foreach (var attr in attributes.Attrs) {
				var atype = attr.ResolveTypeForComparison ();
				if (atype == null)
					continue;

				if (atype == pa.CallerMemberNameAttribute) {
					caller_type = rc.BuiltinTypes.String;
					if (caller_type != parameter_type && !Convert.ImplicitReferenceConversionExists (caller_type, parameter_type)) {
						rc.Report.Error (4019, attr.Location,
							"The CallerMemberName attribute cannot be applied because there is no standard conversion from `{0}' to `{1}'",
							caller_type.GetSignatureForError (), parameter_type.GetSignatureForError ());
					}

					if (!IsValidCallerContext (rc.CurrentMemberDefinition)) {
						rc.Report.Warning (4026, 1, attr.Location,
							"The CallerMemberName applied to parameter `{0}' will have no effect because it applies to a member that is used in context that do not allow optional arguments",
							name);
					}

					modFlags |= Modifier.CallerMemberName;
					callerMemberName = attr;
					continue;
				}

				if (atype == pa.CallerLineNumberAttribute) {
					caller_type = rc.BuiltinTypes.Int;
					if (caller_type != parameter_type && !Convert.ImplicitStandardConversionExists (new IntConstant (caller_type, int.MaxValue, Location.Null), parameter_type)) {
						rc.Report.Error (4017, attr.Location,
							"The CallerLineNumberAttribute attribute cannot be applied because there is no standard conversion from `{0}' to `{1}'",
							caller_type.GetSignatureForError (), parameter_type.GetSignatureForError ());
					}

					if (!IsValidCallerContext (rc.CurrentMemberDefinition)) {
						rc.Report.Warning (4024, 1, attr.Location,
							"The CallerLineNumberAttribute applied to parameter `{0}' will have no effect because it applies to a member that is used in context that do not allow optional arguments",
							name);
					}

					modFlags |= Modifier.CallerLineNumber;
					continue;
				}

				if (atype == pa.CallerFilePathAttribute) {
					caller_type = rc.BuiltinTypes.String;
					if (caller_type != parameter_type && !Convert.ImplicitReferenceConversionExists (caller_type, parameter_type)) {
						rc.Report.Error (4018, attr.Location,
							"The CallerFilePath attribute cannot be applied because there is no standard conversion from `{0}' to `{1}'",
							caller_type.GetSignatureForError (), parameter_type.GetSignatureForError ());
					}

					if (!IsValidCallerContext (rc.CurrentMemberDefinition)) {
						rc.Report.Warning (4025, 1, attr.Location,
							"The CallerFilePath applied to parameter `{0}' will have no effect because it applies to a member that is used in context that do not allow optional arguments",
							name);
					}

					modFlags |= Modifier.CallerFilePath;
					callerFilePath = attr;
					continue;
				}
			}

			if ((modFlags & Modifier.CallerLineNumber) != 0) {
				if (callerMemberName != null) {
					rc.Report.Warning (7081, 1, callerMemberName.Location,
						"The CallerMemberNameAttribute applied to parameter `{0}' will have no effect. It is overridden by the CallerLineNumberAttribute",
						Name);
				}

				if (callerFilePath != null) {
					rc.Report.Warning (7082, 1, callerFilePath.Location,
						"The CallerFilePathAttribute applied to parameter `{0}' will have no effect. It is overridden by the CallerLineNumberAttribute",
						name);
				}
			}

			if ((modFlags & Modifier.CallerMemberName) != 0) {
				if (callerFilePath != null) {
					rc.Report.Warning (7080, 1, callerFilePath.Location,
						"The CallerMemberNameAttribute applied to parameter `{0}' will have no effect. It is overridden by the CallerFilePathAttribute",
						name);
				}

			}
		}

		public void ResolveDefaultValue (ResolveContext rc)
		{
			//
			// Default value was specified using an expression
			//
			if (default_expr != null) {
				((DefaultParameterValueExpression)default_expr).Resolve (rc, this);
				if (attributes != null)
					ResolveCallerAttributes (rc);

				return;
			}

			if (attributes == null)
				return;

			var pa = rc.Module.PredefinedAttributes;
			var def_attr = attributes.Search (pa.DefaultParameterValue);
			if (def_attr != null) {
				if (def_attr.Resolve () == null)
					return;

				var default_expr_attr = def_attr.GetParameterDefaultValue ();
				if (default_expr_attr == null)
					return;

				var dpa_rc = def_attr.CreateResolveContext ();
				default_expr = default_expr_attr.Resolve (dpa_rc);

				if (default_expr is BoxedCast)
					default_expr = ((BoxedCast) default_expr).Child;

				Constant c = default_expr as Constant;
				if (c == null) {
					if (parameter_type.BuiltinType == BuiltinTypeSpec.Type.Object) {
						rc.Report.Error (1910, default_expr.Location,
							"Argument of type `{0}' is not applicable for the DefaultParameterValue attribute",
							default_expr.Type.GetSignatureForError ());
					} else {
						rc.Report.Error (1909, default_expr.Location,
							"The DefaultParameterValue attribute is not applicable on parameters of type `{0}'",
							default_expr.Type.GetSignatureForError ());
					}

					default_expr = null;
					return;
				}

				if (TypeSpecComparer.IsEqual (default_expr.Type, parameter_type) ||
					(default_expr is NullConstant && TypeSpec.IsReferenceType (parameter_type) && !parameter_type.IsGenericParameter) ||
					parameter_type.BuiltinType == BuiltinTypeSpec.Type.Object ||
					parameter_type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
					return;
				}

				//
				// LAMESPEC: Some really weird csc behaviour which we have to mimic
				// User operators returning same type as parameter type are considered
				// valid for this attribute only
				//
				// struct S { public static implicit operator S (int i) {} }
				//
				// void M ([DefaultParameterValue (3)]S s)
				//
				var expr = Convert.ImplicitUserConversion (dpa_rc, default_expr, parameter_type, loc);
				if (expr != null && TypeSpecComparer.IsEqual (expr.Type, parameter_type)) {
					return;
				}
				
				rc.Report.Error (1908, default_expr.Location, "The type of the default value should match the type of the parameter");
				return;
			}

			var opt_attr = attributes.Search (pa.OptionalParameter);
			if (opt_attr != null) {
				default_expr = EmptyExpression.MissingValue;
			}
		}

		public bool HasDefaultValue {
			get { return default_expr != null; }
		}

		public bool HasExtensionMethodModifier {
			get { return (modFlags & Modifier.This) != 0; }
		}

		//
		// Hoisted parameter variant
		//
		public HoistedParameter HoistedVariant {
			get {
				return hoisted_variant;
			}
			set {
				hoisted_variant = value;
			}
		}

		public Modifier ModFlags {
			get { return modFlags & ~Modifier.This; }
		}

		public string Name {
			get { return name; }
			set { name = value; }
		}

		public override AttributeTargets AttributeTargets {
			get {
				return AttributeTargets.Parameter;
			}
		}

		public void Error_DuplicateName (Report r)
		{
			r.Error (100, Location, "The parameter name `{0}' is a duplicate", Name);
		}

		public virtual string GetSignatureForError ()
		{
			string type_name;
			if (parameter_type != null)
				type_name = parameter_type.GetSignatureForError ();
			else
				type_name = texpr.GetSignatureForError ();

			string mod = GetModifierSignature (modFlags);
			if (mod.Length > 0)
				return String.Concat (mod, " ", type_name);

			return type_name;
		}

		public static string GetModifierSignature (Modifier mod)
		{
			switch (mod) {
			case Modifier.OUT:
				return "out";
			case Modifier.PARAMS:
				return "params";
			case Modifier.REF:
				return "ref";
			case Modifier.This:
				return "this";
			default:
				return "";
			}
		}

		public void IsClsCompliant (IMemberContext ctx)
		{
			if (parameter_type.IsCLSCompliant ())
				return;

			ctx.Module.Compiler.Report.Warning (3001, 1, Location,
				"Argument type `{0}' is not CLS-compliant", parameter_type.GetSignatureForError ());
		}

		public virtual void ApplyAttributes (MethodBuilder mb, ConstructorBuilder cb, int index, PredefinedAttributes pa)
		{
			if (builder != null)
				throw new InternalErrorException ("builder already exists");

			var pattrs = ParametersCompiled.GetParameterAttribute (modFlags);
			if (HasOptionalExpression)
				pattrs |= ParameterAttributes.Optional;

			if (mb == null)
				builder = cb.DefineParameter (index, pattrs, Name);
			else
				builder = mb.DefineParameter (index, pattrs, Name);

			if (OptAttributes != null)
				OptAttributes.Emit ();

			if (HasDefaultValue && default_expr.Type != null) {
				//
				// Emit constant values for true constants only, the other
				// constant-like expressions will rely on default value expression
				//
				var def_value = DefaultValue;
				Constant c = def_value != null ? def_value.Child as Constant : default_expr as Constant;
				if (c != null) {
					if (c.Type.BuiltinType == BuiltinTypeSpec.Type.Decimal) {
						pa.DecimalConstant.EmitAttribute (builder, (decimal) c.GetValue (), c.Location);
					} else {
						builder.SetConstant (c.GetValue ());
					}
				} else if (default_expr.Type.IsStruct || default_expr.Type.IsGenericParameter) {
					//
					// Handles special case where default expression is used with value-type or type parameter
					//
					// void Foo (S s = default (S)) {}
					//
					builder.SetConstant (null);
				}
			}

			if (parameter_type != null) {
				if (parameter_type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
					pa.Dynamic.EmitAttribute (builder);
				} else if (parameter_type.HasDynamicElement) {
					pa.Dynamic.EmitAttribute (builder, parameter_type, Location);
				}

				if (parameter_type.HasNamedTupleElement) {
					pa.TupleElementNames.EmitAttribute (builder, parameter_type, Location);
				}
			}
		}

		public Parameter Clone ()
		{
			Parameter p = (Parameter) MemberwiseClone ();
			if (attributes != null)
				p.attributes = attributes.Clone ();

			return p;
		}

		public ExpressionStatement CreateExpressionTreeVariable (BlockContext ec)
		{
			if ((modFlags & Modifier.RefOutMask) != 0)
				ec.Report.Error (1951, Location, "An expression tree parameter cannot use `ref' or `out' modifier");

			expr_tree_variable = TemporaryVariableReference.Create (ResolveParameterExpressionType (ec, Location).Type, ec.CurrentBlock.ParametersBlock, Location);
			expr_tree_variable = (TemporaryVariableReference) expr_tree_variable.Resolve (ec);

			Arguments arguments = new Arguments (2);
			arguments.Add (new Argument (new TypeOf (parameter_type, Location)));
			arguments.Add (new Argument (new StringConstant (ec.BuiltinTypes, Name, Location)));
			return new SimpleAssign (ExpressionTreeVariableReference (),
				Expression.CreateExpressionFactoryCall (ec, "Parameter", null, arguments, Location));
		}

		public void Emit (EmitContext ec)
		{
			ec.EmitArgumentLoad (idx);
		}

		public void EmitAssign (EmitContext ec)
		{
			ec.EmitArgumentStore (idx);
		}

		public void EmitAddressOf (EmitContext ec)
		{
			if ((ModFlags & Modifier.RefOutMask) != 0) {
				ec.EmitArgumentLoad (idx);
			} else {
				ec.EmitArgumentAddress (idx);
			}
		}

		public TemporaryVariableReference ExpressionTreeVariableReference ()
		{
			return expr_tree_variable;
		}

		//
		// System.Linq.Expressions.ParameterExpression type
		//
		public static TypeExpr ResolveParameterExpressionType (IMemberContext ec, Location location)
		{
			TypeSpec p_type = ec.Module.PredefinedTypes.ParameterExpression.Resolve ();
			return new TypeExpression (p_type, location);
		}

		public void SetIndex (int index)
		{
			idx = index;
		}

		public void Warning_UselessOptionalParameter (Report Report)
		{
			Report.Warning (1066, 1, Location,
				"The default value specified for optional parameter `{0}' will never be used",
				Name);
		}
	}

	//
	// Imported or resolved parameter information
	//
	public class ParameterData : IParameterData
	{
		readonly string name;
		readonly Parameter.Modifier modifiers;
		readonly Expression default_value;

		public ParameterData (string name, Parameter.Modifier modifiers)
		{
			this.name = name;
			this.modifiers = modifiers;
		}

		public ParameterData (string name, Parameter.Modifier modifiers, Expression defaultValue)
			: this (name, modifiers)
		{
			this.default_value = defaultValue;
		}

		#region IParameterData Members

		public Expression DefaultValue {
			get { return default_value; }
		}

		public bool HasExtensionMethodModifier {
			get { return (modifiers & Parameter.Modifier.This) != 0; }
		}

		public bool HasDefaultValue {
			get { return default_value != null; }
		}

		public Parameter.Modifier ModFlags {
			get { return modifiers; }
		}

		public string Name {
			get { return name; }
		}

		#endregion
	}

	public abstract class AParametersCollection
	{
		protected bool has_arglist;
		protected bool has_params;

		// Null object pattern
		protected IParameterData [] parameters;
		protected TypeSpec [] types;

		public CallingConventions CallingConvention {
			get {
				return has_arglist ?
					CallingConventions.VarArgs :
					CallingConventions.Standard;
			}
		}

		public int Count {
			get { return parameters.Length; }
		}

		public TypeSpec ExtensionMethodType {
			get {
				if (Count == 0)
					return null;

				return FixedParameters [0].HasExtensionMethodModifier ?
					types [0] : null;
			}
		}

		public IParameterData [] FixedParameters {
			get {
				return parameters;
			}
		}

		public static ParameterAttributes GetParameterAttribute (Parameter.Modifier modFlags)
		{
			return (modFlags & Parameter.Modifier.OUT) != 0 ?
				ParameterAttributes.Out : ParameterAttributes.None;
		}

		// Very expensive operation
		public MetaType[] GetMetaInfo ()
		{
			MetaType[] types;
			if (has_arglist) {
				if (Count == 1)
					return MetaType.EmptyTypes;

				types = new MetaType[Count - 1];
			} else {
				if (Count == 0)
					return MetaType.EmptyTypes;

				types = new MetaType[Count];
			}

			for (int i = 0; i < types.Length; ++i) {
				types[i] = Types[i].GetMetaInfo ();

				if ((FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask) == 0)
					continue;

				// TODO MemberCache: Should go to MetaInfo getter
				types [i] = types [i].MakeByRefType ();
			}

			return types;
		}

		//
		// Returns the parameter information based on the name
		//
		public int GetParameterIndexByName (string name)
		{
			for (int idx = 0; idx < Count; ++idx) {
				if (parameters [idx].Name == name)
					return idx;
			}

			return -1;
		}

		public string GetSignatureForDocumentation ()
		{
			if (IsEmpty)
				return string.Empty;

			StringBuilder sb = new StringBuilder ("(");
			for (int i = 0; i < Count; ++i) {
				if (i != 0)
					sb.Append (",");

				sb.Append (types [i].GetSignatureForDocumentation ());

				if ((parameters[i].ModFlags & Parameter.Modifier.RefOutMask) != 0)
					sb.Append ("@");
			}
			sb.Append (")");

			return sb.ToString ();
		}

		public string GetSignatureForError ()
		{
			return GetSignatureForError ("(", ")", Count);
		}

		public string GetSignatureForError (string start, string end, int count)
		{
			StringBuilder sb = new StringBuilder (start);
			for (int i = 0; i < count; ++i) {
				if (i != 0)
					sb.Append (", ");
				sb.Append (ParameterDesc (i));
			}
			sb.Append (end);
			return sb.ToString ();
		}

		public static bool HasSameParameterDefaults (AParametersCollection a, AParametersCollection b)
		{
			if (a == null)
				return b == null;

			for (int i = 0; i < a.Count; ++i) {
				if (a.FixedParameters [i].HasDefaultValue != b.FixedParameters [i].HasDefaultValue)
					return false;
			}

			return true;
		}

		public bool HasArglist {
			get { return has_arglist; }
		}

		public bool HasExtensionMethodType {
			get {
				if (Count == 0)
					return false;

				return FixedParameters [0].HasExtensionMethodModifier;
			}
		}

		public bool HasParams {
			get { return has_params; }
		}

		public bool IsEmpty {
			get { return parameters.Length == 0; }
		}

		public AParametersCollection Inflate (TypeParameterInflator inflator)
		{
			TypeSpec[] inflated_types = null;
			bool default_value = false;

			for (int i = 0; i < Count; ++i) {
				var inflated_param = inflator.Inflate (types[i]);
				if (inflated_types == null) {
					if (inflated_param == types[i])
						continue;

					default_value |= FixedParameters[i].HasDefaultValue;
					inflated_types = new TypeSpec[types.Length];
					Array.Copy (types, inflated_types, types.Length);
				} else {
					if (inflated_param == types[i])
						continue;

					default_value |= FixedParameters[i].HasDefaultValue;
				}

				inflated_types[i] = inflated_param;
			}

			if (inflated_types == null)
				return this;

			var clone = (AParametersCollection) MemberwiseClone ();
			clone.types = inflated_types;

			//
			// Default expression is original expression from the parameter
			// declaration context which can be of nested enum in generic class type.
			// In such case we end up with expression type of G<T>.E and e.g. parameter
			// type of G<int>.E and conversion would fail without inflate in this
			// context.
			//
			if (default_value) {
				clone.parameters = new IParameterData[Count];
				for (int i = 0; i < Count; ++i) {
					var fp = FixedParameters[i];
					clone.FixedParameters[i] = fp;

					if (!fp.HasDefaultValue)
						continue;

					var expr = fp.DefaultValue;

					if (inflated_types[i] == expr.Type)
						continue;

					var c = expr as Constant;
					if (c != null) {
						//
						// It may fail we are inflating before type validation is done
						//
						c = Constant.ExtractConstantFromValue (inflated_types[i], c.GetValue (), expr.Location);
						if (c == null)
							expr = new DefaultValueExpression (new TypeExpression (inflated_types[i], expr.Location), expr.Location);
						else
							expr = c;
					} else if (expr is DefaultValueExpression)
						expr = new DefaultValueExpression (new TypeExpression (inflated_types[i], expr.Location), expr.Location);

					clone.FixedParameters[i] = new ParameterData (fp.Name, fp.ModFlags, expr);
				}
			}

			return clone;
		}

		public string ParameterDesc (int pos)
		{
			if (types == null || types [pos] == null)
				return ((Parameter)FixedParameters [pos]).GetSignatureForError ();

			string type = types [pos].GetSignatureForError ();
			if (FixedParameters [pos].HasExtensionMethodModifier)
				return "this " + type;

			var mod = FixedParameters[pos].ModFlags & Parameter.Modifier.ModifierMask;
			if (mod == 0)
				return type;

			return Parameter.GetModifierSignature (mod) + " " + type;
		}

		public TypeSpec[] Types {
			get { return types; }
			set { types = value; }
		}
	}

	//
	// A collection of imported or resolved parameters
	//
	public class ParametersImported : AParametersCollection
	{
		public ParametersImported (IParameterData [] parameters, TypeSpec [] types, bool hasArglist, bool hasParams)
		{
			this.parameters = parameters;
			this.types = types;
			this.has_arglist = hasArglist;
			this.has_params = hasParams;
		}

		public ParametersImported (IParameterData[] param, TypeSpec[] types, bool hasParams)
		{
			this.parameters = param;
			this.types = types;
			this.has_params = hasParams;
		}
	}

	/// <summary>
	///   Represents the methods parameters
	/// </summary>
	public class ParametersCompiled : AParametersCollection
	{
		public static readonly ParametersCompiled EmptyReadOnlyParameters = new ParametersCompiled ();
		
		// Used by C# 2.0 delegates
		public static readonly ParametersCompiled Undefined = new ParametersCompiled ();

		private ParametersCompiled ()
		{
			parameters = new Parameter [0];
			types = TypeSpec.EmptyTypes;
		}

		private ParametersCompiled (IParameterData[] parameters, TypeSpec[] types)
		{
			this.parameters = parameters;
		    this.types = types;
		}
		
		public ParametersCompiled (params Parameter[] parameters)
		{
			if (parameters == null || parameters.Length == 0)
				throw new ArgumentException ("Use EmptyReadOnlyParameters");

			this.parameters = parameters;
			int count = parameters.Length;

			for (int i = 0; i < count; i++){
				has_params |= (parameters [i].ModFlags & Parameter.Modifier.PARAMS) != 0;
			}
		}

		public ParametersCompiled (Parameter [] parameters, bool has_arglist) :
			this (parameters)
		{
			this.has_arglist = has_arglist;
		}
		
		public static ParametersCompiled CreateFullyResolved (Parameter p, TypeSpec type)
		{
			return new ParametersCompiled (new Parameter [] { p }, new TypeSpec [] { type });
		}
		
		public static ParametersCompiled CreateFullyResolved (Parameter[] parameters, TypeSpec[] types)
		{
			return new ParametersCompiled (parameters, types);
		}

		public static ParametersCompiled Prefix (ParametersCompiled parameters, Parameter p, TypeSpec type)
		{
			var ptypes = new TypeSpec [parameters.Count + 1];
			ptypes [0] = type;
			Array.Copy (parameters.Types, 0, ptypes, 1, parameters.Count);

			var param = new Parameter [ptypes.Length];
			param [0] = p;
			for (int i = 0; i < parameters.Count; ++i) {
				var pi = parameters [i];
				param [i + 1] = pi;
				pi.SetIndex (i + 1);
			}

			return ParametersCompiled.CreateFullyResolved (param, ptypes);
		}

		//
		// TODO: This does not fit here, it should go to different version of AParametersCollection
		// as the underlying type is not Parameter and some methods will fail to cast
		//
		public static AParametersCollection CreateFullyResolved (params TypeSpec[] types)
		{
			var pd = new ParameterData [types.Length];
			for (int i = 0; i < pd.Length; ++i)
				pd[i] = new ParameterData (null, Parameter.Modifier.NONE, null);

			return new ParametersCompiled (pd, types);
		}

		public static ParametersCompiled CreateImplicitParameter (FullNamedExpression texpr, Location loc)
		{
			return new ParametersCompiled (
				new[] { new Parameter (texpr, "value", Parameter.Modifier.NONE, null, loc) },
				null);
		}

		public void CheckConstraints (IMemberContext mc)
		{
			foreach (Parameter p in parameters) {
				//
				// It's null for compiler generated types or special types like __arglist
				//
				if (p.TypeExpression != null)
					ConstraintChecker.Check (mc, p.Type, p.TypeExpression.Location);
			}
		}

		//
		// Returns non-zero value for equal CLS parameter signatures
		//
		public static int IsSameClsSignature (AParametersCollection a, AParametersCollection b)
		{
			int res = 0;

			for (int i = 0; i < a.Count; ++i) {
				var a_type = a.Types[i];
				var b_type = b.Types[i];
				if (TypeSpecComparer.Override.IsEqual (a_type, b_type)) {
					if ((a.FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask) != (b.FixedParameters[i].ModFlags & Parameter.Modifier.RefOutMask))
						res |= 1;

					continue;
				}

				var ac_a = a_type as ArrayContainer;
				if (ac_a == null)
					return 0;

				var ac_b = b_type as ArrayContainer;
				if (ac_b == null)
					return 0;

				if (ac_a.Element is ArrayContainer || ac_b.Element is ArrayContainer) {
					res |= 2;
					continue;
				}

				if (ac_a.Rank != ac_b.Rank && TypeSpecComparer.Override.IsEqual (ac_a.Element, ac_b.Element)) {
					res |= 1;
					continue;
				}

				return 0;
			}

			return res;
		}

		public static ParametersCompiled MergeGenerated (CompilerContext ctx, ParametersCompiled userParams, bool checkConflicts, Parameter compilerParams, TypeSpec compilerTypes)
		{
			return MergeGenerated (ctx, userParams, checkConflicts,
				new Parameter [] { compilerParams },
				new TypeSpec [] { compilerTypes });
		}

		//
		// Use this method when you merge compiler generated parameters with user parameters
		//
		public static ParametersCompiled MergeGenerated (CompilerContext ctx, ParametersCompiled userParams, bool checkConflicts, Parameter[] compilerParams, TypeSpec[] compilerTypes)
		{
			Parameter[] all_params = new Parameter [userParams.Count + compilerParams.Length];
			userParams.FixedParameters.CopyTo(all_params, 0);

			TypeSpec [] all_types;
			if (userParams.types != null) {
				all_types = new TypeSpec [all_params.Length];
				userParams.Types.CopyTo (all_types, 0);
			} else {
				all_types = null;
			}

			int last_filled = userParams.Count;
			int index = 0;
			foreach (Parameter p in compilerParams) {
				for (int i = 0; i < last_filled; ++i) {
					while (p.Name == all_params [i].Name) {
						if (checkConflicts && i < userParams.Count) {
							ctx.Report.Error (316, userParams[i].Location,
								"The parameter name `{0}' conflicts with a compiler generated name", p.Name);
						}
						p.Name = '_' + p.Name;
					}
				}
				all_params [last_filled] = p;
				if (all_types != null)
					all_types [last_filled] = compilerTypes [index++];
				++last_filled;
			}
			
			ParametersCompiled parameters = new ParametersCompiled (all_params, all_types);
			parameters.has_params = userParams.has_params;
			return parameters;
		}

		//
		// Parameters checks for members which don't have a block
		//
		public void CheckParameters (MemberCore member)
		{
			for (int i = 0; i < parameters.Length; ++i) {
				var name = parameters[i].Name;
				for (int ii = i + 1; ii < parameters.Length; ++ii) {
					if (parameters[ii].Name == name)
						this[ii].Error_DuplicateName (member.Compiler.Report);
				}
			}
		}

		public bool Resolve (IMemberContext ec)
		{
			if (types != null)
				return true;
			
			types = new TypeSpec [Count];
			
			bool ok = true;
			Parameter p;
			for (int i = 0; i < FixedParameters.Length; ++i) {
				p = this [i];
				TypeSpec t = p.Resolve (ec, i);
				if (t == null) {
					ok = false;
					continue;
				}

				types [i] = t;
			}

			return ok;
		}

		public void ResolveDefaultValues (MemberCore m)
		{
			ResolveContext rc = null;
			for (int i = 0; i < parameters.Length; ++i) {
				Parameter p = (Parameter) parameters [i];

				if (p.Type != null)
					p.Type.CheckObsoleteness (m, p.Location);

				//
				// Try not to enter default values resolution if there are is not any default value possible
				//
				if (p.HasDefaultValue || p.OptAttributes != null) {
					if (rc == null)
						rc = new ResolveContext (m);

					p.ResolveDefaultValue (rc);
				}
			}
		}

		// Define each type attribute (in/out/ref) and
		// the argument names.
		public void ApplyAttributes (IMemberContext mc, MethodBase builder)
		{
			if (Count == 0)
				return;

			MethodBuilder mb = builder as MethodBuilder;
			ConstructorBuilder cb = builder as ConstructorBuilder;
			var pa = mc.Module.PredefinedAttributes;

			for (int i = 0; i < Count; i++) {
				this [i].ApplyAttributes (mb, cb, i + 1, pa);
			}
		}

		public void VerifyClsCompliance (IMemberContext ctx)
		{
			foreach (Parameter p in FixedParameters)
				p.IsClsCompliant (ctx);
		}

		public Parameter this [int pos] {
			get { return (Parameter) parameters [pos]; }
		}

		public Expression CreateExpressionTree (BlockContext ec, Location loc)
		{
			var initializers = new ArrayInitializer (Count, loc);
			foreach (Parameter p in FixedParameters) {
				//
				// Each parameter expression is stored to local variable
				// to save some memory when referenced later.
				//
				StatementExpression se = new StatementExpression (p.CreateExpressionTreeVariable (ec), Location.Null);
				if (se.Resolve (ec)) {
					ec.CurrentBlock.AddScopeStatement (new TemporaryVariableReference.Declarator (p.ExpressionTreeVariableReference ()));
					ec.CurrentBlock.AddScopeStatement (se);
				}
				
				initializers.Add (p.ExpressionTreeVariableReference ());
			}

			return new ArrayCreation (
				Parameter.ResolveParameterExpressionType (ec, loc),
				initializers, loc);
		}

		public ParametersCompiled Clone ()
		{
			ParametersCompiled p = (ParametersCompiled) MemberwiseClone ();

			p.parameters = new IParameterData [parameters.Length];
			for (int i = 0; i < Count; ++i)
				p.parameters [i] = this [i].Clone ();

			return p;
		}
	}

	//
	// Default parameter value expression. We need this wrapper to handle
	// default parameter values of folded constants (e.g. indexer parameters).
	// The expression is resolved only once but applied to two methods which
	// both share reference to this expression and we ensure that resolving
	// this expression always returns same instance
	//
	public class DefaultParameterValueExpression : CompositeExpression
	{
		public DefaultParameterValueExpression (Expression expr)
			: base (expr)
		{
		}

		public void Resolve (ResolveContext rc, Parameter p)
		{
			var expr = Resolve (rc);
			if (expr == null) {
				this.expr = ErrorExpression.Instance;
				return;
			}

			expr = Child;

			if (!(expr is Constant || expr is DefaultValueExpression || expr is DefaultLiteralExpression || (expr is New && ((New) expr).IsGeneratedStructConstructor))) {
				if (!(expr is ErrorExpression)) {
					rc.Report.Error (1736, Location,
						"The expression being assigned to optional parameter `{0}' must be a constant or default value",
						p.Name);
				}

				return;
			}

			var parameter_type = p.Type;
			if (type == parameter_type)
				return;

			var res = Convert.ImplicitConversionStandard (rc, expr, parameter_type, Location);
			if (res != null) {
				if (parameter_type.IsNullableType && res is Nullable.Wrap) {
					Nullable.Wrap wrap = (Nullable.Wrap) res;
					res = wrap.Child;
					if (!(res is Constant)) {
						rc.Report.Error (1770, Location,
							"The expression being assigned to nullable optional parameter `{0}' must be default value",
							p.Name);
						return;
					}
				}

				if (!res.IsNull && TypeSpec.IsReferenceType (parameter_type) && parameter_type.BuiltinType != BuiltinTypeSpec.Type.String) {
					rc.Report.Error (1763, Location,
						"Optional parameter `{0}' of type `{1}' can only be initialized with default value",
						p.Name, parameter_type.GetSignatureForError ());

					return;
				}

				this.expr = res;
				return;
			}

			rc.Report.Error (1750, Location,
				"Optional parameter expression of type `{0}' cannot be converted to parameter type `{1}'",
				type.GetSignatureForError (), parameter_type.GetSignatureForError ());

			this.expr = ErrorExpression.Instance;
		}
		
		public override object Accept (StructuralVisitor visitor)
		{
			return visitor.Visit (this);
		}
	}
}
