using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Collections;
using System.ComponentModel;

namespace AutobotManager
{
    /// <summary>
    /// Delegate used by the events StdOutReceived and
    /// StdErrReceived...
    /// </summary>
    public delegate void DataReceivedHandler(object sender,
        DataReceivedEventArgs e);

    /// <summary>
    /// Event Args for above delegate
    /// </summary>
    public class DataReceivedEventArgs : EventArgs
    {
        /// <summary>
        /// The text that was received
        /// </summary>
        public string Text;
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="text">The text that was received for this event to be triggered.</param>
        public DataReceivedEventArgs(string text)
        {
            Text = text;
        }
    }

    /// <summary>
    /// This class can launch a process (like a bat file, perl
    /// script, etc) and return all of the StdOut and StdErr
    /// to GUI app for display in textboxes, etc.
    /// </summary>
	public class ProcessCaller : AsyncOperation
	{

        /// <summary>
        /// The command to run (should be made into a property)
        /// </summary>
        public string FileName;
        /// <summary>
        /// The Arguments for the cmd (should be made into a property)
        /// </summary>
        public string Arguments;

        /// <summary>
        /// The WorkingDirectory (should be made into a property)
        /// </summary>
        public string WorkingDirectory;

        /// <summary>
        /// Fired for every line of stdOut received.
        /// </summary>
        public event DataReceivedHandler StdOutReceived;

        /// <summary>
        /// Fired for every line of stdErr received.
        /// </summary>
        public event DataReceivedHandler StdErrReceived;

        /// <summary>
        /// Amount of time to sleep on threads while waiting
        /// for the process to finish.
        /// </summary>
        public int SleepTime = 500;

        /// <summary>
        /// The process used to run your task
        /// </summary>
        private Process process;

        /// <summary>
        /// The Parent Owner of this Process
        /// </summary>
        public Object m_pParent;

        MethodInvoker m_StdErrReader;
        MethodInvoker m_StdOutReader;
        IAsyncResult  m_StdErrReaderTag;
        IAsyncResult  m_StdOutReaderTag;

        /// <summary>
        /// Initialises a ProcessCaller with an association to the
        /// supplied ISynchronizeInvoke.  All events raised from this
        /// object will be delivered via this target.  (This might be a
        /// Control object, so events would be delivered to that Control's
        /// UI thread.)
        /// </summary>
        /// <param name="isi">An object implementing the
        /// ISynchronizeInvoke interface.  All events will be delivered
        /// through this target, ensuring that they are delivered to the
        /// correct thread.</param>
        public ProcessCaller(ISynchronizeInvoke isi)
            : base(isi)
        {
        }

// This constructor only works with changes to AsyncOperation...
//        /// <summary>
//        /// Initialises a ProcessCaller without an association to an
//        /// ISynchronizeInvoke.  All events raised from this object
//        /// will be delievered via the worker thread.
//        /// </summary>
//        public ProcessCaller()
//        {
//        }

        /// <summary>
        /// Launch a process, but do not return until the process has exited.
        /// That way we can kill the process if a cancel is requested.
        /// </summary>
        protected override void DoWork()
        {
            bool bWaitForExitCalled = false;
            StartProcess();

            // Wait for the process to end, or cancel it
            while (! process.HasExited)
            {
                Thread.Sleep(SleepTime); // sleep
                if (CancelRequested)
                {
                    // Not a very nice way to end a process,
                    // but effective.
                    try
                    {
                        process.Kill();
                    }
                    finally
                    {
                        BlockFlushConsole();
                        //process.WaitForExit();
                        bWaitForExitCalled = true;
                        AcknowledgeCancel();
                    }
                }
            }

            if (!bWaitForExitCalled)
            {
                BlockFlushConsole();
                //process.WaitForExit();
            }
        }

        /// <summary>
        /// This method is generally called by DoWork()
        /// which is called by the base classs Start()
        /// </summary>
        protected virtual void StartProcess()
        {
            // Start a new process for the cmd
            process = new Process();
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.CreateNoWindow = true;
            process.StartInfo.FileName = FileName;
            process.StartInfo.Arguments = Arguments;
            process.StartInfo.WorkingDirectory = WorkingDirectory;
            process.Start();

            m_StdErrReader = new MethodInvoker(ReadStdErr);
            m_StdOutReader = new MethodInvoker(ReadStdOut);
            
            // Invoke stdOut and stdErr readers - each
            // has its own thread to guarantee that they aren't
            // blocked by, or cause a block to, the actual
            // process running (or the gui).
           m_StdErrReaderTag = m_StdErrReader.BeginInvoke(null, null);
           m_StdOutReaderTag = m_StdOutReader.BeginInvoke(null, null);
        }

        /// <summary>
        /// Handles reading of stdout and firing an event for
        /// every line read
        /// </summary>
        protected virtual void ReadStdOut()
        {
            string str;
            while ((str = process.StandardOutput.ReadLine()) != null)
            {
                FireAsync(StdOutReceived, this, new DataReceivedEventArgs(str));
            }
        }

        /// <summary>
        /// Handles reading of stdErr
        /// </summary>
        protected virtual void ReadStdErr()
        {
            string str;
            while ((str = process.StandardError.ReadLine()) != null)
            {
                FireAsync(StdErrReceived, this, new DataReceivedEventArgs(str));
            }
        }

        /// <summary>
        /// Consume everything from the streams before shut down
        /// </summary>
        protected override void BlockFlushConsole()
        {
            if (null != m_StdErrReader)
            {
                m_StdErrReader.EndInvoke(m_StdErrReaderTag);
            }
            if (null != m_StdOutReader)
            {
                m_StdOutReader.EndInvoke(m_StdOutReaderTag);
            }
        }

	}
}
