#region license
// Copyright (c) 2003, 2004, 2005 Rodrigo B. de Oliveira (rbo@acm.org)
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//     * Neither the name of Rodrigo B. de Oliveira nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion


namespace Boo.Lang.Compiler.Steps
{
	using System;
	using System.Collections;
	using Boo.Lang.Compiler;
	using Boo.Lang.Compiler.Ast;
	using Boo.Lang.Compiler.TypeSystem;

	class GeneratorExpressionProcessor : AbstractCompilerComponent
	{
		GeneratorExpression _generator;
		
		BooClassBuilder _enumerable;
		
		BooClassBuilder _enumerator;
		
		Field _current;
		
		Field _enumeratorField;
		
		ForeignReferenceCollector _collector;
		
		IType _sourceItemType;
		IType _sourceEnumeratorType; 
		IType _sourceEnumerableType;

		IType _resultItemType;
		IType _resultEnumeratorType; 
		
		public GeneratorExpressionProcessor(CompilerContext context,
								ForeignReferenceCollector collector,
								GeneratorExpression node)
		{
			_collector = collector;
			_generator = node;
			Initialize(context);
		}
		
		public void Run()
		{
			RemoveReferencedDeclarations();
			CreateAnonymousGeneratorType();
		}
		
		void RemoveReferencedDeclarations()
		{
			Hash referencedEntities = _collector.ReferencedEntities;
			foreach (Declaration d in _generator.Declarations)
			{
				referencedEntities.Remove(d.Entity);
			}
		}
		
		void CreateAnonymousGeneratorType()
		{
			_enumerable = (BooClassBuilder)_generator["GeneratorClassBuilder"];

			_sourceItemType = TypeSystemServices.ObjectType;
			_sourceEnumeratorType = TypeSystemServices.IEnumeratorType;
			_sourceEnumerableType = TypeSystemServices.IEnumerableType;
			
			_resultItemType = (IType)_generator["GeneratorItemType"];
			_resultEnumeratorType = TypeSystemServices.IEnumeratorType;
			
			//_enumerator = _collector.CreateSkeletonClass(_enumerable.ClassDefinition.Name + "Enumerator");
			_enumerator = _collector.CreateSkeletonClass("Enumerator");
			
#if NET_2_0
			// If source item type isn't object, use a generic enumerator for the source type
			_sourceItemType = TypeSystemServices.GetGenericEnumerableItemType(_generator.Iterator.ExpressionType);
			
			if (_sourceItemType != null && _sourceItemType != TypeSystemServices.ObjectType)
			{
				_sourceEnumerableType = TypeSystemServices.IEnumerableGenericType.GenericTypeDefinitionInfo.MakeGenericType(_sourceItemType);
				_sourceEnumeratorType = TypeSystemServices.IEnumeratorGenericType.GenericTypeDefinitionInfo.MakeGenericType(_sourceItemType);
			}
			else
			{
				_sourceItemType = TypeSystemServices.ObjectType;
			}
			
			// Expose a generic enumerator
			_resultEnumeratorType = TypeSystemServices.IEnumeratorGenericType.GenericTypeDefinitionInfo.MakeGenericType(_resultItemType);
#endif

			_enumerator.AddBaseType(_resultEnumeratorType);
			_enumerator.AddBaseType(TypeSystemServices.Map(typeof(ICloneable)));
			_enumerator.AddBaseType(TypeSystemServices.Map(typeof(IDisposable)));
			
			_enumeratorField = _enumerator.AddField("____enumerator", _sourceEnumeratorType);
			_current = _enumerator.AddField("____current", _resultItemType);
			
			CreateReset();
			CreateCurrent();
			CreateMoveNext();
			CreateClone();
			CreateDispose();
			EnumeratorConstructorMustCallReset();
			
			_collector.AdjustReferences();
			
			_collector.DeclareFieldsAndConstructor(_enumerable);
			
			CreateGetEnumerator();
			_enumerable.ClassDefinition.Members.Add(_enumerator.ClassDefinition);
			//TypeSystemServices.AddCompilerGeneratedType(_enumerator.ClassDefinition);
		}
		
		public MethodInvocationExpression CreateEnumerableConstructorInvocation()
		{
			return _collector.CreateConstructorInvocationWithReferencedEntities(
							_enumerable.Entity);
		}
		
		void EnumeratorConstructorMustCallReset()
		{
			Constructor constructor = _enumerator.ClassDefinition.GetConstructor(0);
			constructor.Body.Add(CreateMethodInvocation(_enumerator.ClassDefinition, "Reset"));
		}
		
		IMethod GetMemberwiseCloneMethod()
		{
			return TypeSystemServices.Map(
						typeof(object).GetMethod("MemberwiseClone",
							System.Reflection.BindingFlags.NonPublic|System.Reflection.BindingFlags.Instance));
		}
		
		MethodInvocationExpression CreateMethodInvocation(ClassDefinition cd, string name)
		{
			IMethod method = (IMethod)((Method)cd.Members[name]).Entity;
			return CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateSelfReference(method.DeclaringType),
						method);
		}
		
		void CreateCurrent()
		{
			Property property = _enumerator.AddReadOnlyProperty("Current", TypeSystemServices.ObjectType);
			property.Getter.Modifiers |= TypeMemberModifiers.Virtual;
			property.Getter.Body.Add(
				new ReturnStatement(
					CodeBuilder.CreateReference(_current)));

#if NET_2_0
			// If item type is object, we're done
			if (_resultItemType == TypeSystemServices.ObjectType) return;
				
			// Since enumerator is generic, this object-typed property should be the 
			// explicit interface implementation for the non-generic IEnumerator interface
			property.ExplicitInfo = new ExplicitMemberInfo();
			property.ExplicitInfo.InterfaceType =
				(SimpleTypeReference)CodeBuilder.CreateTypeReference(TypeSystemServices.IEnumeratorType);
			
			// ...and now we create a typed property for the generic IEnumerator<> interface
			Property typedProperty = _enumerator.AddReadOnlyProperty("Current", _resultItemType);
			typedProperty.Getter.Modifiers |= TypeMemberModifiers.Virtual;
			typedProperty.Getter.Body.Add(
				new ReturnStatement(
					CodeBuilder.CreateReference(_current)));		
#endif
		}
		
		void CreateGetEnumerator()
		{
			BooMethodBuilder method = (BooMethodBuilder)_generator["GetEnumeratorBuilder"];
			
			MethodInvocationExpression mie = CodeBuilder.CreateConstructorInvocation(_enumerator.ClassDefinition);
			foreach (TypeMember member in _enumerable.ClassDefinition.Members)
			{
				if (NodeType.Field == member.NodeType)
				{
					IField field = (IField)member.Entity;
					mie.Arguments.Add(CodeBuilder.CreateMemberReference(field));
				}
			}
			
			method.Body.Add(new ReturnStatement(mie));
		}
		
		void CreateClone()
		{
			BooMethodBuilder method = _enumerator.AddVirtualMethod("Clone", TypeSystemServices.ObjectType);
			method.Body.Add(
				new ReturnStatement(
					CodeBuilder.CreateMethodInvocation(
						CodeBuilder.CreateSelfReference(_enumerator.Entity),
						GetMemberwiseCloneMethod())));
		}
		
		void CreateReset()
		{
			BooMethodBuilder method = _enumerator.AddVirtualMethod("Reset", TypeSystemServices.VoidType);
			method.Body.Add(
				CodeBuilder.CreateAssignment(
					CodeBuilder.CreateReference((InternalField)_enumeratorField.Entity),
					CodeBuilder.CreateMethodInvocation(
						_generator.Iterator, 
						GetMethod(_sourceEnumerableType, "GetEnumerator"))));
		}
		
		void CreateMoveNext()
		{
			BooMethodBuilder method = _enumerator.AddVirtualMethod("MoveNext", TypeSystemServices.BoolType);
			
			Expression moveNext = CodeBuilder.CreateMethodInvocation(
				CodeBuilder.CreateReference((InternalField)_enumeratorField.Entity),
				TypeSystemServices.Map(Types.IEnumerator.GetMethod("MoveNext")));
						
			Expression current = CodeBuilder.CreateMethodInvocation(
				CodeBuilder.CreateReference((InternalField)_enumeratorField.Entity),
				GetProperty(_sourceEnumeratorType, "Current").GetGetMethod());
			
			Statement filter = null;
			Statement stmt = null;
			Block outerBlock = null;
			Block innerBlock = null;
			
			if (null == _generator.Filter)
			{
				IfStatement istmt = new IfStatement(moveNext, new Block(), null);
				outerBlock = innerBlock = istmt.TrueBlock;
				
				stmt = istmt;
			}
			else
			{
				WhileStatement wstmt = new WhileStatement(moveNext);
				outerBlock = wstmt.Block;
				
				if (StatementModifierType.If == _generator.Filter.Type)
				{
					IfStatement ifstmt = new IfStatement(_generator.Filter.Condition, new Block(), null);
					innerBlock = ifstmt.TrueBlock;
					filter = ifstmt;
				}
				else
				{
					UnlessStatement ustmt = new UnlessStatement(_generator.Filter.Condition);
					innerBlock = ustmt.Block;
					filter = ustmt;
				}
				
				stmt = wstmt;
			}
												
			DeclarationCollection declarations = _generator.Declarations;
			if (declarations.Count > 1)
			{
				NormalizeIterationStatements.UnpackExpression(CodeBuilder,
												method.Method,
												outerBlock,
												current,
												declarations);
												
				foreach (Declaration declaration in declarations)
				{
					method.Locals.Add(((InternalLocal)declaration.Entity).Local);
				}
			}
			else
			{
				InternalLocal local = (InternalLocal)declarations[0].Entity;
				method.Locals.Add(local.Local);
				outerBlock.Add(CodeBuilder.CreateAssignment(
								CodeBuilder.CreateReference(local),
								current));
			}
			
			if (null != filter)
			{
				outerBlock.Add(filter);
			}
			
			innerBlock.Add(CodeBuilder.CreateAssignment(
								CodeBuilder.CreateReference((InternalField)_current.Entity),
								_generator.Expression));
			innerBlock.Add(new ReturnStatement(new BoolLiteralExpression(true)));
			
			method.Body.Add(stmt);
			method.Body.Add(new ReturnStatement(new BoolLiteralExpression(false)));
		}
		
		private void CreateDispose()
		{
			BooMethodBuilder dispose = _enumerator.AddVirtualMethod("Dispose", TypeSystemServices.VoidType);
			if (TypeSystemServices.Map(typeof(IDisposable)).IsAssignableFrom(_sourceEnumeratorType))
			{			
				dispose.Body.Add(CodeBuilder.CreateMethodInvocation(
					CodeBuilder.CreateReference(_enumeratorField),
					typeof(IDisposable).GetMethod("Dispose")));
			}
		}

		private IMethod GetMethod(IType type, string name)
		{
			ExternalType external = type as ExternalType;

#if NET_2_0
			MixedGenericType mixed = type as MixedGenericType;
			
			if (mixed != null)
			{
				IMethod definition = GetMethod(mixed.GenericDefinition, name);
				return (IMethod)mixed.MapMember(definition);
			}
#endif

			if (external != null)
			{
				return TypeSystemServices.Map(external.ActualType.GetMethod(name));
			}
			
			else
			{
				throw new ArgumentException(string.Format("Invalid type for GetMethod: {0}", type));
			}
		}	
		
		private IProperty GetProperty(IType type, string name)
		{
			ExternalType external = type as ExternalType;

#if NET_2_0
			MixedGenericType mixed = type as MixedGenericType;
			
			if (mixed != null)
			{
				IProperty definition = GetProperty(mixed.GenericDefinition, name);
				return (IProperty)mixed.MapMember(definition);
			}
#endif

			if (external != null)
			{
				return (IProperty)TypeSystemServices.Map(external.ActualType.GetProperty(name));
			}
			
			else
			{
				throw new ArgumentException(string.Format("Invalid type for GetMethod: {0}", type));
			}
		}
	}
}
