using System;
using System.Collections;
using System.IO;
using System.Reflection;
using DotNetMock.Dynamic;
using DotNetMock.Dynamic.Generate;
using DotNetMock.Dynamic.Predicates;
using NUnit.Framework;
namespace DotNetMock.Tests.Dynamic.Generate
{

	#region Dummy Test Structures
	public interface IThingy
	{
		void NoArgs( );
		void Another( );
		void WithSimpleArg( string s );
		void WithTwoArgs( string a, string b );
		void WithThreeArgs( string a, string b, string c );
		void WithLotsOfArgs( string a, string b, string c, string d, string e, string f );
		void WithOtherArgs( int x, bool y, object o, IList list );
		void WithParams( int i, params string[] extra );
		object simpleReturn( );
		string stringReturn( );
		int intReturn( );
		bool boolReturn( );
		double doubleReturn( );
		IThingy AThingy( );
		string ReadProperty { get; }
		string WriteProperty { set; }
		string AProperty { get; set; }
		void TakesGuid( Guid guid );
		Guid ReturnsGuid( );
	}
	public abstract class SolidThingy
	{
		public virtual string VirtualMethod( )
		{
			return "xx";
		}
		public override string ToString( )
		{
			return "xx";
		}
		public abstract string AbstractMethod( );
		// cannot override
		public static string StaticMethod( )
		{
			return "xx";
		}
		public string NonVirtualMethod( )
		{
			return "xx";
		}
		private string privateMethod( )
		{
			return "xx";
		}
		internal string internalMethod( )
		{
			return "xx";
		}
		protected virtual string protectedMethod( )
		{
			return "xx";
		}
		protected internal virtual string protectedInternalMethod( )
		{
			return "xx";
		}
		private string defaultInternalMethod( )
		{
			return "xx";
		}
	}
	public interface IBaseInterface
	{
		void BaseMethod( );
	}
	public interface ISubInterface : IBaseInterface
	{
		void SubMethod( );
	}
	#endregion

	[TestFixture]
	public class ClassGeneratorTest
	{
		private ClassGenerator cg;
		private IDynamicMock mock;
		private IThingy thingy;
		[SetUp]
		public void SetUp( )
		{
			cg = new ClassGenerator( );
			mock = new DynamicMock( "Test Mock" );
			thingy = ( IThingy )cg.Generate( typeof ( IThingy ), mock );
		}
		private interface IDirectionalParameters
		{
			void DoWithInParameters( int a, string b );
			void DoWithRefParameters( ref int a, ref string b );
			void DoWithOutParameters( out int a, out string b, out TimeSpan c );
		}
		private class DirectionalMockedCallHandler :
			IMockedCallHandler
		{
			public MethodInfo MethodInfo
			{
				get
				{
					return _methodInfo;
				}
			}
			public object Call( MethodInfo mi, params object[] args )
			{
				_methodInfo = mi;
				this.MethodName = mi.Name;
				this.IncomingArgs = ( object[] )args.Clone( );
				if ( this.OutgoingArgs != null )
				{
					this.OutgoingArgs.CopyTo( args, 0 );
				}
				return null;
			}
			public object Call( string methodName, params object[] args )
			{
				return null;
			}
			public object[] IncomingArgs = null;
			public object[] OutgoingArgs = null;
			public string MethodName = null;
			private MethodInfo _methodInfo = null;
		}
		[Test]
			public void SaveGeneratedAssembly()
		{
			string mockAssemblyName = "mockAssembly.dll";
			if ( File.Exists( mockAssemblyName ) )
			{
				File.Delete( mockAssemblyName );
			}
			cg = new ClassGenerator( mockAssemblyName );
			mock = new DynamicMock( "Test Mock" );
			thingy = ( IThingy )cg.Generate( typeof ( IThingy ), mock );

			Assert.IsTrue( File.Exists( mockAssemblyName ) );
			if ( File.Exists( mockAssemblyName ) )
			{
				File.Delete( mockAssemblyName );
			}
		}
		[Test]
		public void NamedParametersAvailableInProxy( )
		{
			DirectionalMockedCallHandler dmch =
				new DirectionalMockedCallHandler( );
			IDirectionalParameters dp = ( IDirectionalParameters )
				cg.Generate( typeof ( IDirectionalParameters ), dmch );
			Type dpt = dp.GetType( );
			MethodInfo mi = dpt.GetMethod( "DoWithInParameters" );
			ParameterInfo[] pis = mi.GetParameters( );
			Assert.AreEqual( 2, pis.Length );
			Assert.AreEqual( typeof ( int ), pis[ 0 ].ParameterType );
			Assert.AreEqual( typeof ( string ), pis[ 1 ].ParameterType );
			Assert.AreEqual( "a", pis[ 0 ].Name );
			Assert.AreEqual( "b", pis[ 1 ].Name );
		}
		[Test]
		public void GeneratePersistentAssembly( )
		{
			if ( File.Exists( "test.dll" ) )
			{
				File.Delete( "test.dll" );
			}
			cg = new ClassGenerator( "test.dll" );
			DirectionalMockedCallHandler dmch =
				new DirectionalMockedCallHandler( );
			cg.Generate( typeof ( IDirectionalParameters ), dmch );
			Assert.IsTrue( File.Exists( "test.dll" ) );
			if ( File.Exists( "test.dll" ) )
			{
				File.Delete( "test.dll" );
			}
		}
		[Test]
		public void TestInParameters( )
		{
			DirectionalMockedCallHandler dmch =
				new DirectionalMockedCallHandler( );
			IDirectionalParameters dp = ( IDirectionalParameters )
				cg.Generate( typeof ( IDirectionalParameters ), dmch );
			dp.DoWithInParameters( 1, "what" );
			Assert.AreEqual( "DoWithInParameters", dmch.MethodInfo.Name );
			Assert.AreEqual( "DoWithInParameters", dmch.MethodName );
			Assert.AreEqual( 2, dmch.IncomingArgs.Length );
			Assert.AreEqual( 1, dmch.IncomingArgs[ 0 ] );
			Assert.AreEqual( "what", dmch.IncomingArgs[ 1 ] );
		}
		[Test]
		public void TestOutParameters( )
		{
			DirectionalMockedCallHandler dmch =
				new DirectionalMockedCallHandler( );
			dmch.OutgoingArgs
				= new object[]
					{
						2,
						"when",
						TimeSpan.FromSeconds( 123 )
					};
			IDirectionalParameters dp = ( IDirectionalParameters )
				cg.Generate( typeof ( IDirectionalParameters ), dmch );
			int a = 1;
			string b = "what";
			TimeSpan c = TimeSpan.FromSeconds( 321 );
			dp.DoWithOutParameters( out a, out b, out c );
			Assert.AreEqual( "DoWithOutParameters", dmch.MethodInfo.Name );
			Assert.AreEqual( "when", b );
			Assert.AreEqual( TimeSpan.FromSeconds( 123 ), c );
			Assert.AreEqual( 2, a );
			Assert.AreEqual( "DoWithOutParameters", dmch.MethodName );
			Assert.AreEqual( 3, dmch.IncomingArgs.Length );
		}
		[Test]
		public void TestRefParameters( )
		{
			DirectionalMockedCallHandler dmch =
				new DirectionalMockedCallHandler( );
			dmch.OutgoingArgs = new object[] {2, "when"};
			IDirectionalParameters dp = ( IDirectionalParameters )
				cg.Generate( typeof ( IDirectionalParameters ), dmch );
			int a = 1;
			string b = "what";
			dp.DoWithRefParameters( ref a, ref b );
			Assert.AreEqual( "DoWithRefParameters", dmch.MethodInfo.Name );
			Assert.AreEqual( "DoWithRefParameters", dmch.MethodName );
			Assert.AreEqual( 2, dmch.IncomingArgs.Length );
			Assert.AreEqual( 1, dmch.IncomingArgs[ 0 ] );
			Assert.AreEqual( "what", dmch.IncomingArgs[ 1 ] );
			Assert.AreEqual( "when", b );
			Assert.AreEqual( 2, a );
		}
		[Test]
		public void CallMethodIsCalled( )
		{
			mock.Expect( "NoArgs" );
			thingy.NoArgs( );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithReturn( )
		{
			object x = "sdfs";
			mock.ExpectAndReturn( "simpleReturn", x );
			object result = thingy.simpleReturn( );
			Assert.AreEqual( x, result );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithReturnAndCast( )
		{
			string x = "sdfs";
			mock.ExpectAndReturn( "stringReturn", x );
			string result = thingy.stringReturn( );
			Assert.AreEqual( x, result );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithWeirdObjectReturn( )
		{
			IThingy t = thingy;
			mock.ExpectAndReturn( "AThingy", t );
			IThingy result = thingy.AThingy( );
			Assert.AreEqual( thingy, result );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithReturnInt( )
		{
			mock.ExpectAndReturn( "intReturn", 7 );
			int result = thingy.intReturn( );
			Assert.AreEqual( 7, result );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithReturnBoxings( )
		{
			mock.ExpectAndReturn( "boolReturn", true );
			mock.ExpectAndReturn( "doubleReturn", 1234567891234E+10 );
			Assert.IsTrue( thingy.boolReturn( ) );
			Assert.AreEqual( 1234567891234E+10, thingy.doubleReturn( ) );
			mock.Verify( );
		}
		[Test]
		[ExpectedException( typeof ( IOException ) )]
		public void CallMethodTheThrowsException( )
		{
			mock.ExpectAndThrow( "boolReturn", new IOException( ) );
			thingy.boolReturn( );
		}
		[Test]
		public void CallMethodWithParamExpectations( )
		{
			mock.Expect( "WithSimpleArg", new IsEqual( "hello" ) );
			thingy.WithSimpleArg( "hello" );
			mock.Verify( );
		}
		[Test]
		[ExpectedException( typeof ( AssertionException ) )]
		public void CallMethodWithParamExpectationsThatFails( )
		{
			mock.Expect( "WithSimpleArg", new IsEqual( "hello" ) );
			thingy.WithSimpleArg( "goodbye" );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithTwoParamExpectations( )
		{
			mock.Expect( "WithTwoArgs", new IsEqual( "hello" ), new IsEqual( "world" ) );
			thingy.WithTwoArgs( "hello", "world" );
			mock.Verify( );
		}
		[Test]
		[ExpectedException( typeof ( AssertionException ) )]
		public void CallMethodWithTwoParamExpectationsThatFails( )
		{
			mock.Expect( "WithTwoArgs", new IsEqual( "hello" ), new IsEqual( "world" ) );
			thingy.WithTwoArgs( "hello", "moon" );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithThreeParamExpectations( )
		{
			mock.Expect( "WithThreeArgs", new IsEqual( "hello" ), new IsEqual( "the" ), new IsEqual( "world" ) );
			thingy.WithThreeArgs( "hello", "the", "world" );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithLoadsOfParamExpectations( )
		{
			mock.Expect( "WithLotsOfArgs", new IsEqual( "hello" ), new IsEqual( "world" ), new IsEqual( "is" ), new IsEqual( "this" ), new IsEqual( "the" ), new IsEqual( "end" ) );
			thingy.WithLotsOfArgs( "hello", "world", "is", "this", "the", "end" );
			mock.Verify( );
		}
		[Test]
		public void CallMethodWithOtherArgs( )
		{
			IList l = new ArrayList( );
			mock.Expect( "WithOtherArgs", new IsEqual( 6 ), new IsEqual( true ), new IsNull( ), new IsEqual( l ) );
			thingy.WithOtherArgs( 6, true, null, l );
			mock.Verify( );
		}
		[Test]
		public void CallReadOnlyProperty( )
		{
			mock.ExpectAndReturn( "ReadProperty", "hello" );
			mock.ExpectAndReturn( "ReadProperty", "world" );
			Assert.AreEqual( "hello", thingy.ReadProperty );
			Assert.AreEqual( "world", thingy.ReadProperty );
			mock.Verify( );
		}
		[Test]
		public void WriteOnlyPropertyExpectations( )
		{
			mock.Expect( "WriteProperty", "hello" );
			mock.Expect( "WriteProperty", "world" );
			thingy.WriteProperty = "hello";
			thingy.WriteProperty = "world";
			mock.Verify( );
		}
		[Test]
		public void ReadAndWriteProperty( )
		{
			mock.Expect( "AProperty", "hello" );
			mock.Expect( "AProperty", "world" );
			mock.ExpectAndReturn( "AProperty", "good" );
			mock.ExpectAndReturn( "AProperty", "bye" );
			thingy.AProperty = "hello";
			thingy.AProperty = "world";
			Assert.AreEqual( "good", thingy.AProperty );
			Assert.AreEqual( "bye", thingy.AProperty );
			mock.Verify( );
		}
		[Test]
		public void ExtendClass( )
		{
			cg = new ClassGenerator( );
			SolidThingy s = ( SolidThingy )cg.Generate( typeof ( SolidThingy ), mock );
			mock.ExpectAndReturn( "VirtualMethod", "hello" );
			mock.ExpectAndReturn( "ToString", "STRING" );
			mock.ExpectAndReturn( "GetHashCode", 123 );
			mock.ExpectAndReturn( "AbstractMethod", "fish" );
			Assert.AreEqual( "hello", s.VirtualMethod( ) );
			Assert.AreEqual( "STRING", s.ToString( ) );
			Assert.AreEqual( 123, s.GetHashCode( ) );
			Assert.AreEqual( "fish", s.AbstractMethod( ) );
			Assert.AreEqual( "xx", s.NonVirtualMethod( ) );
			mock.Verify( );
		}
		[Test]
		public void ExtendedInterface( )
		{
			cg = new ClassGenerator( );
			mock.Expect( "BaseMethod" );
			mock.Expect( "SubMethod" );
			ISubInterface i = ( ISubInterface )cg.Generate( typeof ( ISubInterface ), mock );
			i.BaseMethod( );
			i.SubMethod( );
			mock.Verify( );
		}
		[Test]
		public void ValueTypeParameter( )
		{
			cg = new ClassGenerator( );
			Guid guid = Guid.NewGuid( );
			mock.Expect( "TakesGuid", new IsEqual( guid ) );
			IThingy i = ( IThingy )cg.Generate( typeof ( IThingy ), mock );
			i.TakesGuid( guid );
			mock.Verify( );
		}
		[Test]
		public void ValueTypeReturn( )
		{
			cg = new ClassGenerator( );
			Guid guid = Guid.NewGuid( );
			mock.ExpectAndReturn( "ReturnsGuid", guid );
			IThingy i = ( IThingy )cg.Generate( typeof ( IThingy ), mock );
			Guid returnedGuid = i.ReturnsGuid( );
			mock.Verify( );
			Assert.AreEqual( guid, returnedGuid );
		}
		[Test]
		public void GenerateClassWithSpecifiedMethods( )
		{
			cg = new ClassGenerator( );
			DirectionalMockedCallHandler dmch = new DirectionalMockedCallHandler( );
			MethodSignature ms1 = new MethodSignature( "Method1", typeof ( void ), Type.EmptyTypes );
			MethodSignature ms2 = new MethodSignature( "Method2", typeof ( object ), new Type[] {typeof ( int ), typeof ( object )} );
			MethodSignature[] mss = new MethodSignature[] {ms1, ms2};
			object obj = cg.Generate( "TestType", mss, dmch );
			Type genType = obj.GetType( );
			genType.GetMethod( ms1.MethodName ).Invoke( obj, new object[0] );
			Assert.AreEqual( ms1.MethodName, dmch.MethodName, "Wrong [dmch.MethodName]." );
			Assert.AreEqual( 0, dmch.IncomingArgs.Length, "Wrong [dmch.IncomingArgs.Length]." );
			object testObject = new object( );
			genType.GetMethod( ms2.MethodName ).Invoke( obj, new object[] {5, testObject} );
			Assert.AreEqual( ms2.MethodName, dmch.MethodName, "ms2.MethodName != dmch.MethodName" );
			Assert.AreEqual( 2, dmch.IncomingArgs.Length, "Wrong [dmch.IncomingArgs.Length]." );
			Assert.AreEqual( 5, dmch.IncomingArgs[ 0 ], "Wrong [dmch.IncomingArgs[0]]." );
			Assert.AreEqual( testObject, dmch.IncomingArgs[ 1 ], "Wrong [dmch.IncomingArgs[1]]." );
		}
	}
}