/*
	$Id: NetworkJobServer.cs 80 2010-05-12 07:58:11Z catwalk $
*/
using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.Net;
using System.IO;
using System.ComponentModel;
using Hiyoko.Utilities;

namespace Hiyoko.Net{
	public class NetworkJobServer : DisposableObject{
		private QueueServer<NetworkJob> server = new QueueServer<NetworkJob>();
		private WebRequest currentWebRequest = null;
		private readonly object syncObject = new object();
		public int Timeout{get; set;}
		
		public NetworkJobServer(){
			this.server.ProcessItem += this.ProcessItem;
			this.Timeout = 15000;
		}
		
		public void EnqueueJob(RequireWebRequestDataCallback require, RequestStreamCallback requestStream, NetworkJobCallback response){
			this.EnqueueJob(require, requestStream, response, null);
		}
		
		public void EnqueueJob(RequireWebRequestDataCallback require, RequestStreamCallback requestStream, NetworkJobCallback response, Action<object> @finally){
			this.EnqueueJob(require, requestStream, response, @finally, null);
		}
		
		public void EnqueueJob(RequireWebRequestDataCallback require, RequestStreamCallback request, NetworkJobCallback response, Action<object> @finally, object arg){
			this.server.EnqueueItem(new NetworkJob(require, request, response, @finally, arg));
			this.OnJobEnqueued(EventArgs.Empty);
		}
		
		private void ProcessItem(NetworkJob job){
			try{
				WebRequestData data = null;
				try{
					data = job.Require(job.Argument);
				}catch(Exception ex){
					System.Windows.MessageBox.Show(ex.ToString());
				}
				lock(this.syncObject){
					this.currentWebRequest = data.WebRequest;
				}
				if(this.currentWebRequest != null){
					lock(this.syncObject){
						this.currentWebRequest.Timeout = this.Timeout;
						this.currentWebRequest.Proxy = this.Proxy;
					}
					this.OnJobRunning(EventArgs.Empty);
					
					// RequestStream
					if(job.Request != null){
						ManualResetEvent wait1 = new ManualResetEvent(false);
						data.WebRequest.BeginGetRequestStream(delegate(IAsyncResult async){
							try{
								NetworkJobData jobData = new NetworkJobData(data, async, job.Argument);
								job.Request(jobData);
							}catch(Exception ex){
								Debug.WriteLine(ex.ToString());
							}finally{
								wait1.Set();
							}
						}, null);
						wait1.WaitOne();
					}
					
					// WebResponse
					ManualResetEvent wait2 = new ManualResetEvent(false);
					data.WebRequest.BeginGetResponse(delegate(IAsyncResult async){
						try{
							NetworkJobData jobData = new NetworkJobData(data, async, job.Argument);
							job.Response(jobData);
						}catch(Exception ex){
							Debug.WriteLine(ex.ToString());
						}finally{
							wait2.Set();
						}
					}, null);
					wait2.WaitOne();
				}
				this.OnJobCompleted(EventArgs.Empty);
			}finally{
				lock(this.syncObject){
					this.currentWebRequest = null;
				}
				if(job.Finally != null){
					job.Finally(job.Argument);
				}
			}
		}
		
		public bool AbortJob(){
			lock(this.syncObject){
				if(this.currentWebRequest != null){
					this.currentWebRequest.Abort();
					return true;
				}else{
					return false;
				}
			}
		}
		
		public bool IsBusy{
			get{
				lock(this.syncObject){
					return (this.currentWebRequest != null);
				}
			}
		}
		
		public bool IsBackground{
			get{
				return this.server.IsBackground;
			}
			set{
				this.server.IsBackground = value;
			}
		}
		
		private IWebProxy proxy;
		public IWebProxy Proxy{
			get{
				return this.proxy;
			}
			set{
				this.proxy = value;
			}
		}
		
		private bool disposed = false;
		protected override void Dispose(bool disposing){
			try{
				if(!this.disposed){
					this.server.Dispose();
					this.AbortJob();
				}
			}finally{
				base.Dispose(disposing);
			}
		}
		
		public int JobCount{
			get{
				return this.server.QueueCount;
			}
		}
		
		private class NetworkJob{
			public RequireWebRequestDataCallback Require{get; private set;}
			public RequestStreamCallback Request{get; private set;}
			public NetworkJobCallback Response{get; private set;}
			public Action<object> Finally{get; private set;}
			public object Argument{get; private set;}
			
			public NetworkJob(RequireWebRequestDataCallback require, RequestStreamCallback request, NetworkJobCallback response, Action<object> @finally, object arg){
				if((require == null) || (response == null)){
					throw new ArgumentNullException();
				}
				this.Require = require;
				this.Request = request;
				this.Response = response;
				this.Argument = arg;
				this.Finally = @finally;
			}
		}
		
		protected virtual void OnJobCompleted(EventArgs e){
			if(this.JobCompleted != null){
				this.JobCompleted(this, e);
			}
		}
		public event EventHandler JobCompleted;
		
		protected virtual void OnJobEnqueued(EventArgs e){
			if(this.JobEnqueued != null){
				this.JobEnqueued(this, e);
			}
		}
		public event EventHandler JobEnqueued;
		
		protected virtual void OnJobRunning(EventArgs e){
			if(this.JobRunning != null){
				this.JobRunning(this, e);
			}
		}
		public event EventHandler JobRunning;
	}
	
	public class NetworkJobData{
		public object Argument{get; private set;}
		public WebRequestData WebRequestData{get; private set;}
		public IAsyncResult AsyncResult{get; private set;}
		
		public NetworkJobData(WebRequestData data, IAsyncResult async, object arg){
			this.Argument = arg;
			this.WebRequestData = data;
			this.AsyncResult = async;
		}
		
		public Stream GetRequestStream(){
			return this.WebRequestData.WebRequest.EndGetRequestStream(this.AsyncResult);
		}
		
		public WebResponse GetResponse(){
			return this.WebRequestData.WebRequest.EndGetResponse(this.AsyncResult);
		}
	}
	
	public delegate WebRequestData RequireWebRequestDataCallback(object arg);
	public delegate void RequestStreamCallback(NetworkJobData args);
	public delegate void NetworkJobCallback(NetworkJobData args);
}