using System;

using NUnit.Framework;

using BLToolkit.Patterns;

namespace Patterns
{
	[TestFixture]
	public class DuckTypingTest
	{
		public interface TestInterface
		{
			int  Method(int value);
			void Method(int v1, out int v2);

			int Prop { get; }

			event EventHandler Event;

			void CallEvent();
		}

		public interface TestInterface2
		{
			int  Method(int value);
			void I2Method(int v1, out int v2);
		}

		public class TestClass
		{
			public static int Field;

			public int Method(int value)
			{
				return value + 2;
			}

			public void Method(int v1, out int v2)
			{
				v2 = v1;
			}

			public void I2Method(int v1, out int v2)
			{
				v2 = v1;
			}

			public int Prop
			{
				get { return 22; }
			}

			public event EventHandler Event;

			public void CallEvent()
			{
				Event(this, EventArgs.Empty);
			}
		}

		[Test]
		public void Test()
		{
			var duck  = DuckTyping.Implement<TestInterface> (new TestClass());
			var same  = DuckTyping.Implement<TestInterface> (duck);
			var duck2 = DuckTyping.Implement<TestInterface2>(same);

			Assert.AreSame(duck, same);

			int value;
			duck.Method(33, out value);

			Assert.AreEqual(33, value);
			Assert.AreEqual(42, duck.Method(40));
			Assert.AreEqual(22, duck.Prop);

			duck.Event += duck_Event;

			duck.CallEvent();

			Assert.AreEqual(55, eventValue);

			duck2.I2Method(33, out value);

			Assert.AreEqual(33, value);
			Assert.AreEqual(42, duck2.Method(40));
		}

		int eventValue;

		void duck_Event(object sender, EventArgs e)
		{
			eventValue = 55;
		}

		public class Child1 : TestClass
		{
			public new int Method(int value)
			{
				return value + 5;
			}
		}

		public class Child2 : TestClass
		{
			public new int Method(int value)
			{
				return value + 10;
			}
		}

		[Test]
		public void BulkTest()
		{
			var ducks  = DuckTyping.Implement<TestInterface,TestClass>(new Child1(), new Child2());

			Assert.IsNotEmpty(ducks);
			Assert.AreEqual(42, ducks[0].Method(40));
			Assert.AreEqual(42, ducks[1].Method(40));
		}

		[Test]
		public void BulkTest2()
		{
			var ducks = DuckTyping.Implement<TestInterface>(new Child1(), new Child2());

			Assert.IsNotEmpty(ducks);
			Assert.AreEqual(45, ducks[0].Method(40));
			Assert.AreEqual(50, ducks[1].Method(40));
		}
		
		[Test]
		public void InheritanceTest()
		{
			var duck1 = DuckTyping.Implement<TestInterface>(new Child1());
			var duck2 = DuckTyping.Implement<TestInterface>(new Child2());

			Assert.AreNotSame(duck1, duck2);
			Assert.AreEqual(45, duck1.Method(40));
			Assert.AreEqual(50, duck2.Method(40));
		}

		[Test]
		public void InheritanceTest2()
		{
			var duck1 = DuckTyping.Implement<TestInterface,TestClass>(new Child1());
			var duck2 = DuckTyping.Implement<TestInterface,TestClass>(new Child2());

			Assert.AreNotSame(duck1, duck2);
			Assert.AreEqual(42, duck1.Method(40));
			Assert.AreEqual(42, duck2.Method(40));
		}

		public class StaticClass
		{
			public static int Method(int value)
			{
				return value + 3;
			}
		}

		[Test]
		public void StaticTest()
		{
			DuckTyping.AllowStaticMembers = true;

			var duck = DuckTyping.Implement<TestInterface,StaticClass>(new StaticClass());

			Assert.AreEqual(43, duck.Method(40));
		}

		public struct TestStruct
		{
			public int Method(int value)
			{
				return value + 3;
			}
		}

		[Test]
		public void StructTest()
		{
			DuckTyping.AllowStaticMembers = true;

			var duck = DuckTyping.Implement<TestInterface>(new TestStruct());

			Assert.AreEqual(43, duck.Method(40));
		}


		public interface GenericInterface<T>
		{
			T Method(T value);
		}

		public class GenericClass<T>
		{
			public T Method(T value)
			{
				return value;
			}

			public void I2Method(int v1, out int v2)
			{
				v2 = v1 + 2;
			}
		}

		[Test]
		public void GenericInterfaceTest()
		{
			var o     = new GenericClass<int>();
			var duck  = DuckTyping.Implement<GenericInterface<int>>(o);
			var duck2 = DuckTyping.Implement<TestInterface2> (o);

			Assert.AreEqual(40, duck .Method(40));
			Assert.AreEqual(40, duck2.Method(40));

			int value;
			duck2.I2Method (33, out value);
			Assert.AreEqual(35, value);
		}

		[Test, ExpectedException(typeof(ArgumentNullException))]
		public void InvalidArgTest1()
		{
			TestInterface o = null;

			var duck1 = DuckTyping.Implement<TestInterface>(o);
			var duck2 = (TestInterface)DuckTyping.Implement(typeof(TestInterface), o);
		}

		interface NonPublicInterface
		{
		}

		[Test, ExpectedException(typeof(ArgumentException))]
		public void InvalidArgTest2()
		{
			var duck  = (NonPublicInterface)DuckTyping.Implement(typeof(NonPublicInterface), new TestClass());
		}

		[Test, ExpectedException(typeof(ArgumentException))]
		public void InvalidArgTest3()
		{
			var duck  = (Child1)DuckTyping.Implement(typeof(Child1), new Child2());
		}

		[Test, ExpectedException(typeof(ArgumentException))]
		public void InvalidArgTest4()
		{
			var duck  = (TestInterface)DuckTyping.Implement(typeof(TestInterface), typeof(TestInterface), new TestClass());
		}

		#region Aggregate

		public interface IAggregatable
		{
			int Method1();
			int Method2();
		}

		public interface IClass1
		{
			int Method1();
			int Method3();
		}

		public interface IClass2
		{
			int Method2();
		}

		public class AggregateClass1: IClass1
		{
			public int Method1() { return 1; }
			public int Method3() { return 3; }
		}

		public class AggregateClass2: IClass2
		{
			public int Method2() { return 2; }
		}

		[Test]
		public void AggregateTest()
		{
			var duck  = DuckTyping.Aggregate<IAggregatable>(new AggregateClass1(), new AggregateClass2());

			Assert.IsNotNull(duck);

			Assert.AreEqual(1, duck.Method1());
			Assert.AreEqual(2, duck.Method2());

			// It is still possible to get access
			// to an interface of an underlying object.
			//
			var cls2 = DuckTyping.Implement<IClass2>(duck);

			Assert.IsNotNull(cls2);
			Assert.AreEqual(2, cls2.Method2());

			// Even to switch from one aggregated object to another
			//
			var cls1 = DuckTyping.Implement<IClass1>(cls2);

			Assert.IsNotNull(cls1);
			Assert.AreEqual(1, cls1.Method1());
			Assert.AreEqual(3, cls1.Method3());
		}

		[Test]
		public void MissedMethodsAggregateTest()
		{
			var duck = DuckTyping.Aggregate<IClass1>(new Version(1,0), Guid.NewGuid());

			Assert.That(duck, Is.Not.Null);

			// Neither System.Guid nor System.Version will ever have Method1.
			//
			Assert.That(duck.Method1(), Is.EqualTo(0));
		}

		#endregion
	}
}
