//
// iterators.cs: Support for implementing iterators
//
// Author:
//   Miguel de Icaza (miguel@ximian.com)
//
// (C) 2003 Ximian, Inc.
//
// TODO:
//    Flow analysis for Yield.
//    Emit calls to parent object constructor.
//
// Generics note:
//    Current should be defined to return T, and IEnumerator.Current returns object
//

using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;

namespace Mono.CSharp {

	public interface IIteratorContainer {

		//
		// Invoked if a yield statement is found in the body
		//
		void SetYields ();
	}
	
	public class Yield : Statement {
		public Expression expr;

		public Yield (Expression expr, Location l)
		{
			this.expr = expr;
			loc = l;
		}

		public static bool CheckContext (EmitContext ec, Location loc)
		{
			if (ec.CurrentBranching.InFinally (true)){
				Report.Error (-208, loc, "yield statement can not appear in finally clause");
				return false;
			}
			if (ec.CurrentBranching.InCatch ()){
				Report.Error (-209, loc, "yield statement can not appear in the catch clause");
				return false;
			}
			if (ec.InAnonymousMethod){
				Report.Error (-209, loc, "yield statement can not appear inside an anonymoud method");
				return false;
			}

			//
			// FIXME: Missing check for Yield inside try block that contains catch clauses
			//
			return true;
		}
		
		public override bool Resolve (EmitContext ec)
		{
			expr = expr.Resolve (ec);
			if (expr == null)
				return false;
			if (!CheckContext (ec, loc))
				return false;
			
			Type iterator_type = IteratorHandler.Current.IteratorType;
			if (expr.Type != iterator_type){
				expr = Convert.ImplicitConversionRequired (ec, expr, iterator_type, loc);
				if (expr == null)
					return false;
			}
			return true;
		}

		protected override void DoEmit (EmitContext ec)
		{
			IteratorHandler.Current.MarkYield (ec, expr);
		}
	}

	public class YieldBreak : Statement {

		public YieldBreak (Location l)
		{
			loc = l;
		}

		public override bool Resolve (EmitContext ec)
		{
			if (!Yield.CheckContext (ec, loc))
				return false;

			ec.CurrentBranching.CurrentUsageVector.Goto ();
			return true;
		}

		protected override void DoEmit (EmitContext ec)
		{
			IteratorHandler.Current.EmitYieldBreak (ec.ig, true);
		}
	}

	public class IteratorHandler {
		//
		// Points to the current iterator handler, will be probed by
		// Yield and YieldBreak to get their context information
		//
		public static IteratorHandler Current;
		
		//
		// The typebuilder to the proxy class we create
		//
		TypeBuilder enumerator_proxy_class;
		TypeBuilder enumerable_proxy_class;

		//
		// The type of this iterator, object by default.
		//
		public Type IteratorType;
		
		//
		// The members we create on the proxy class
		//
		MethodBuilder move_next_method;
		MethodBuilder reset_method;
		MethodBuilder get_current_method;
		MethodBuilder dispose_method;
		MethodBuilder getenumerator_method;
		PropertyBuilder current_property;
		ConstructorBuilder enumerator_proxy_constructor;
		ConstructorBuilder enumerable_proxy_constructor;

		//
		// The PC for the state machine.
		//
		FieldBuilder pc_field;

		//
		// The value computed for Current
		//
		FieldBuilder current_field;

		//
		// Used to reference fields on the container class (instance methods)
		//
		public FieldBuilder this_field;
		public FieldBuilder enumerable_this_field;

		//
		// References the parameters
		//

		public FieldBuilder [] parameter_fields;
		FieldBuilder [] enumerable_parameter_fields;
		
		//
		// The state as we generate the iterator
		//
		ArrayList resume_labels = new ArrayList ();
		int pc;
		
		//
		// Context from the original method
		//
		string name;
		TypeContainer container;
		Type return_type;
		Type [] param_types;
		InternalParameters parameters;
		Block original_block;
		Location loc;
		int modifiers;

		static int proxy_count;
		string MakeProxyName ()
		{
			return String.Format ("__Proxy_{0}", proxy_count++);
		}

		public void EmitYieldBreak (ILGenerator ig, bool add_return)
		{
			ig.Emit (OpCodes.Ldarg_0);
			IntConstant.EmitInt (ig, -1);
			ig.Emit (OpCodes.Stfld, pc_field);
			if (add_return){
				ig.Emit (OpCodes.Ldc_I4_0);
				ig.Emit (OpCodes.Ret);
			}
		}

		void EmitThrowInvalidOp (ILGenerator ig)
		{
			ig.Emit (OpCodes.Newobj, TypeManager.invalid_operation_ctor);
			ig.Emit (OpCodes.Throw);
		}
		
		void Create_MoveNext ()
		{
			move_next_method = enumerator_proxy_class.DefineMethod (
				"System.IEnumerator.MoveNext",
				MethodAttributes.HideBySig | MethodAttributes.NewSlot |
				MethodAttributes.Virtual,
				CallingConventions.HasThis, TypeManager.bool_type, TypeManager.NoTypes);
			enumerator_proxy_class.DefineMethodOverride (move_next_method, TypeManager.bool_movenext_void);

			ILGenerator ig = move_next_method.GetILGenerator ();
			EmitContext ec = new EmitContext (
				container, loc, ig,
				TypeManager.void_type, modifiers);

			Label dispatcher = ig.DefineLabel ();
			ig.Emit (OpCodes.Br, dispatcher);
			Label entry_point = ig.DefineLabel ();
			ig.MarkLabel (entry_point);
			resume_labels.Add (entry_point);
			
			Current = this;
			SymbolWriter sw = CodeGen.SymbolWriter;
			if ((sw != null) && !Location.IsNull (loc) && !Location.IsNull (original_block.EndLocation)) {
				sw.OpenMethod (container, move_next_method, loc, original_block.EndLocation);

				ec.EmitTopBlock (original_block, parameters, loc);

				sw.CloseMethod ();
			} else {
				ec.EmitTopBlock (original_block, parameters, loc);
			}
			Current = null;

			EmitYieldBreak (ig, true);

			//
			// FIXME: Split the switch in blocks that can be consumed by switch.
			//
			ig.MarkLabel (dispatcher);
			
			Label [] labels = new Label [resume_labels.Count];
			resume_labels.CopyTo (labels);
			ig.Emit (OpCodes.Ldarg_0);
			ig.Emit (OpCodes.Ldfld, pc_field);
			ig.Emit (OpCodes.Switch, labels);
			ig.Emit (OpCodes.Ldc_I4_0); 
			ig.Emit (OpCodes.Ret); 
		}

		// 
		// Invoked when a local variable declaration needs to be mapped to
		// a field in our proxy class
		//
		// Prefixes registered:
		//   v_   for EmitContext.MapVariable
		//   s_   for Storage
		//
		public FieldBuilder MapVariable (string pfx, string name, Type t)
		{
			return enumerator_proxy_class.DefineField (pfx + name, t, FieldAttributes.Public);
		}
		
		void Create_Reset ()
		{
			reset_method = enumerator_proxy_class.DefineMethod (
				"System.IEnumerator.Reset",
				MethodAttributes.HideBySig | MethodAttributes.NewSlot |
				MethodAttributes.Virtual,
				CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
			enumerator_proxy_class.DefineMethodOverride (reset_method, TypeManager.void_reset_void);
			ILGenerator ig = reset_method.GetILGenerator ();
			EmitThrowInvalidOp (ig);
		}

		void Create_Current ()
		{
			get_current_method = enumerator_proxy_class.DefineMethod (
				"System.IEnumerator.get_Current",
				MethodAttributes.HideBySig | MethodAttributes.SpecialName |
				MethodAttributes.NewSlot | MethodAttributes.Virtual,
				CallingConventions.HasThis, TypeManager.object_type, TypeManager.NoTypes);
			enumerator_proxy_class.DefineMethodOverride (get_current_method, TypeManager.object_getcurrent_void);

			current_property = enumerator_proxy_class.DefineProperty (
				"Current",
				PropertyAttributes.RTSpecialName | PropertyAttributes.SpecialName,
				TypeManager.object_type, null);

			current_property.SetGetMethod (get_current_method);
			
			ILGenerator ig = get_current_method.GetILGenerator ();

			ig.Emit (OpCodes.Ldarg_0);
			ig.Emit (OpCodes.Ldfld, pc_field);
			ig.Emit (OpCodes.Ldc_I4_0);
			Label return_current = ig.DefineLabel ();
			ig.Emit (OpCodes.Bgt, return_current);
			EmitThrowInvalidOp (ig);
			
			ig.MarkLabel (return_current);
			ig.Emit (OpCodes.Ldarg_0);
			ig.Emit (OpCodes.Ldfld, current_field);
			ig.Emit (OpCodes.Ret);
		}

		void Create_Dispose ()
		{
			dispose_method = enumerator_proxy_class.DefineMethod (
				"System.IDisposable.Dispose",
				MethodAttributes.HideBySig | MethodAttributes.SpecialName |
				MethodAttributes.NewSlot | MethodAttributes.Virtual,
				CallingConventions.HasThis, TypeManager.void_type, TypeManager.NoTypes);
			enumerator_proxy_class.DefineMethodOverride (dispose_method, TypeManager.void_dispose_void);
			ILGenerator ig = dispose_method.GetILGenerator (); 

			EmitYieldBreak (ig, false);
			ig.Emit (OpCodes.Ret);
		}
		
		void Create_GetEnumerator ()
		{
			getenumerator_method = enumerable_proxy_class.DefineMethod (
				"IEnumerable.GetEnumerator",
				MethodAttributes.HideBySig | MethodAttributes.SpecialName |
				MethodAttributes.NewSlot | MethodAttributes.Virtual,
				CallingConventions.HasThis, TypeManager.ienumerator_type, TypeManager.NoTypes);

			enumerable_proxy_class.DefineMethodOverride  (getenumerator_method, TypeManager.ienumerable_getenumerator_void);
			ILGenerator ig = getenumerator_method.GetILGenerator ();

			if (enumerable_this_field != null){
				ig.Emit (OpCodes.Ldarg_0);
				ig.Emit (OpCodes.Ldfld, enumerable_this_field);
			}
			for (int i = 0; i < parameters.Count; i++){
				ig.Emit (OpCodes.Ldarg_0);
				ig.Emit (OpCodes.Ldfld, enumerable_parameter_fields [i]);
			}
			ig.Emit (OpCodes.Newobj, (ConstructorInfo) enumerator_proxy_constructor);
			ig.Emit (OpCodes.Ret);
		}

		void LoadArgs (ILGenerator ig)
		{
			int count = parameters.Count;
			if ((modifiers & Modifiers.STATIC) == 0)
				count++;

			for (int i = 0; i < count; i++)
				ParameterReference.EmitLdArg (ig, i);
		}
		
		//
		// Called back from Yield
		//
		public void MarkYield (EmitContext ec, Expression expr)
		{
			ILGenerator ig = ec.ig;

			// Store the new current
			ig.Emit (OpCodes.Ldarg_0);
			expr.Emit (ec);
			ig.Emit (OpCodes.Stfld, current_field);

			// increment pc
			pc++;
			ig.Emit (OpCodes.Ldarg_0);
			IntConstant.EmitInt (ig, pc);
			ig.Emit (OpCodes.Stfld, pc_field);
			
			// Return ok.
			ig.Emit (OpCodes.Ldc_I4_1);
			ig.Emit (OpCodes.Ret);
			
			Label resume_point = ig.DefineLabel ();
			ig.MarkLabel (resume_point);
			resume_labels.Add (resume_point);
		}

		void ComputeConstructorTypes (out Type [] constructor_types, out Parameters constructor_parameters)
		{
			bool is_static =  (modifiers & Modifiers.STATIC) != 0;
			
			if (is_static && parameters.Count == 0){
				constructor_types = TypeManager.NoTypes;
				constructor_parameters = Parameters.EmptyReadOnlyParameters;
				return;
			}

			int count = (is_static ? 0 : 1) + parameters.Count;
			constructor_types = new Type [count];
			Parameter [] pars = new Parameter [count];
			constructor_parameters = new Parameters (pars, null, loc);
			
			int i = 0;
			if (!is_static){
				constructor_types [0] = container.TypeBuilder;

				Parameter THIS = new Parameter (
					new TypeExpression (container.TypeBuilder, loc), "this", Parameter.Modifier.NONE, null);
				pars [0] = THIS;
				i++;
			}

			for (int j = 0; j < parameters.Count; j++, i++){
				Type partype = parameters.ParameterType (j);
				
				pars [i] = new Parameter (new TypeExpression (partype, loc),
							  parameters.ParameterName (j),
							  Parameter.Modifier.NONE, null);
				constructor_types [i] = partype;
			}
		}
		
		//
		// Creates the IEnumerator Proxy class
		//
		void MakeEnumeratorProxy ()
		{
			TypeExpr [] proxy_base_interfaces = new TypeExpr [2];
			proxy_base_interfaces [0] = new TypeExpression (TypeManager.ienumerator_type, loc);
			proxy_base_interfaces [1] = new TypeExpression (TypeManager.idisposable_type, loc);
			Type [] proxy_base_itypes = new Type [2];
			proxy_base_itypes [0] = TypeManager.ienumerator_type;
			proxy_base_itypes [1] = TypeManager.idisposable_type;
			TypeBuilder container_builder = container.TypeBuilder;

			//
			// Create the class
			//
			enumerator_proxy_class = container_builder.DefineNestedType (
				MakeProxyName (), TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPublic,
				TypeManager.object_type, proxy_base_itypes);

			TypeManager.RegisterBuilder (enumerator_proxy_class, proxy_base_interfaces);

			//
			// Define our fields
			//
			pc_field = enumerator_proxy_class.DefineField ("PC", TypeManager.int32_type, FieldAttributes.Private);
			current_field = enumerator_proxy_class.DefineField ("current", IteratorType, FieldAttributes.Private);
			if ((modifiers & Modifiers.STATIC) == 0)
				this_field = enumerator_proxy_class.DefineField ("THIS", container.TypeBuilder, FieldAttributes.Private);

			parameter_fields = new FieldBuilder [parameters.Count];
			for (int i = 0; i < parameters.Count; i++){
				parameter_fields [i] = enumerator_proxy_class.DefineField (
					String.Format ("p{0}_{1}", i, parameters.ParameterName (i)),
					parameters.ParameterType (i), FieldAttributes.Private);
			}
			
			//
			// Define a constructor 
			//
			// FIXME: currently its parameterless
			Type [] constructor_types;
			Parameters constructor_parameters;

			ComputeConstructorTypes (out constructor_types, out constructor_parameters);
			
			enumerator_proxy_constructor = enumerator_proxy_class.DefineConstructor (
				MethodAttributes.Public | MethodAttributes.HideBySig |
				MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
				CallingConventions.HasThis, constructor_types);
			InternalParameters parameter_info = new InternalParameters (constructor_types, constructor_parameters);
			TypeManager.RegisterMethod (enumerator_proxy_constructor, parameter_info, constructor_types);

			//
			// Our constructor
			//
			ILGenerator ig = enumerator_proxy_constructor.GetILGenerator ();
			ig.Emit (OpCodes.Ldarg_0);
			ig.Emit (OpCodes.Call, TypeManager.object_ctor);

			int arg_start;
			if (this_field != null){
				arg_start = 2;
				ig.Emit (OpCodes.Ldarg_0);
				ig.Emit (OpCodes.Ldarg_1);
				ig.Emit (OpCodes.Stfld, this_field);
			} else {
				arg_start = 1;
			}
			for (int i = 0; i < parameters.Count; i++){
				ig.Emit (OpCodes.Ldarg_0);
				ParameterReference.EmitLdArg (ig, i + arg_start);
				ig.Emit (OpCodes.Stfld, parameter_fields [i]);
			}
			ig.Emit (OpCodes.Ret);
		}

		//
		// Creates the IEnumerable proxy class
		//
		void MakeEnumerableProxy ()
		{
			TypeBuilder container_builder = container.TypeBuilder;
			Type [] proxy_base_interfaces = new Type [1];
			proxy_base_interfaces [0] = TypeManager.ienumerable_type;

			//
			// Creates the Enumerable proxy class.
			//
			enumerable_proxy_class = container_builder.DefineNestedType (
				MakeProxyName (), TypeAttributes.AutoLayout | TypeAttributes.Class |TypeAttributes.NestedPublic,
				TypeManager.object_type, proxy_base_interfaces);

			//
			// Constructor
			//
			Type [] constructor_types;
			Parameters constructor_parameters;

			ComputeConstructorTypes (out constructor_types, out constructor_parameters);
			if ((modifiers & Modifiers.STATIC) == 0){
				enumerable_this_field = enumerable_proxy_class.DefineField (
					"THIS", container.TypeBuilder, FieldAttributes.Private);
			}
			enumerable_parameter_fields = new FieldBuilder [parameters.Count];
			for (int i = 0; i < parameters.Count; i++){
				enumerable_parameter_fields [i] = enumerable_proxy_class.DefineField (
					String.Format ("p{0}_{1}", i, parameters.ParameterName (i)),
					parameters.ParameterType (i), FieldAttributes.Private);
			}
			
			enumerable_proxy_constructor = enumerable_proxy_class.DefineConstructor (
				MethodAttributes.Public | MethodAttributes.HideBySig |
				MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
				CallingConventions.HasThis, constructor_types);
			InternalParameters parameter_info = new InternalParameters (constructor_types, constructor_parameters);
			TypeManager.RegisterMethod (enumerable_proxy_constructor, parameter_info, constructor_types);
			
			ILGenerator ig = enumerable_proxy_constructor.GetILGenerator ();
			ig.Emit (OpCodes.Ldarg_0);
			ig.Emit (OpCodes.Call, TypeManager.object_ctor);

			int first_arg;
			if (enumerable_this_field != null){
				ig.Emit (OpCodes.Ldarg_0);
				ig.Emit (OpCodes.Ldarg_1);
				ig.Emit (OpCodes.Stfld, enumerable_this_field);
				first_arg = 2;
			} else
				first_arg = 1;
			
			for (int i = 0; i < parameters.Count; i++){
				ig.Emit (OpCodes.Ldarg_0);
				ParameterReference.EmitLdArg (ig, i + first_arg);
				ig.Emit (OpCodes.Stfld, enumerable_parameter_fields [i]);
			}
			ig.Emit (OpCodes.Ret);
		}

		//
		// Populates the Enumerator Proxy class
		//
		void PopulateProxy ()
		{
			RootContext.RegisterHelperClass (enumerator_proxy_class);
			
			Create_MoveNext ();
			Create_Reset ();
			Create_Current ();
			Create_Dispose ();

			if (return_type == TypeManager.ienumerable_type){
				Create_GetEnumerator ();
				RootContext.RegisterHelperClass (enumerable_proxy_class);
			}
		}
		

		//
		// This is invoked by the EmitCode hook
		//
		void SetupIterator ()
		{
			PopulateProxy ();
		}

		//
		// Our constructor
		//
		public IteratorHandler (string name, TypeContainer container, Type return_type, Type [] param_types,
					InternalParameters parameters, int modifiers, Location loc)
		{
			this.name = name;
			this.container = container;
			this.return_type = return_type;
			this.param_types = param_types;
			this.parameters = parameters;
			this.modifiers = modifiers;
			this.loc = loc;

			IteratorType = TypeManager.object_type;
			
			RootContext.EmitCodeHook += new RootContext.Hook (SetupIterator);
		}

		//
		// This class is just an expression that evaluates to a type, and the
		// type is our internal proxy class.  Used in the generated new body
		// of the original method
		//
		class NewInnerType : Expression {
			IteratorHandler handler;
			
			public NewInnerType (IteratorHandler handler, Location l) 
			{
				this.handler = handler;
				eclass = ExprClass.Value;
				loc = l;
			}

			public override Expression DoResolve (EmitContext ec)
			{
				// Create the proxy class type.
				handler.MakeEnumeratorProxy ();

				if (handler.return_type == TypeManager.ienumerable_type)
					handler.MakeEnumerableProxy ();

				type = handler.return_type;
				return this;
			}

			public override Expression ResolveAsTypeStep (EmitContext ec)
			{
				return DoResolve (ec);
			}

			public override void Emit (EmitContext ec)
			{
				handler.LoadArgs (ec.ig);
				
				if (handler.return_type == TypeManager.ienumerable_type)
					ec.ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerable_proxy_constructor);
				else 
					ec.ig.Emit (OpCodes.Newobj, (ConstructorInfo) handler.enumerator_proxy_constructor);
			}
		}

		//
		// This return statement tricks return into not flagging an error for being
		// used in a Yields method
		//
		class NoCheckReturn : Return {
			public NoCheckReturn (Expression expr, Location loc) : base (expr, loc)
			{
			}
			
			public override bool Resolve (EmitContext ec)
			{
				ec.InIterator = false;
				bool ret_val = base.Resolve (ec);
				ec.InIterator = true;

				return ret_val;
			}
		}
		
		//
		// Returns the new block for the method, or null on failure
		//
		public Block Setup (Block block)
		{
			if (return_type != TypeManager.ienumerator_type &&
			    return_type != TypeManager.ienumerable_type){
				Report.Error (
					-205, loc, String.Format (
						"The method `{0}' contains a yield statement, but has an invalid return type for an iterator",
						name));
				return null;
			}

			for (int i = 0; i < parameters.Count; i++){
				Parameter.Modifier mod = parameters.ParameterModifier (i);
				if ((mod & (Parameter.Modifier.REF | Parameter.Modifier.OUT)) != 0){
					Report.Error (-207, loc, String.Format (
							      "Parameter {0} of `{1}' is {2} and not allowed for an iterator method",
							      i+1, name, parameters.ParameterDesc (i)));
					return null;
				}
			}

			original_block = block;
			Block b = new Block (null);

			// return new InnerClass ()
			b.AddStatement (new NoCheckReturn (new NewInnerType (this, loc), loc));
			return b;
		}
	}
}

