#region License
// Copyright (c) 2004 Griffin Caprio & Choy Rim. All rights reserved.
#endregion
#region Imports
using System;
using System.Reflection;
using DotNetMock.Dynamic;
using NUnit.Framework;

using DotNetMock.Dynamic.Predicates;
#endregion

namespace DotNetMock.Tests.Dynamic
{
	[TestFixture]
	public abstract class AbstractDynamicMockTests
	{
		protected IDynamicMock mock;
		protected MethodInfo myMethod;
		protected MethodInfo myMethod1;
		protected MethodInfo myMethod2;
		protected MethodInfo myMethod3;
		protected MethodInfo anotherMethod;
		protected MethodInfo method0;
		protected MethodInfo method1;

		protected static readonly MethodInfo METHOD2A = typeof(IBlah).GetMethod(
			"Method2",
			new Type[] { typeof(int) }
			);
		protected static readonly MethodInfo METHOD2AB = typeof(IBlah).GetMethod(
			"Method2",
			new Type[] { typeof(int), typeof(int) }
			);


		/// <summary>
		/// Create instance of <see cref="IDynamicMock"/> to test.
		/// </summary>
		protected abstract IDynamicMock newDynamicMock(Type type, string name);
		protected abstract IDynamicMock newDynamicMock(Type type);
		protected abstract IDynamicMock newDynamicMock(string name);

		protected interface IBlah 
		{
			object DoStuff(string name);
			object DoStuffWithParams( string id, params string[] names );
			bool DoStuffBooleanStyle( bool flag );
			void MethodWithOutParameter(out string p1);
			void MethodWithRefParameter(ref string p1);

			void myMethod();
			void myMethod1();
			void myMethod2();
			void myMethod3();
			void anotherMethod();
			void Method0(object p0, object p1);
			object Method1(object p0);

			void Method2(int a);
			void Method2(int a, int b);
		}

		[SetUp] public void BeforeEachTest() 
		{
			mock = newDynamicMock("mymock");
			myMethod = typeof(IBlah).GetMethod("myMethod", new Type[0]);
			myMethod1 = typeof(IBlah).GetMethod("myMethod1", new Type[0]);
			myMethod2 = typeof(IBlah).GetMethod("myMethod2", new Type[0]);
			myMethod3 = typeof(IBlah).GetMethod("myMethod3", new Type[0]);
			anotherMethod = typeof(IBlah).GetMethod("anotherMethod", new Type[0]);
			method0 = typeof(IBlah).GetMethod("Method0");
			method1 = typeof(IBlah).GetMethod("Method1");
		}

		[ExpectedException(typeof(AssertionException))]
		[Test] public void FailsIfPassedTooManyArguments() 
		{
			mock = new DynamicMock(typeof(IBlah));
			mock.Expect("Method2", 1);

			IBlah blah = (IBlah) mock.Object;
			blah.Method2(1, 2);
		}

		[Test]
		public void SetsRefParameter()
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah));
			IBlah blah = (IBlah) mock.Object;
			string p1 = "hello";
			mock.Expect("MethodWithRefParameter",
				new Assign("goodbye").AndRequire(new IsEqual("hello"))
				);

			blah.MethodWithRefParameter(ref p1);
			Assert.AreEqual("goodbye", p1);
			mock.Verify();
		}

		[Test]
		public void SetsOutParameter()
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah));
			IBlah blah = (IBlah) mock.Object;
			string p1 = null;
			mock.Expect("MethodWithOutParameter",
				new Assign("hello")
				);

			blah.MethodWithOutParameter(out p1);
			Assert.AreEqual("hello", p1);
			mock.Verify();
		}

		[Test]
		public void DefaultDynamicMock()
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah));
			Assert.AreEqual("MockBlah", mock.MockName);
			
			mock.ExpectAndReturn("DoStuff", "world", "hello");
			
			IBlah blah = (IBlah)mock.Object;
			Assert.AreEqual("world", blah.DoStuff("hello"));
			
			mock.Verify();
		}

		[Test]		
		public void NamedDynamicMock()
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah), "XBlah");
			Assert.AreEqual("XBlah", mock.MockName);
			mock.Verify();
		}

		[Test]
		public void CustomDynamicMockWithBool()
		{
			IDynamicMock mock = newDynamicMock( typeof(IBlah), "XBlah" );
			mock.ExpectAndReturn( "DoStuffBooleanStyle", false, true );
			IBlah blah = (IBlah) mock.Object;
			Assert.AreEqual( false, blah.DoStuffBooleanStyle( true ) );
			mock.Verify();
		}
		[Test]
		public void DynamicMockWithParams() 
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah));
			mock.ExpectAndReturn( "DoStuffWithParams", "world2", "12345", new string[] {"hello" } );

			IBlah blah = (IBlah)mock.Object;

			Assert.AreEqual( "world2", blah.DoStuffWithParams("12345", "hello") );
			mock.Verify();

		}
		[Test]
		public void DynamicMockWithManyParams() 
		{
			IDynamicMock mock = newDynamicMock(typeof(IBlah));
			mock.ExpectAndReturn( "DoStuffWithParams", "world2", "12345", new string[] { "hello", "dotnetmock" } );

			IBlah blah = (IBlah)mock.Object;

			Assert.AreEqual( "world2", blah.DoStuffWithParams("12345", "hello", "dotnetmock") );
			mock.Verify();
		}
		[Test]
		public void Name() 
		{
			Assert.AreEqual("mymock", mock.MockName);
		}
		
		[Test]
		public void NewMockVerifies() 
		{
			mock.Verify();
		}

		[Test]
		public void CallToVoidMethod() 
		{
			mock.Expect("myMethod");
			mock.Call(myMethod);
			mock.Verify();
		}
		[Test]
		[ExpectedException(typeof(AssertionException))]
		public void NoCallToVoidMethod() 
		{
			mock.Expect("myMethod");
			mock.Verify();
		}
		[Test]
		[ExpectedException(typeof(AssertionException))]
		public void TooManyCallsToVoidMethod() 
		{
			mock.Expect("myMethod");
			mock.Call(myMethod);
			mock.Call(myMethod);
		}

		[Test]
		public void ManyCallsToVoidMethod() 
		{
			mock.Expect("myMethod");
			mock.Expect("myMethod");
			mock.Expect("myMethod");
			mock.Call(myMethod);
			mock.Call(myMethod);
			mock.Call(myMethod);
			mock.Verify();
		}

		[Test]
		public void CallToMethodWithParams() 
		{
			mock.Expect("Method0", new IsEqual("hello"), new IsAnything());
			mock.Call(method0, "hello", null);
			mock.Verify();
		}
		[Test]
		[ExpectedException(typeof(DotNetMock.AssertionException))]
		public void CallToMethodWithInvalidParams() 
		{
			mock.Expect("Method0", new IsEqual("hello"), new IsAnything());
			mock.Call(method0, "world", null);
		}

		[Test]
		public void CallToMethodWithParamsButNotCheckingValues() 
		{
			mock.Expect("Method0");
			mock.Call(method0, "world", null);
			mock.Verify();
		}

		[Test]
		public void CallMultipleMethods() 
		{
			mock.Expect("myMethod1");
			mock.Expect("myMethod2");
			mock.Expect("myMethod3");
			mock.Call(myMethod1);
			mock.Call(myMethod2);
			mock.Call(myMethod3);
			mock.Verify();
		}

		[Test]
		public void CallToNonVoidMethod() 
		{
			object something = new object();
			mock.ExpectAndReturn("myMethod", something);
			object result = mock.Call(myMethod);
			mock.Verify();
			Assert.AreEqual(something, result);
		}

		[Test]
		public void CallToNonVoidMethodWithParams() 
		{
			object something = new object();
			mock.ExpectAndReturn("Method1", something, new IsEqual("hello"));
			object result = mock.Call(method1, "hello");
			mock.Verify();
			Assert.AreEqual(something, result);
		}

		[Test]
		[ExpectedException(typeof(AssertionException))]
		public void CallToNonVoidMethodWithWrongParams() 
		{
			object something = new object();
			mock.ExpectAndReturn("Method1", something, new IsEqual("hello"));
			object result = mock.Call(method1, "bye");
			mock.Verify();
			Assert.AreEqual(something, result);
		}

		[Test]
		public void MultipleCallToNonVoidMethod() 
		{
			object something = new object();
			object anotherthing = new object();
			int x = 3;
			mock.ExpectAndReturn("myMethod", something);
			mock.ExpectAndReturn("myMethod", anotherthing);
			mock.ExpectAndReturn("myMethod", x);
			Assert.AreEqual(something, mock.Call(myMethod));
			Assert.AreEqual(anotherthing, mock.Call(myMethod));
			Assert.AreEqual(x, mock.Call(myMethod));
			mock.Verify();
		}

		[Test]
		public void CallToNonVoidMethodReturningNull() 
		{
			mock.ExpectAndReturn("myMethod", null);
			Assert.IsNull(mock.Call(myMethod));
			mock.Verify();
		}

		[Test]
		public void DefaultEqualsPredicates() 
		{
			object o = new object();
			mock.Expect("Method1", o);
			mock.Call(method1, o);
		}

		[Test]
		[ExpectedException(typeof(AssertionException))]
		public void DefaultEqualsPredicatesFailure() 
		{
			mock.Expect("Method1", new object());
			mock.Call(method1, new object());
		}

		[Test]
		public void DefaultAnythingPredicates() 
		{
			mock.Expect("Method0", null, null);
			mock.Expect("Method0", null, "zzz");
			mock.Expect("Method0", "zzz", null);
			mock.Call(method0, "???", "???");
			mock.Call(method0, "???", "zzz");
			mock.Call(method0, "zzz", "???");
		}

		[Test]
		public void FixedValue() 
		{
			mock.SetValue("myMethod", "hello");
			mock.SetValue("anotherMethod", "world");
			Assert.AreEqual("hello", mock.Call(myMethod));
			Assert.AreEqual("hello", mock.Call(myMethod));
			Assert.AreEqual("hello", mock.Call(myMethod));
			Assert.AreEqual("hello", mock.Call(myMethod));
			Assert.AreEqual("world", mock.Call(anotherMethod));
			Assert.AreEqual("world", mock.Call(anotherMethod));
			mock.SetValue("myMethod", "bye");
			Assert.AreEqual("bye", mock.Call(myMethod));
			Assert.AreEqual("bye", mock.Call(myMethod));
			Assert.AreEqual("world", mock.Call(anotherMethod));
			mock.SetValue("myMethod", null);
			Assert.IsNull(mock.Call(myMethod));
			Assert.IsNull(mock.Call(myMethod));
			mock.Verify();
		}
		
		[Test]
		[ExpectedException(typeof(System.IO.IOException))]
		public void CallThrowingException()
		{
			mock.ExpectAndThrow("myMethod", new System.IO.IOException());
			mock.Call(myMethod);
		}
	}
}
