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

using NUnit.Framework;

using DotNetMock.Dynamic;
using DotNetMock.Dynamic.Predicates;
#endregion

namespace DotNetMock.Tests.Dynamic
{
	[TestFixture]
	public class ExpectationMethodTests
	{
		private ExpectationMethod methodCallExpectation = null;

		private interface IMethods 
		{
			void Method1();
			void Method2(int x, int y, int z);
			void Method3();
			void Method4(int x);
			void Method4(int x, int y);
		}

		static readonly MethodInfo method1 = typeof(IMethods).GetMethod("Method1");
		static readonly MethodInfo method2 = typeof(IMethods).GetMethod("Method2");
		static readonly MethodInfo method3 = typeof(IMethods).GetMethod("Method3");
		static readonly MethodInfo method4x = typeof(IMethods).GetMethod(
			"Method4",
			new Type[] { typeof(int) }
			);
		static readonly MethodInfo method4xy = typeof(IMethods).GetMethod(
			"Method4",
			new Type[] { typeof(int), typeof(int) }
			);

		[ExpectedException(
			 typeof(AssertionException),
			 "Expected 2 arguments but received 1 in method call Method4(x=1)."
			 )]
		[Test] public void CallMethodWithSameNameButFewerArguments()
		{
			methodCallExpectation = new ExpectationMethod(
				method4xy.Name,
				null,
				new object[] { 1, 2 }
				);
			MethodCall call = new MethodCall(method4x, 1);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[ExpectedException(
			 typeof(AssertionException),
			 "Expected 1 arguments but received 2 in method call Method4(x=1, y=2)."
			 )]
		[Test] public void CallMethodWithSameNameButMoreArguments()
		{
			methodCallExpectation = new ExpectationMethod(
				method4x.Name,
				null,
				new object[] { 1 }
				);
			MethodCall call = new MethodCall(method4xy, 1, 2);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		public void ExpectationMethodVoid()
		{
			methodCallExpectation = new ExpectationMethod(method1.Name);
			MethodCall call = new MethodCall(method1);
			object returnValue =
				methodCallExpectation.CheckCallAndSendResponse(call);
			Assert.IsNull(returnValue);
		}
		[Test]
		[ExpectedException(typeof(AssertionException))]
		public void ExpectationMethodVoidNoCall()
		{
			methodCallExpectation = new ExpectationMethod(method1.Name);
			methodCallExpectation.Verify();
		}
		[Test]
		public void ExpectedMethodReturnValue()
		{
			methodCallExpectation = new ExpectationMethod(method1.Name, true );
			MethodCall call = new MethodCall(method1);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			Assert.IsTrue((bool) returnValue);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(
			 typeof(AssertionException),
			 "Failed to satisfy '(value is a System.Double) and (value is within 0.1 of 1)' on argument[0] of method call Method2(x=3, y=2, z=1)"
			 )]
		public void ErrorMessageWhenComplexPredicateFails() 
		{
			IPredicate complexPredicate = new AndPredicate(
				new IsTypeOf(typeof(double)),
				new IsCloseTo(1, 0.1)
				);
			methodCallExpectation = new ExpectationMethod(
				method2.Name,
				null,
				new object[] { complexPredicate, 2, 3 }
				);
			MethodCall call = new MethodCall(method2, 3, 2, 1);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(
			 typeof(AssertionException),
			 "Failed to satisfy 'value equals 1' on argument[0] of method call Method2(x=3, y=2, z=1)"
			 )]
		public void DecentErrorMessageWhenPredicateFails() 
		{
			methodCallExpectation = new ExpectationMethod(method2.Name, null, new object[] { 1, 2, 3 });
			MethodCall call = new MethodCall(method2, 3, 2, 1);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(
			 typeof(AssertionException),
			 "Failed to satisfy 'value equals 2' on argument[1] of method call Method2(x=1, y=3, z=2)"
			 )]
		public void DecentErrorMessageWhenPredicateFailsOnSecondArgument() 
		{
			methodCallExpectation = new ExpectationMethod(method2.Name, null, new object[] { 1, 2, 3 });
			MethodCall call = new MethodCall(method2, 1, 3, 2);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(typeof(InvalidOperationException))]
		public void AccessingActualMethodPropertyBeforeSetting() 
		{
			methodCallExpectation = new ExpectationMethod(method1.Name);
			MethodInfo mi = methodCallExpectation.ActualMethod;
		}
		[Test]
		[ExpectedException(typeof(AssertionException), "Method1() expected, but Method3() called.")]
		public void WrongMethodCalled()
		{
			methodCallExpectation = new ExpectationMethod(method1.Name);
			MethodCall call = new MethodCall(method3);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(
			 typeof(InvalidOperationException),
			 "Method Method2 takes 3 arguments but received 2."
			 )]
		public void ArgumentCountontMatchMethodSignature() 
		{
			methodCallExpectation = new ExpectationMethod(method2.Name, null, new object[] { 1, 2, 3 });
			MethodCall call = new MethodCall(method2, 1, 3);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
		[Test]
		[ExpectedException(
			 typeof(AssertionException),
			 "Expected 4 arguments but received 3 in method call Method2(x=1, y=2, z=3)."
			 )]
		public void MoreExpectationsThanArguments() 
		{
			methodCallExpectation = new ExpectationMethod(
				method2.Name,
				null,
				new object[] { 1, 2, 3, 4 }
				);
			MethodCall call = new MethodCall(method2, 1, 2, 3);
			object returnValue = methodCallExpectation.CheckCallAndSendResponse(call);
			methodCallExpectation.Verify();
		}
	}
}
