using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using NUnit.Framework;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

using MonoTests.Helpers;

namespace MonoTests.System.ServiceModel.Dispatcher
{
	[TestFixture]
	public class ChannelDispatcherTest
	{
		[Test]
		public void ConstructorNullBindingName ()
		{
			new ChannelDispatcher (new MyChannelListener (new Uri ("urn:foo")), null);
			new ChannelDispatcher (new MyChannelListener (new Uri ("urn:foo")), null, null);
		}

		[Test]
		public void ServiceThrottle ()
		{
			var cd = new ChannelDispatcher (new MyChannelListener<IReplyChannel> (new Uri ("urn:foo")));
			var st = cd.ServiceThrottle;
			Assert.IsNull (st, "#0");

			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			h.AddServiceEndpoint (typeof (TestContract).FullName, new BasicHttpBinding (), "address");
			h.ChannelDispatchers.Add (cd);
			Assert.IsNull (st, "#1");
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			Assert.IsNull (ed.ChannelDispatcher, "#1-2");
			ed.DispatchRuntime.Type = typeof (TestContract);
			cd.Endpoints.Add (ed);
			Assert.AreEqual (cd, ed.ChannelDispatcher, "#1-3");
			cd.MessageVersion = MessageVersion.Default;

			{
				cd.Open (TimeSpan.FromSeconds (10));
				try {
					Assert.IsNull (st, "#2");
					// so, can't really test actual slot values as it is null.
				} finally {
					cd.Close (TimeSpan.FromSeconds (10));
				}
				return;
			}
		}

		[Test]			
		public void Collection_Add_Remove () {
			Console.WriteLine ("STart test Collection_Add_Remove");
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			h.AddServiceEndpoint (typeof (TestContract).FullName, new BasicHttpBinding (), "address");
			MyChannelDispatcher d = new MyChannelDispatcher (new MyChannelListener (uri));
			h.ChannelDispatchers.Add (d);
			Assert.IsTrue (d.Attached, "#1");
			h.ChannelDispatchers.Remove (d);
			Assert.IsFalse (d.Attached, "#2");
			h.ChannelDispatchers.Insert (0, d);
			Assert.IsTrue (d.Attached, "#3");
			h.ChannelDispatchers.Add (new MyChannelDispatcher (new MyChannelListener (uri)));
			h.ChannelDispatchers.Clear ();
			Assert.IsFalse (d.Attached, "#4");
		}

		[Test]
		public void EndpointDispatcherAddTest ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			MyChannelDispatcher d = new MyChannelDispatcher (new MyChannelListener (uri));
			d.Endpoints.Add (new EndpointDispatcher (new EndpointAddress (uri), "", ""));
		}

		[Test]
		[ExpectedException (typeof (InvalidOperationException))] 
		public void EndpointDispatcherAddTest2 () {
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			MyChannelDispatcher d = new MyChannelDispatcher (new MyChannelListener (uri));
			d.Endpoints.Add (new EndpointDispatcher (new EndpointAddress (uri), "", ""));
			d.Open (); // the dispatcher must be attached.
		}

		[Test]
		[ExpectedException (typeof (InvalidOperationException))]
		public void EndpointDispatcherAddTest3 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			MyChannelDispatcher d = new MyChannelDispatcher (new MyChannelListener (uri));
			d.Endpoints.Add (new EndpointDispatcher (new EndpointAddress (uri), "", ""));
			h.ChannelDispatchers.Add (d);
			d.Open (); // missing MessageVersion
		}

		[Test]
		[ExpectedException (typeof (InvalidOperationException))] // i.e. it is thrown synchronously in current thread.
		public void EndpointDispatcherAddTest4 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			var listener = new MyChannelListener (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			Assert.IsNotNull (ed.DispatchRuntime, "#1");
			Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#2");
			Assert.IsNull (ed.DispatchRuntime.InstanceContextProvider, "#3");
			Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#3.2");
			Assert.IsNull (ed.DispatchRuntime.SingletonInstanceContext, "#4");
			d.Endpoints.Add (ed);
			d.MessageVersion = MessageVersion.Default;
			h.ChannelDispatchers.Add (d);
			// it misses DispatchRuntime.Type, which seems set
			// automatically when the dispatcher is created in
			// ordinal process but need to be set manually in this case.
			try {
				d.Open ();
				try {
					// should not reach here, but in case it didn't, it must be closed.
					d.Close (TimeSpan.FromSeconds (10));
				} catch {
				}
			} finally {
				Assert.AreEqual (CommunicationState.Opened, listener.State, "#5");
			}
		}

		[Test]
		[ExpectedException (typeof (InvalidOperationException))] // i.e. it is thrown synchronously in current thread.
		public void EndpointDispatcherAddTest5 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			var binding = new BasicHttpBinding ();
			var listener = new MyChannelListener (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			d.Endpoints.Add (ed);

			ed.DispatchRuntime.Type = typeof (TestContract); // different from Test4

			d.MessageVersion = MessageVersion.Default;
			h.ChannelDispatchers.Add (d);
			// It rejects "unrecognized type" of the channel listener.
			// Test6 uses IChannelListener<IReplyChannel> and works.
			d.Open ();
			// should not reach here, but in case it didn't, it must be closed.
			d.Close (TimeSpan.FromSeconds (10));
		}

		[Test]
		public void EndpointDispatcherAddTest6 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			var binding = new BasicHttpBinding ();
			var listener = new MyChannelListener<IReplyChannel> (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			d.Endpoints.Add (ed);
			Assert.IsFalse (d.Attached, "#x1");

			ed.DispatchRuntime.Type = typeof (TestContract);

			d.MessageVersion = MessageVersion.Default;
			h.ChannelDispatchers.Add (d);
			Assert.IsTrue (d.Attached, "#x2");
			d.Open (); // At this state, it does *not* call AcceptChannel() yet.
			Assert.IsFalse (listener.AcceptChannelTried, "#1");
			Assert.IsFalse (listener.WaitForChannelTried, "#2");

			Assert.IsNotNull (ed.DispatchRuntime, "#3");
			Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#4");
			Assert.IsNull (ed.DispatchRuntime.InstanceContextProvider, "#5"); // it is not still set after ChannelDispatcher.Open().
			Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#5.2");
			Assert.IsNull (ed.DispatchRuntime.SingletonInstanceContext, "#6");

			d.Close (); // we don't have to even close it.
		}

		[Test]
		[ExpectedException (typeof (InvalidOperationException))]
		public void EndpointDispatcherAddTest7 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			var binding = new BasicHttpBinding ();
			var listener = new MyChannelListener<IReplyChannel> (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			d.Endpoints.Add (ed);

			ed.DispatchRuntime.Type = typeof (TestContract);

			d.MessageVersion = MessageVersion.Default;

			// add service endpoint to open the host (unlike all tests above).
			h.AddServiceEndpoint (typeof (TestContract),
				new BasicHttpBinding (), uri.ToString ());
			h.ChannelDispatchers.Clear ();

			h.ChannelDispatchers.Add (d);
			d.Open (); // At this state, it does *not* call AcceptChannel() yet.

			// This rejects already-opened ChannelDispatcher.
			h.Open (TimeSpan.FromSeconds (10));
			// should not reach here, but in case it didn't, it must be closed.
			h.Close (TimeSpan.FromSeconds (10));
		}

		[Test]
		[Category ("NotWorking")]
		// Validating duplicate listen URI causes this regression.
		// Since it is niche, I rather fixed ServiceHostBase to introduce validation.
		// It is probably because this code doesn't use ServiceEndpoint to build IChannelListener i.e. built without Binding.
		// We can add an extra field to ChannelDispatcher to indicate that it is from ServiceEndpoint (i.e. with Binding),
		// but it makes little sense especially for checking duplicate listen URIs. Duplicate listen URIs should be rejected anyways.
		public void EndpointDispatcherAddTest8 ()
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (typeof (TestContract), uri);
			var listener = new MyChannelListener<IReplyChannel> (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			d.Endpoints.Add (ed);

			ed.DispatchRuntime.Type = typeof (TestContract);

			d.MessageVersion = MessageVersion.Default;

			// add service endpoint to open the host (unlike all tests above).
			h.AddServiceEndpoint (typeof (TestContract),
				new BasicHttpBinding (), uri.ToString ());
			h.ChannelDispatchers.Clear ();

			h.ChannelDispatchers.Add (d);

			Assert.AreEqual (h, d.Host, "#0");

			try {
				h.Open (TimeSpan.FromSeconds (10));
				Assert.AreEqual (3, h.ChannelDispatchers.Count, "#0"); // TestContract, d, mex
				Assert.IsTrue (listener.BeginAcceptChannelTried, "#1"); // while it throws NIE ...
				Assert.IsFalse (listener.WaitForChannelTried, "#2");
				Assert.IsNotNull (ed.DispatchRuntime, "#3");
				Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#4");
				Assert.IsNotNull (ed.DispatchRuntime.InstanceContextProvider, "#5"); // it was set after ServiceHost.Open().
				Assert.IsNull (ed.DispatchRuntime.SingletonInstanceContext, "#6");
				/*
				var l = new HttpListener ();
				l.Prefixes.Add (uri.ToString ());
				l.Start ();
				l.Stop ();
				*/
			} finally {
				h.Close (TimeSpan.FromSeconds (10));
				h.Abort ();
			}
		}

		// FIXME: this test itself indeed passes, but some weird conflict that blocks correspoding port happens between this and somewhere (probably above)
//		[Test]
		public void EndpointDispatcherAddTest9 () // test singleton service
		{
			var uri = new Uri ("http://localhost:" + NetworkHelpers.FindFreePort ());
			ServiceHost h = new ServiceHost (new TestContract (), uri);
			h.Description.Behaviors.Find<ServiceBehaviorAttribute> ().InstanceContextMode = InstanceContextMode.Single;
			var listener = new MyChannelListener<IReplyChannel> (uri);
			MyChannelDispatcher d = new MyChannelDispatcher (listener);
			var ed = new EndpointDispatcher (new EndpointAddress (uri), "", "");
			d.Endpoints.Add (ed);
			ed.DispatchRuntime.Type = typeof (TestContract);
			d.MessageVersion = MessageVersion.Default;
			h.AddServiceEndpoint (typeof (TestContract), new BasicHttpBinding (), uri.ToString ());
			h.ChannelDispatchers.Clear ();
			Assert.IsNull (ed.DispatchRuntime.SingletonInstanceContext, "#1");
			h.ChannelDispatchers.Add (d);
			Assert.IsNull (ed.DispatchRuntime.SingletonInstanceContext, "#2");
			try {
				h.Open (TimeSpan.FromSeconds (10));
				Assert.IsNull (ed.DispatchRuntime.InstanceProvider, "#4");
				Assert.IsNotNull (ed.DispatchRuntime.InstanceContextProvider, "#5"); // it was set after ServiceHost.Open().
				Assert.IsNotNull (ed.DispatchRuntime.SingletonInstanceContext, "#6");
			} finally {
				h.Close (TimeSpan.FromSeconds (10));
				h.Abort ();
			}
		}

		[ServiceContract]
		public class TestContract
		{
			[OperationContract]
			public void Process (string input) {
			}
		}

		class MyChannelDispatcher : ChannelDispatcher
		{
			public bool Attached = false;

			public MyChannelDispatcher (IChannelListener l) : base (l) { }
			protected override void Attach (ServiceHostBase host) {
				base.Attach (host);
				Attached = true;				
			}

			protected override void Detach (ServiceHostBase host) {
				base.Detach (host);
				Attached = false;				
			}
		}

		class MyChannelListener<TChannel> : MyChannelListener, IChannelListener<TChannel> where TChannel : class, IChannel
		{
			public MyChannelListener (Uri uri)
				: base (uri)
			{
			}

			public bool AcceptChannelTried { get; set; }
			public bool BeginAcceptChannelTried { get; set; }

			public TChannel AcceptChannel ()
			{
				AcceptChannelTried = true;
				throw new NotImplementedException ();
			}

			public TChannel AcceptChannel (TimeSpan timeout)
			{
				AcceptChannelTried = true;
				throw new NotImplementedException ();
			}

			public IAsyncResult BeginAcceptChannel (AsyncCallback callback, object state)
			{
				BeginAcceptChannelTried = true;
				throw new NotImplementedException ();
			}

			public IAsyncResult BeginAcceptChannel (TimeSpan timeout, AsyncCallback callback, object state)
			{
				BeginAcceptChannelTried = true;
				throw new NotImplementedException ();
			}

			public TChannel EndAcceptChannel (IAsyncResult result)
			{
				throw new NotImplementedException ();
			}
		}

		class MyChannelListener : IChannelListener
		{
			public MyChannelListener (Uri uri)
			{
				Uri = uri;
			}

			public bool WaitForChannelTried { get; set; }

			public CommunicationState State { get; set; }

			#region IChannelListener Members

			public IAsyncResult BeginWaitForChannel (TimeSpan timeout, AsyncCallback callback, object state)
			{
				WaitForChannelTried = true;
				throw new NotImplementedException ();
			}

			public bool EndWaitForChannel (IAsyncResult result)
			{
				throw new NotImplementedException ();
			}

			public T GetProperty<T> () where T : class
			{
				throw new NotImplementedException ();
			}

			public Uri Uri { get; set; }

			public bool WaitForChannel (TimeSpan timeout)
			{
				WaitForChannelTried = true;
				throw new NotImplementedException ();
			}

			#endregion

			#region ICommunicationObject Members

			public void Abort ()
			{
				State = CommunicationState.Closed;
			}

			public IAsyncResult BeginClose (TimeSpan timeout, AsyncCallback callback, object state) {
				throw new NotImplementedException ();
			}

			public IAsyncResult BeginClose (AsyncCallback callback, object state) {
				throw new NotImplementedException ();
			}

			public IAsyncResult BeginOpen (TimeSpan timeout, AsyncCallback callback, object state) {
				throw new NotImplementedException ();
			}

			public IAsyncResult BeginOpen (AsyncCallback callback, object state) {
				throw new NotImplementedException ();
			}

			public void Close (TimeSpan timeout)
			{
				State = CommunicationState.Closed;
			}

			public void Close ()
			{
				State = CommunicationState.Closed;
			}

			public event EventHandler Closed;

			public event EventHandler Closing;

			public void EndClose (IAsyncResult result) {
				throw new NotImplementedException ();
			}

			public void EndOpen (IAsyncResult result) {
				throw new NotImplementedException ();
			}

			public event EventHandler Faulted;

			public void Open (TimeSpan timeout)
			{
				State = CommunicationState.Opened;
			}

			public void Open () 
			{
				State = CommunicationState.Opened;
			}

			public event EventHandler Opened;

			public event EventHandler Opening;

			#endregion
		}
	}
}