// created on 12/18/2004 at 16:28
using System;
using System.Threading;
using System.Diagnostics;

namespace MonoDevelop.Core.Execution
{
	public delegate void ProcessEventHandler(object sender, string message);
	
	public class ProcessWrapper : Process, IProcessAsyncOperation
	{
		private Thread captureOutputThread;
		private Thread captureErrorThread;
		ManualResetEvent endEventOut = new ManualResetEvent (false);
		ManualResetEvent endEventErr = new ManualResetEvent (false);
		bool done;
		object lockObj = new object ();
		
		public ProcessWrapper ()
		{
		}
		
		public new void Start ()
		{
			CheckDisposed ();
			base.Start ();
			
			captureOutputThread = new Thread (new ThreadStart(CaptureOutput));
			captureOutputThread.Name = "Process output reader";
			captureOutputThread.IsBackground = true;
			captureOutputThread.Start ();
			
			if (ErrorStreamChanged != null) {
				captureErrorThread = new Thread (new ThreadStart(CaptureError));
				captureErrorThread.Name = "Process error reader";
				captureErrorThread.IsBackground = true;
				captureErrorThread.Start ();
			} else {
				endEventErr.Set ();
			}
		}
		
		public void WaitForOutput (int milliseconds)
		{
			CheckDisposed ();
			WaitForExit (milliseconds);
			WaitHandle.WaitAll (new WaitHandle[] {endEventOut});
		}
		
		public void WaitForOutput ()
		{
			WaitForOutput (-1);
		}
		
		private void CaptureOutput ()
		{
			try {
				if (OutputStreamChanged != null) {
					char[] buffer = new char [1024];
					int nr;
					while ((nr = StandardOutput.Read (buffer, 0, buffer.Length)) > 0) {
						if (OutputStreamChanged != null)
							OutputStreamChanged (this, new string (buffer, 0, nr));
					}
				}
			} catch (ThreadAbortException) {
				// There is no need to keep propagating the abort exception
				Thread.ResetAbort ();
			} finally {
				// WORKAROUND for "Bug 410743 - wapi leak in System.Diagnostic.Process"
				// Process leaks when an exit event is registered
				if (endEventErr != null)
					WaitHandle.WaitAll (new WaitHandle[] {endEventErr} );

				OnExited (this, EventArgs.Empty);

				//call this AFTER the exit event, or the ProcessWrapper may get disposed and abort this thread
				if (endEventOut != null)
					endEventOut.Set ();
			}
		}
		
		private void CaptureError ()
		{
			try {
				char[] buffer = new char [1024];
				int nr;
				while ((nr = StandardError.Read (buffer, 0, buffer.Length)) > 0) {
					if (ErrorStreamChanged != null)
						ErrorStreamChanged (this, new string (buffer, 0, nr));
				}					
			} finally {
				if (endEventErr != null)
					endEventErr.Set ();
			}
		}
		
		protected override void Dispose (bool disposing)
		{
			lock (lockObj) {
				if (endEventOut == null)
					return;
			}
			
			if (!done)
				((IAsyncOperation)this).Cancel ();
			
			captureOutputThread = captureErrorThread = null;
			endEventOut.Close ();
			endEventErr.Close ();
			endEventOut = endEventErr = null;
			
			base.Dispose (disposing);
		}
		
		void CheckDisposed ()
		{
			if (endEventOut == null)
				throw new ObjectDisposedException ("ProcessWrapper");
		}
		
		int IProcessAsyncOperation.ExitCode {
			get { return ExitCode; }
		}
		
		int IProcessAsyncOperation.ProcessId {
			get { return Id; }
		}
		
		void IAsyncOperation.Cancel ()
		{
			try {
				if (!done) {
					try {
						Kill ();
					} catch {
						// Ignore
					}
				}
			} catch (Exception ex) {
				LoggingService.LogError (ex.ToString ());
			}
		}

		void IAsyncOperation.WaitForCompleted ()
		{
			WaitForOutput ();
		}
		
		void OnExited (object sender, EventArgs args)
		{
			try {
				if (!HasExited)
					WaitForExit ();
			} catch {
				// Ignore
			} finally {
				lock (lockObj) {
					done = true;
					try {
						if (completedEvent != null)
							completedEvent (this);
					} catch {
						// Ignore
					}
				}
			}
		}
		
		event OperationHandler IAsyncOperation.Completed {
			add {
				bool raiseNow = false;
				lock (lockObj) {
					if (done)
						raiseNow = true;
					else
						completedEvent += value;
				}
				if (raiseNow)
					value (this);
			}
			remove {
				lock (lockObj) {
					completedEvent -= value;
				}
			}
		}
	
		bool IAsyncOperation.Success {
			get { return done ? ExitCode == 0 : false; }
		}
		
		bool IAsyncOperation.SuccessWithWarnings {
			get { return false; }
		}
		
		bool IAsyncOperation.IsCompleted {
			get { return done; }
		}
		
		event OperationHandler completedEvent;
		
		public event ProcessEventHandler OutputStreamChanged;
		public event ProcessEventHandler ErrorStreamChanged;
	}
}
