using System;
using System.ComponentModel;
using System.Threading;

/*
    Copyright (c) 2010 flixmonk. (http://DVDSwarm.codeplex.com)
    All Rights Reserved.
        
    Redistribution and use in source and binary forms, with or without modification,
    are permitted provided that the following conditions are met:
        
    * Redistributions of source code must retain the above copyright notice, this list
      of conditions and the following disclaimer.
        
    * Redistributions in binary form must reproduce the above copyright notice, this
    list of conditions and the following disclaimer in the documentation and/or other
    materials provided with the distribution. 
        
    * Neither the name of The Flixmonk nor the names of its contributors may be used to
    endorse or promote products derived from this software without specific prior written
    permission.
        
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
    EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
    SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
    OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace DVDSwarm
{
    /// <summary>
    /// Utilizes a BackgroundWorker to run Nero AAC Encoder in a seperate thread.
    /// </summary>
    sealed public class NeroAacEncoderBackgroundWorker
    {
        #region Fields

        private string _appFilename = string.Empty;
        private string _arguments = string.Empty;
        private Int32 _bitrate = 192;
        private DateTime _lastRaisedDateTime = DateTime.Now;
        private string _outputFilename = string.Empty;
        private System.Diagnostics.Process _process = null;
        private Int32 _progressSeconds = 0;
        private string _sourceFilename = string.Empty;
        private FicSprout.Bacicworx.IO.JobStatus _status = FicSprout.Bacicworx.IO.JobStatus.Inactive;
        private BackgroundWorker _worker = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the CLI arguments.
        /// </summary>
        public string Arguments
        {
            get
            {
                return _arguments;
            }
        }
        /// <summary>
        /// Gets the output filename.
        /// </summary>
        public string OutputFilename
        {
            get
            {
                return _outputFilename;
            }
        }
        /// <summary>
        /// Gets the number of seconds of the audio track which have been encoded.
        /// </summary>
        public Int32 ProgressSeconds
        {
            get
            {
                return _progressSeconds;
            }
        }
        /// <summary>
        /// Gets the source audio filename.
        /// </summary>
        public string SourceFilename
        {
            get
            {
                return _sourceFilename;
            }
        }
        /// <summary>
        /// Gets the status of the worker.
        /// </summary>
        public FicSprout.Bacicworx.IO.JobStatus Status
        {
            get
            {
                return _status;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the NeroAacEncoderBackgroundWorker class to encode the specified source audio.
        /// </summary>
        /// <param name="appFilename">The application path including the filename.</param>
        /// <param name="source">The source filename.</param>
        /// <param name="output">The output filename.</param>
        /// <param name="quality">The EncoderQualityOption for the encoder. Low Compexity (LC) is enabled.</param>
        /// <exception cref="System.ArgumentException">Thrown when appFilename is null or empty.</exception>
        /// <exception cref="System.ArgumentException">Thrown when arguments is null or empty.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when appFilename is not found.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when source is not found.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when output is not found.</exception>
        public NeroAacEncoderBackgroundWorker(string appFilename, string source, string output,
             FicSprout.Bacicworx.Media.EncoderQualityOption quality)
        {
            if (string.IsNullOrEmpty(appFilename))
            {
                throw new ArgumentException("appFilename is null or empty.");
            }
            if (!System.IO.File.Exists(appFilename))
            {
                throw new System.IO.FileNotFoundException("'" + appFilename + "' not found.");
            }
            if (string.IsNullOrEmpty(source))
            {
                throw new ArgumentException("source is null or empty.");
            }
            if (string.IsNullOrEmpty(output))
            {
                throw new ArgumentException("output is null or empty.");
            }

            _appFilename = appFilename;
            _sourceFilename = source;
            _outputFilename = output;
            _bitrate = FicSprout.Snapzworx.Audio.Vorbis.Utility.ComputeVariableBitrate(quality);
            _arguments = "-lc -br " + _bitrate + "000 -if \"" + _sourceFilename + "\" -of \"" + _outputFilename + "\"";
        }

        /// <summary>
        /// Cancels the worker.
        /// </summary>
        public void Cancel()
        {
            if (_worker != null && _process != null)
            {
                _status = FicSprout.Bacicworx.IO.JobStatus.Cancelled;

                _worker.CancelAsync();

                // Unhook events.
                _process.CancelErrorRead();
                _process.ErrorDataReceived -= new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);

                // End process.
                _process.Kill();
                _process.Dispose();
                _process = null;
            }
        }

        /// <summary>
        /// Starts the worker.
        /// </summary>
        public void Start()
        {
            _worker = new BackgroundWorker();
            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
            _worker.RunWorkerAsync();
        }

        #endregion

        #region Published Events

        /// <summary>
        /// Raised when the worker has exited.
        /// </summary>
        public event EventHandler Completed;

        /// <summary>
        /// Raises the OnCompleted event.
        /// </summary>
        public void OnCompleted()
        {
            // Event assigned?
            if (Completed != null && _worker != null)
            {
                // Throttle event if it has been less than 50 milliseconds since we last raised an event.
                if (DateTime.Now.Subtract(_lastRaisedDateTime).Milliseconds < 50)
                {
                    Thread.Sleep(50);
                }

                _lastRaisedDateTime = DateTime.Now;
                Completed(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Raised when the progress value has updated.
        /// </summary>
        public event EventHandler Progress;

        /// <summary>
        /// Raises the OnProgress event.
        /// </summary>
        public void OnProgress()
        {
            // Event assigned?
            if (Progress != null && _worker != null)
            {
                // Throttle event if it has been less than 50 milliseconds since we last raised an event.
                if (DateTime.Now.Subtract(_lastRaisedDateTime).Milliseconds < 50)
                {
                    Thread.Sleep(50);
                }

                _lastRaisedDateTime = DateTime.Now;
                Progress(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Raised when the worker has started.
        /// </summary>
        public event EventHandler Started;

        /// <summary>
        /// Raises the OnStarted event.
        /// </summary>
        public void OnStarted()
        {
            // Event assigned?
            if (Started != null && _worker != null)
            {
                // Throttle event if it has been less than 50 milliseconds since we last raised an event.
                if (DateTime.Now.Subtract(_lastRaisedDateTime).Milliseconds < 50)
                {
                    Thread.Sleep(50);
                }

                _lastRaisedDateTime = DateTime.Now;
                Started(this, EventArgs.Empty);
            }
        }

        #endregion

        #region Subscribed Events

        /// <summary>
        /// Reports the worker progress.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        private void process_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (string.IsNullOrEmpty(e.Data) || _process == null || _status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                return;
            }

            if (e.Data.StartsWith("Processed "))
            {
                // Parse "Processed nnnnnnn seconds..."
                string[] lines = e.Data.Split(new char[] { ' ' });
                if (lines.Length == 3)
                {
                    Int32.TryParse(lines[1], out _progressSeconds);
                }
                OnProgress();
            }
        }

        /// <summary>
        /// Starts the worker.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The event arguments.</param>
        private void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
            _status = FicSprout.Bacicworx.IO.JobStatus.Active;
            OnStarted();

            // Remove output files if they already exist.
            if (System.IO.File.Exists(_outputFilename))
            {
                System.IO.File.Delete(_outputFilename);
            }

            // Start the process.
            _process = new System.Diagnostics.Process();
            _process.StartInfo.CreateNoWindow = true;
            _process.StartInfo.FileName = _appFilename;
            _process.StartInfo.Arguments = _arguments;
            _process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            _process.StartInfo.UseShellExecute = false;
            _process.StartInfo.RedirectStandardError = true;
            _process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(process_ErrorDataReceived);

            _process.Start();
            _process.BeginErrorReadLine();
            _process.WaitForExit();

            if (_status == FicSprout.Bacicworx.IO.JobStatus.Active)
            {
                // Output file not found? Indicate failure.
                if (!System.IO.File.Exists(_outputFilename))
                {
                    _status = FicSprout.Bacicworx.IO.JobStatus.Failed;
                }
                else
                {
                    _status = FicSprout.Bacicworx.IO.JobStatus.Completed;
                }
            }

            OnCompleted();
        }

        #endregion
    }
}
