using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;

/*
    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>
    /// Form1.
    /// </summary>
    public partial class Form1 : Form
    {
        #region Fields

        private ConfigurationInfo _config = new ConfigurationInfo();
        private bool _cancelAfterCurrentJobFinishes = false;
        private bool _cancelledWhileThrottled = false;
        private string _configFilename = Application.StartupPath + "\\config.xml";
        private Int32 _currentAudioTrackNumber = -1; // Required data for seperate thread
        private Int32 _currentJobIndex = -1;
        private DgindexBackgroundWorker _dgindex = null;
        private const string _fileExtension_DvdSwarmLog = "DVDSwarmLog.txt";
        private FicSprout.Bacicworx.Media.Flac.FlacEncoderBackgroundWorker _flacEncoder = null;
        private List<JobInfo> _jobs = new List<JobInfo>();
        private FicSprout.Bacicworx.IO.LogWriter _log = null;
        private string _logStandardHeader = "DVD Swarm (" + Assembly.GetExecutingAssembly().GetName().Version.ToString() +
            ") Log\r\n\r\n";
        private FicSprout.Bacicworx.Media.Matroska.MkvmergeBackgroundWorker _mkvmerge = null;
        private Mp4boxBackgroundWorker _mp4box = null;
        private NeroAacEncoderBackgroundWorker _neroAacEncoder = null;
        private FicSprout.Bacicworx.Media.Vorbis.OggEncoderBackgroundWorker _oggEncoder = null;
        private JobRunMode _jobRunMode = JobRunMode.SingleJobRun;
        private bool _rerunX264 = false;
        private WaviBackgroundWorker _wavi = null;
        private FicSprout.Bacicworx.Media.X264.X264BackgroundWorker _x264 = null;
        private string _projectWebsite = "http://www.codeplex.com/DVDSwarm";
        private string[] _requiredTools = new string[] { "AutoCrop.dll", "Avisynth_258.exe", "DGDecode.dll",
            "DGIndex.exe", "flac.exe", "in_flac.dll", "js32.dll", "libFLAC_dynamic.dll", "libogg.dll",
            "libvorbis.dll", "mkvmerge.exe", "MP4Box.exe", "NeroAacEnc.exe", "NicAudio.dll", "oggenc.exe",
            "PgcDemux.exe", "TDeint.dll", "TIVTC.dll", "vinverse.dll", "wavi.exe", "x264.exe" };
        private string _workingDirectory = Application.StartupPath;

        #endregion

        #region Methods

        /// <summary>
        /// Constructor.
        /// </summary>
        public Form1()
        {
            InitializeComponent();

            // Paths.
            if (!System.IO.File.Exists(_configFilename))
            {
                System.IO.File.WriteAllText(_configFilename, Serialize(), System.Text.Encoding.UTF8);
            }

            // Load configuration data.
            Deserialize(System.IO.File.ReadAllText(_configFilename));

            // Update UI.
            UpdateMenus();
        }

        /// <summary>
        /// Adds a job to the queue.
        /// </summary>
        private void AddJob()
        {
            JobDialog dialog = new JobDialog(_config);
            dialog.Config = _config;

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                // Duplicate?
                bool isDuplicate = false;
                for (int i = 0; i < listViewJobs.Items.Count; i++)
                {
                    if (listViewJobs.Items[i].SubItems[0].Text == dialog.Job.SourceFilename)
                    {
                        if (MessageBox.Show("The video source is already in the jobs list.\n\nAdd it anyway?",
                            "Add Job", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.No)
                        {
                            isDuplicate = true;
                            break;
                        }
                    }
                }

                if (!isDuplicate)
                {
                    JobInfo job = new JobInfo();
                    job.Copy(dialog.Job);
                    _jobs.Add(job);
                    _workingDirectory = System.IO.Path.GetDirectoryName(dialog.Job.SourceFilename);

                    ListViewItem item = new ListViewItem(new string[] { dialog.Job.SourceFilename,
                        dialog.Job.Status.ToString(), "", "" });
                    listViewJobs.Items.Add(item);
                    listViewJobs.SelectedIndices.Clear();
                    listViewJobs.SelectedIndices.Add(listViewJobs.Items.Count - 1);

                    SaveConfig();
                }
            }

            dialog.Close();
            dialog.Dispose();
            dialog = null;
        }

        /// <summary>
        /// Cancels a job run after the current job finishes.
        /// </summary>
        private void CancelAfterCurrentJobFinishes()
        {
            _cancelAfterCurrentJobFinishes = !_cancelAfterCurrentJobFinishes;
            cancelAfterCurrentJobContextMenuItem.Checked = _cancelAfterCurrentJobFinishes;
            cancelAfterCurrentJobToolStripMenuItem.Checked = _cancelAfterCurrentJobFinishes;
        }

        /// <summary>
        /// Immediately cancels a job run.
        /// </summary>
        private void CancelJobRun()
        {
            // Terminates any running worker.
            if (_dgindex != null)
            {
                _dgindex.Cancel();
            }
            if (_flacEncoder != null)
            {
                _flacEncoder.Cancel();
            }
            if (_mkvmerge != null)
            {
                _mkvmerge.Cancel();
            }
            if (_mp4box != null)
            {
                _mp4box.Cancel();
            }
            if (_neroAacEncoder != null)
            {
                _neroAacEncoder.Cancel();
            }
            if (_oggEncoder != null)
            {
                _oggEncoder.Cancel();
            }
            if (_wavi != null)
            {
                _wavi.Cancel();
            }
            if (_x264 != null)
            {
                _x264.Cancel();
            }

            _cancelledWhileThrottled = true;
        }

        /// <summary>
        /// Performs an audio-only conversion job.
        /// </summary>
        private void ConvertAudio()
        {
            ConvertAudioDialog dialog = new ConvertAudioDialog();
            dialog.DynamicRangeCompression = _config.AudioDynamicRangeCompression;
            dialog.AudioEncoderQualityOption = _config.AudioEncoderQualityOption;
            dialog.NormalizeAudio = _config.NormalizeAudio;

            dialog.ShowDialog();

            dialog.Close();
            dialog.Dispose();
            dialog = null;
        }

        /// <summary>
        /// Deletes all completed jobs.
        /// </summary>
        private void DeleteCompletedJobs()
        {
            List<int> finishedJobIndices = new List<int>();

            for (int i = _jobs.Count - 1; i >= 0; i--)
            {
                if (_jobs[i].Status == FicSprout.Bacicworx.IO.JobStatus.Completed)
                {
                    DeleteWorkFiles(_jobs[i].SourceFilename);
                    _jobs.RemoveAt(i);
                    listViewJobs.Items.RemoveAt(i);
                };
            }

            SaveConfig();
            UpdateMenus();
        }

        /// <summary>
        /// Deletes the selected jobs from the queue.
        /// </summary>
        private void DeleteJobs()
        {
            if (listViewJobs.Items.Count == 0 || listViewJobs.SelectedIndices.Count == 0)
            {
                return;
            }

            for (int i = listViewJobs.SelectedIndices.Count - 1; i >= 0; i--)
            {
                DeleteWorkFiles(_jobs[listViewJobs.SelectedIndices[i]].SourceFilename);
                _jobs.RemoveAt(listViewJobs.SelectedIndices[i]);
                listViewJobs.Items.RemoveAt(listViewJobs.SelectedIndices[i]);
            }

            SaveConfig();
            UpdateMenus();
        }

        /// <summary>
        /// Deletes the log, index, and work files associated with the specified job.
        /// </summary>
        /// <param name="filename">The filename to delete work files for.</param>
        /// <exception cref="System.ArgumentException">Thrown when filename is null or empty.</exception>
        private void DeleteWorkFiles(string filename)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentException("filename is null or empty.");
            }

            filename = System.IO.Path.ChangeExtension(filename, "log");
            if (System.IO.File.Exists(filename))
            {
                System.IO.File.Delete(filename);
            }

            filename = System.IO.Path.ChangeExtension(filename, "d2v");
            if (System.IO.File.Exists(filename))
            {
                System.IO.File.Delete(filename);
            }

            filename = System.IO.Path.ChangeExtension(filename, "DVDSwarmLog.txt");
            if (System.IO.File.Exists(filename))
            {
                System.IO.File.Delete(filename);
            }
        }

        /// <summary>
        /// Demuxes the contents of an unprotected DVD to a local folder.
        /// </summary>
        private void DemuxDvd()
        {
            DemuxDvdDialog dialog = new DemuxDvdDialog();
            dialog.DvdOutputPath = _config.DvdOutputPath;

            dialog.ShowDialog();
            _config.DvdOutputPath = dialog.DvdOutputPath;

            dialog.Close();
            dialog.Dispose();
            dialog = null;
        }

        /// <summary>
        /// Loads application data from a string containing xml.
        /// </summary>
        /// <param name="value">A string containing xml.</param>
        /// <returns>True if successful, otherwise false.</returns>
        /// <exception cref="System.ArgumentException">Thrown when value is null or empty.</exception>
        /// <example>
        /// <para>
        /// The serialization format for this application is:
        /// </para>
        /// <code>
        /// &lt;DVDSwarm version="1.0.0.0"&gt;
        ///   &lt;Audio delay="..." downmix="..." drc="..." encoderQualityOption="..." format="..."
        ///       normalize="..."/&gt;
        ///   &lt;Dvd&gt;...[path]...&lt;/Dvd&gt;
        ///   &lt;Jobs&gt;
        ///     &lt;Job&gt;
        ///       &lt;AudioTracks&gt;
        ///         &lt;AudioTrack delay="..." downmix="..." drc="..." format="..." normalize="..." outputFormat="..."
        ///             &gt;...Filename...&lt;/AudioTrack&gt;
        ///       &lt;/AudioTracks&gt;
        ///       &lt;Crop aspectRatio="..." bottom="..." enabled="..." height="..." left="..." mode="..." right="..."
        ///           top="..." width="..."/&gt;
        ///       &lt;OutputBaseFilename&gt;...&lt;/OutputBaseFilename&gt;
        ///       &lt;Processing type="None" override="..." overrideType="None"/&gt;
        ///       &lt;Status end="..." start="..."&gt;...&lt;/Status&gt;
        ///       &lt;VideoSource aspectRatio="..." height="..." isAnimation="..." resizeHeight="..." resizeWidth="..."
        ///           trim="..." trimDuration="..." trimOffset="..." width="..."&gt;...Filename...&lt;/VideoSource&gt;
        ///     &lt;/Job&gt;
        ///   &lt;/Jobs&gt;
        ///   &lt;Options container="..." crop="..." shutdownAfterRun="..." smallestFilesize="..." throttle="..."
        ///       throttleSeconds="120" videoEncoderQualityOption="..."/&gt;
        ///   &lt;WorkingDirectory&gt;&lt;/WorkingDirectory&gt;
        ///   &lt;X264 decoderLevel="..." encoderEfficiency="..." inputTuning="..." outputProfile="..."/&gt;
        /// &lt;/DVDSwarm&gt;
        /// </code>
        /// </example>
        private bool Deserialize(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return true;// throw new ArgumentException("value is null or empty.");
            }

            StringReader sr = new StringReader(value);
            XmlReader reader = XmlReader.Create(sr);
            _jobs.Clear();

            JobInfo job = null;
            try
            {
                while (reader.Read())
                {
                    if (reader.Name == "DVDSwarm")
                    {
                        // Ignore versioning of the config file at this time.

                        continue;
                    }
                    if (reader.Name == "Jobs" || reader.Name == "AudioTracks")
                    {
                        continue;
                    }
                    if (reader.Name == "Job" && reader.IsStartElement())
                    {
                        job = new JobInfo();
                    }
                    if (reader.Name == "Job" && !reader.IsStartElement())
                    {
                        JobInfo newJob = new JobInfo();
                        newJob.Copy(job);

                        string startTime = string.Empty;
                        string endTime = string.Empty;
                        if (newJob.Status == FicSprout.Bacicworx.IO.JobStatus.Completed)
                        {
                            startTime = newJob.StartTime.ToString();
                            endTime = newJob.EndTime.ToString();
                        }

                        ListViewItem item = new ListViewItem(new string[] { newJob.SourceFilename,
                                    newJob.Status.ToString(), startTime, endTime });
                        listViewJobs.Items.Add(item);

                        _jobs.Add(newJob);
                        job = null;
                    }
                    if (reader.Name == "AudioTrack" && job != null)
                    {
                        AudioTrackInfo audio = new AudioTrackInfo();
                        audio.Delay = Convert.ToInt32(reader.GetAttribute("delay"));
                        audio.Downmix = Convert.ToBoolean(reader.GetAttribute("downmix"));
                        audio.DynamicRangeCompression = Convert.ToBoolean(reader.GetAttribute("drc"));
                        audio.Normalize = Convert.ToBoolean(reader.GetAttribute("normalize"));
                        audio.OutputFormat = Utility.GetAudioFormat(reader.GetAttribute("outputFormat"));
                        audio.Filename = reader.ReadString(); // This property also sets the source Format property.

                        job.AudioTracks.Add(audio);
                    }
                    if (reader.Name == "Crop" && job != null)
                    {
                        job.CropAspectRatio = FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatio(reader.GetAttribute("aspectRatio"));
                        job.CropBottom = Convert.ToInt32(reader.GetAttribute("bottom"));
                        job.CropHeight = Convert.ToInt32(reader.GetAttribute("height"));
                        job.CropLeft = Convert.ToInt32(reader.GetAttribute("left"));
                        job.Crop = (CropMode)FicSprout.Bacicworx.Text.Utility.TryParseEnumeration(
                            typeof(CropMode), reader.GetAttribute("mode"), true, CropMode.Auto);
                        job.CropRight = Convert.ToInt32(reader.GetAttribute("right"));
                        job.CropTop = Convert.ToInt32(reader.GetAttribute("top"));
                        job.CropWidth = Convert.ToInt32(reader.GetAttribute("width"));
                    }
                    if (reader.Name == "OutputBaseFilename" && job != null)
                    {
                        job.BareFilename = reader.ReadString();
                    }
                    if (reader.Name == "Processing" && job != null)
                    {
                        job.Processing = FicSprout.Bacicworx.Media.Utility.GetVideoProcessingType(reader.GetAttribute("type"));
                        job.ProcessingOverrideEnabled = Convert.ToBoolean(reader.GetAttribute("override"));
                        job.ProcessingOverride = FicSprout.Bacicworx.Media.Utility.GetVideoProcessingType(reader.GetAttribute("overrideType"));
                    }
                    if (reader.Name == "Status" && job != null)
                    {
                        job.EndTime = XmlConvert.ToDateTime(reader.GetAttribute("end"), XmlDateTimeSerializationMode.Utc);
                        job.StartTime = XmlConvert.ToDateTime(reader.GetAttribute("start"), XmlDateTimeSerializationMode.Utc);
                        job.Status = (FicSprout.Bacicworx.IO.JobStatus)
                            FicSprout.Bacicworx.Text.Utility.TryParseEnumeration(
                            typeof(FicSprout.Bacicworx.IO.JobStatus), reader.ReadString(), true,
                            FicSprout.Bacicworx.IO.JobStatus.Inactive);
                    }
                    if (reader.Name == "VideoSource" && job != null)
                    {
                        job.DisplayAspectRatio = FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatio(reader.GetAttribute("aspectRatio"));
                        job.SourceHeight = Convert.ToInt32(reader.GetAttribute("height"));
                        job.IsAnimation = Convert.ToBoolean(reader.GetAttribute("isAnimation"));
                        job.ResizeHeight = Convert.ToInt32(reader.GetAttribute("resizeHeight"));
                        job.ResizeWidth = Convert.ToInt32(reader.GetAttribute("resizeWidth"));
                        job.Trim = Convert.ToBoolean(reader.GetAttribute("trim"));
                        job.TrimDuration = Convert.ToInt32(reader.GetAttribute("trimDuration"));
                        job.TrimOffset = Convert.ToInt32(reader.GetAttribute("trimOffset"));
                        job.SourceWidth = Convert.ToInt32(reader.GetAttribute("width"));
                        job.SourceFilename = reader.ReadString();
                    }
                    if (reader.Name == "Audio")
                    {
                        _config.AudioDelay = Convert.ToInt32(reader.GetAttribute("delay"));
                        _config.DownmixAudio = Convert.ToBoolean(reader.GetAttribute("downmix"));
                        _config.AudioDynamicRangeCompression = Convert.ToBoolean(reader.GetAttribute("drc"));
                        _config.AudioEncoderQualityOption = FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOption(
                            reader.GetAttribute("encoderQualityOption"));
                        _config.AudioFormat = Utility.GetAudioFormat(reader.GetAttribute("format"));
                        _config.NormalizeAudio = Convert.ToBoolean(reader.GetAttribute("normalize"));
                    }
                    if (reader.Name == "Dvd")
                    {
                        _config.DvdOutputPath = reader.ReadString();
                    }
                    if (reader.Name == "Options")
                    {
                        _config.ContainerFormat = Utility.GetOutputContainerFormat(reader.GetAttribute("container"));
                        _config.Crop = Utility.GetCropMode(reader.GetAttribute("crop"));
                        _config.ShutdownAfterRun = Convert.ToBoolean(reader.GetAttribute("shutdownAfterRun"));
                        _config.SmallestFilesize = Convert.ToBoolean(reader.GetAttribute("smallestFilesize"));
                        _config.Throttle = Convert.ToBoolean(reader.GetAttribute("throttle"));
                        _config.ThrottleSeconds = Convert.ToInt32(reader.GetAttribute("throttleSeconds"));
                        _config.VideoEncoderQualityOption = FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOption(
                             reader.GetAttribute("videoEncoderQualityOption"));
                    }
                    if (reader.Name == "WorkingDirectory")
                    {
                        _workingDirectory = reader.ReadString();
                    }
                    if (reader.Name == "X264")
                    {
                        _config.X264DecoderLevel = FicSprout.Bacicworx.Media.X264.Utility.GetDecoderLevel(
                            reader.GetAttribute("decoderLevel"));
                        _config.X264EncoderEfficiency = FicSprout.Bacicworx.Media.X264.Utility.GetEncoderEfficiency(
                            reader.GetAttribute("encoderEfficiency"));
                        _config.X264InputTuning = FicSprout.Bacicworx.Media.X264.Utility.GetInputTuning(
                            reader.GetAttribute("inputTuning"));
                        _config.X264OutputProfile = FicSprout.Bacicworx.Media.X264.Utility.GetOutputProfile(
                            reader.GetAttribute("outputProfile"));
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                throw ex.GetBaseException();
            }
            finally
            {
                reader = null;
                sr.Close();
                sr.Dispose();
                sr = null;
            }
        }

        /// <summary>
        /// Opens the JobDialog for editing of a job.
        /// </summary>
        private void EditJob()
        {
            if (listViewJobs.Items.Count == 0 || listViewJobs.SelectedIndices.Count == 0)
            {
                return;
            }

            JobDialog dialog = new JobDialog(_config, _jobs[listViewJobs.SelectedIndices[0]]);
            dialog.Config = _config;

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                _jobs[listViewJobs.SelectedIndices[0]].Copy(dialog.Job);
                _workingDirectory = System.IO.Path.GetDirectoryName(dialog.Job.SourceFilename);

                SaveConfig();
            }

            dialog.Close();
            dialog.Dispose();
            dialog = null;
        }

        /// <summary>
        /// Finalizes a job run.
        /// </summary>
        private void FinalizeJobRun()
        {
            _jobs[_currentJobIndex].OutputFilename = string.Empty;
            _jobs[_currentJobIndex].EndTime = DateTime.Now;

            // Finalize log.
            _log.AppendLine("==> Cleanup Temporary Work Files (" + _jobs[_currentJobIndex].EndTime.ToString() + ")");

            string deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].SourceFilename, "avs");
            if (System.IO.File.Exists(deleteFilename))
            {
                System.IO.File.Delete(deleteFilename);
                _log.AppendLine("Delete: " + deleteFilename);
            }

            // Delete output audio tracks if conversion was performed. If no conversion, no deleting occurs.
            for (int i = 0; i < _jobs[_currentJobIndex].AudioTracks.Count; i++)
            {
                if (_jobs[_currentJobIndex].AudioTracks[i].Format != _jobs[_currentJobIndex].AudioTracks[i].OutputFormat)
                {
                    deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[i].Filename,
                        Utility.GetAudioFormatExtension(_jobs[_currentJobIndex].AudioTracks[i].OutputFormat));

                    if (System.IO.File.Exists(deleteFilename))
                    {
                        System.IO.File.Delete(deleteFilename);
                        _log.AppendLine("Delete: " + deleteFilename);
                    }
                }
            }
            _log.AppendLine();
            _log.AppendLine();

            _log.AppendLine("==> End Of Job Run (" + _jobs[_currentJobIndex].EndTime.ToString() + ")");
            _log.AppendLine("Status: " + _jobs[_currentJobIndex].Status);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();
            _log.Close();
            _log = null;

            SaveConfig();

            // Update GUI.
            Invoke(new UpdateJobInListViewFromThread(UpdateJobInListView), new object[] { _currentJobIndex });
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { string.Empty });

            // Is multi-job run?
            if (_jobRunMode == JobRunMode.MultiJobRun)
            {
                // Cancelled or failed?
                if (_jobs[_currentJobIndex].Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                    _jobs[_currentJobIndex].Status == FicSprout.Bacicworx.IO.JobStatus.Failed ||
                    _cancelAfterCurrentJobFinishes)
                {
                    _jobRunMode = JobRunMode.Ended;

                    // Cancel all queued jobs.
                    for (int i = _currentJobIndex + 1; i < _jobs.Count; i++)
                    {
                        _jobs[i].Status = FicSprout.Bacicworx.IO.JobStatus.Cancelled;
                        Invoke(new UpdateJobInListViewFromThread(UpdateJobInListView), new object[] { i });
                    }
                }

                // Current job completed?
                if (_jobs[_currentJobIndex].Status == FicSprout.Bacicworx.IO.JobStatus.Completed && !_cancelAfterCurrentJobFinishes)
                {
                    // Update the current job index to the id of the next job to run.
                    _currentJobIndex++;

                    // Are there jobs still queued? Throttle successful (regardless of whether it actually throttled)?
                    if (_currentJobIndex < _jobs.Count && Throttle())
                    {
                        // If not cancelled while throttled, Start next job in list.
                        InitializeJobRun();
                    }
                    else
                    {
                        _jobRunMode = JobRunMode.Ended;
                    }
                }
            }

            // Single job run? End of run all jobs?
            if (_jobRunMode == JobRunMode.SingleJobRun || _jobRunMode == JobRunMode.Ended)
            {
                _cancelAfterCurrentJobFinishes = false;
                _cancelledWhileThrottled = false;
                _currentJobIndex = -1;
                _currentAudioTrackNumber = -1;
                _jobRunMode = JobRunMode.Ended;
            }

            // Update GUI menus.
            Invoke(new UpdateMenusFromThread(UpdateMenus));

            // Shutdown?
            if (_config.ShutdownAfterRun && _jobRunMode == JobRunMode.Ended)
            {
                SaveConfig();
                Utility.ShutdownWindows();
            }
        }

        /// <summary>
        /// Initializes a job run.
        /// </summary>
        private void InitializeJobRun()
        {
            // Update job info.
            _jobs[_currentJobIndex].StartTime = DateTime.Now;
            _jobs[_currentJobIndex].EndTime = new DateTime(_jobs[_currentJobIndex].StartTime.Ticks);
            _jobs[_currentJobIndex].OutputFilename = System.IO.Path.GetDirectoryName(_jobs[_currentJobIndex].SourceFilename)
                + "\\" + _jobs[_currentJobIndex].BareFilename + Utility.GetOutputContainerFormatExtension(_config.ContainerFormat);
            _jobs[_currentJobIndex].Status = FicSprout.Bacicworx.IO.JobStatus.Active;

            SaveConfig();

            // Start log file.
            _log = new FicSprout.Bacicworx.IO.LogWriter(System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].OutputFilename,
                _fileExtension_DvdSwarmLog), _logStandardHeader);
            _log.AppendLine("==> Starting Job Run [" + _currentJobIndex.ToString() + "]");
            _log.AppendLine("Source: \"" + _jobs[_currentJobIndex].SourceFilename + "\"");

            if (_jobs[_currentJobIndex].Trim)
            {
                Int32 start = Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset, _jobs[_currentJobIndex].OutputFrameRate);
                Int32 end = Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset + _jobs[_currentJobIndex].TrimDuration,
                    _jobs[_currentJobIndex].OutputFrameRate);
                FicSprout.Bacicworx.Media.SmpteTimecode startTimecode = new FicSprout.Bacicworx.Media.SmpteTimecode(0, 0, 0, start,
                    _jobs[_currentJobIndex].OutputFrameRate, true);
                FicSprout.Bacicworx.Media.SmpteTimecode endTimecode = new FicSprout.Bacicworx.Media.SmpteTimecode(startTimecode);
                endTimecode.Add(0, 0, end, 0);

                _log.AppendLine("Trim  [" + startTimecode.ToString() + "]  ---> [" + endTimecode.ToString() + "], " +
                    (end - start + 1).ToString() + "frames");
            }

            if (_jobs[_currentJobIndex].ProcessingOverrideEnabled)
            {
                _log.AppendLine("Processing: Override, use " + FicSprout.Bacicworx.Media.Utility.GetVideoProcessingTypeName(_jobs[_currentJobIndex].ProcessingOverride));
            }
            else
            {
                _log.AppendLine("Processing: Auto");
            }

            if (_jobs[_currentJobIndex].Crop == CropMode.Auto)
            {
                _log.AppendLine("Crop: Auto");
            }

            if (_jobs[_currentJobIndex].Crop == CropMode.Aspect)
            {
                _log.AppendLine("Crop: Aspect " + FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatioName(_jobs[_currentJobIndex].CropAspectRatio));
            }

            if (_jobs[_currentJobIndex].Crop == CropMode.Manual)
            {
                _log.AppendLine("Crop: Manual (" + _jobs[_currentJobIndex].CropLeft.ToString() + ", " + _jobs[_currentJobIndex].CropTop.ToString() +
                    ", " + _jobs[_currentJobIndex].CropRight.ToString() + ", " + _jobs[_currentJobIndex].CropBottom.ToString() + ")");
            }

            _log.AppendLine("Is Animation: " + _jobs[_currentJobIndex].IsAnimation.ToString());
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            // Start by indexing the video.
            ExecuteDgindex();
        }

        /// <summary>
        /// Opens the containing folder for the selected job.
        /// </summary>
        private void OpenFolder()
        {
            // No jobs in list? No job selected?
            if (listViewJobs.Items.Count == 0 || listViewJobs.SelectedIndices.Count == 0)
            {
                return;
            }

            System.Diagnostics.Process.Start(System.IO.Path.GetDirectoryName(_jobs[listViewJobs.SelectedIndices[0]].SourceFilename));
        }

        /// <summary>
        /// Opens the project website in a new browser window.
        /// </summary>
        private void OpenProjectWebsite()
        {
            System.Diagnostics.Process.Start(_projectWebsite);
        }

        /// <summary>
        /// Resets all jobs to 'Queued' status.
        /// </summary>
        private void QueueJobs()
        {
            for (int i = 0; i < _jobs.Count; i++)
            {
                JobInfo job = _jobs[i];
                job.Status = FicSprout.Bacicworx.IO.JobStatus.Queued;
                listViewJobs.Items[i].SubItems[1].Text = job.Status.ToString();
                listViewJobs.Items[i].SubItems[2].Text = string.Empty;
                listViewJobs.Items[i].SubItems[3].Text = string.Empty;
                _jobs[i] = job;
            }

            SaveConfig();
            UpdateMenus();
        }

        /// <summary>
        /// Resets all jobs to 'Inactive' status.
        /// </summary>
        private void ResetJobs()
        {
            // Update all jobs to "Waiting".
            for (int i = 0; i < _jobs.Count; i++)
            {
                JobInfo job = _jobs[i];
                job.Status = FicSprout.Bacicworx.IO.JobStatus.Inactive;
                listViewJobs.Items[i].SubItems[1].Text = job.Status.ToString();
                _jobs[i] = job;
            }

            SaveConfig();
            UpdateMenus();
        }

        /// <summary>
        /// Runs all jobs in the queue.
        /// </summary>
        private void RunAllJobs()
        {
            // No jobs in list?
            if (listViewJobs.Items.Count == 0)
            {
                return;
            }

            // Set all jobs to queued.
            QueueJobs();

            // Set the first job in the list as the current job.
            _currentJobIndex = 0;
            _jobRunMode = JobRunMode.MultiJobRun;

            InitializeJobRun();
        }

        /// <summary>
        /// Runs the selected job.
        /// </summary>
        private void RunJob()
        {
            // No jobs in list? No job selected?
            if (listViewJobs.Items.Count == 0 || listViewJobs.SelectedIndices.Count == 0)
            {
                return;
            }

            // Set selected job as the current job.
            _currentJobIndex = listViewJobs.SelectedIndices[0];
            _jobRunMode = JobRunMode.SingleJobRun;

            InitializeJobRun();
        }

        /// <summary>
        /// Saves the application configuration data.
        /// </summary>
        private void SaveConfig()
        {
            System.IO.File.WriteAllText(_configFilename, Serialize(), System.Text.Encoding.UTF8);
        }

        /// <summary>
        /// Set the selected property for all jobs in the ListView to the specified value.
        /// </summary>
        /// <param name="value">The value to set the jobs to.</param>
        private void SetAllJobsSelected(bool value)
        {
            for (int i = 0; i < listViewJobs.Items.Count; i++)
            {
                listViewJobs.Items[i].Selected = value;
            }
        }

        /// <summary>
        /// Serializes the appplication data as xml.
        /// </summary>
        /// <returns>A string containing the application data as xml</returns>
        /// <example>
        /// <para>
        /// The serialization format for this application is:
        /// </para>
        /// <code>
        /// &lt;DVDSwarm version="1.0.0.0"&gt;
        ///   &lt;Audio delay="..." downmix="..." drc="..." encoderQualityOption="..." format="..."
        ///       normalize="..."/&gt;
        ///   &lt;Dvd&gt;...[path]...&lt;/Dvd&gt;
        ///   &lt;Jobs&gt;
        ///     &lt;Job&gt;
        ///       &lt;AudioTracks&gt;
        ///         &lt;AudioTrack delay="..." downmix="..." drc="..." format="..." normalize="..." outputFormat="..."
        ///             &gt;...Filename...&lt;/AudioTrack&gt;
        ///       &lt;/AudioTracks&gt;
        ///       &lt;Crop aspectRatio="..." bottom="..." enabled="..." height="..." left="..." mode="..." right="..."
        ///           top="..." width="..."/&gt;
        ///       &lt;OutputBaseFilename&gt;...&lt;/OutputBaseFilename&gt;
        ///       &lt;Processing type="None" override="..." overrideType="None"/&gt;
        ///       &lt;Status end="..." start="..."&gt;...&lt;/Status&gt;
        ///       &lt;VideoSource aspectRatio="..." height="..." isAnimation="..." resizeHeight="..." resizeWidth="..."
        ///           trim="..." trimDuration="..." trimOffset="..." width="..."&gt;...Filename...&lt;/VideoSource&gt;
        ///     &lt;/Job&gt;
        ///   &lt;/Jobs&gt;
        ///   &lt;Options container="..." crop="..." shutdownAfterRun="..." smallestFilesize="..." throttle="..."
        ///       throttleSeconds="120" videoEncoderQualityOption="..."/&gt;
        ///   &lt;WorkingDirectory&gt;&lt;/WorkingDirectory&gt;
        ///   &lt;X264 decoderLevel="..." encoderEfficiency="..." inputTuning="..." outputProfile="..."/&gt;
        /// &lt;/DVDSwarm&gt;
        /// </code>
        /// </example>
        private string Serialize()
        {
            XmlWriterSettings writerSettings = new XmlWriterSettings();
            writerSettings.ConformanceLevel = ConformanceLevel.Fragment;
            writerSettings.Encoding = System.Text.Encoding.UTF8;
            writerSettings.Indent = true;
            writerSettings.OmitXmlDeclaration = true;
            StringBuilder sb = new StringBuilder();
            XmlWriter writer = XmlWriter.Create(sb, writerSettings);

            writer.WriteStartElement("DVDSwarm");
            writer.WriteAttributeString("version", Assembly.GetExecutingAssembly().GetName().Version.ToString());

            writer.WriteStartElement("Audio");
            writer.WriteAttributeString("delay", _config.AudioDelay.ToString());
            writer.WriteAttributeString("downmix", _config.DownmixAudio.ToString());
            writer.WriteAttributeString("drc", _config.AudioDynamicRangeCompression.ToString());
            writer.WriteAttributeString("encoderQualityOption",
                 FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOptionName(_config.AudioEncoderQualityOption));
            writer.WriteAttributeString("format", Utility.GetAudioFormatName(_config.AudioFormat));
            writer.WriteAttributeString("normalize", _config.NormalizeAudio.ToString());
            writer.WriteEndElement();

            writer.WriteStartElement("Dvd");
            writer.WriteString(_config.DvdOutputPath);
            writer.WriteEndElement();

            writer.WriteStartElement("Jobs");
            foreach (JobInfo job in _jobs)
            {
                writer.WriteStartElement("Job");

                writer.WriteStartElement("AudioTracks");
                for (int i = 0; i < job.AudioTracks.Count; i++)
                {
                    writer.WriteStartElement("AudioTrack");
                    writer.WriteAttributeString("delay", job.AudioTracks[i].Delay.ToString());
                    writer.WriteAttributeString("downmix", job.AudioTracks[i].Downmix.ToString());
                    writer.WriteAttributeString("drc", job.AudioTracks[i].DynamicRangeCompression.ToString());
                    writer.WriteAttributeString("normalize", job.AudioTracks[i].Normalize.ToString());
                    writer.WriteAttributeString("outputFormat", Utility.GetAudioFormatName(job.AudioTracks[i].OutputFormat));
                    writer.WriteString(job.AudioTracks[i].Filename);
                    writer.WriteEndElement();
                }
                writer.WriteEndElement();

                writer.WriteStartElement("Crop");
                writer.WriteAttributeString("aspectRatio", FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatioName(job.CropAspectRatio));
                writer.WriteAttributeString("bottom", job.CropBottom.ToString());
                writer.WriteAttributeString("height", job.CropHeight.ToString());
                writer.WriteAttributeString("left", job.CropLeft.ToString());
                writer.WriteAttributeString("mode", job.Crop.ToString());
                writer.WriteAttributeString("right", job.CropRight.ToString());
                writer.WriteAttributeString("top", job.CropTop.ToString());
                writer.WriteAttributeString("width", job.CropWidth.ToString());
                writer.WriteEndElement();

                writer.WriteElementString("OutputBaseFilename", job.BareFilename);

                writer.WriteStartElement("Processing");
                writer.WriteAttributeString("type",
                    FicSprout.Bacicworx.Media.Utility.GetVideoProcessingTypeName(job.Processing));
                writer.WriteAttributeString("override", job.ProcessingOverrideEnabled.ToString());
                writer.WriteAttributeString("overrideType",
                    FicSprout.Bacicworx.Media.Utility.GetVideoProcessingTypeName(job.ProcessingOverride));
                writer.WriteEndElement();

                writer.WriteStartElement("Status");
                writer.WriteAttributeString("end", XmlConvert.ToString(job.EndTime, XmlDateTimeSerializationMode.Utc));
                writer.WriteAttributeString("start", XmlConvert.ToString(job.StartTime, XmlDateTimeSerializationMode.Utc));
                writer.WriteString(job.Status.ToString());
                writer.WriteEndElement();

                writer.WriteStartElement("VideoSource");
                writer.WriteAttributeString("aspectRatio", FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatioName(job.DisplayAspectRatio));
                writer.WriteAttributeString("height", job.SourceHeight.ToString());
                writer.WriteAttributeString("isAnimation", job.IsAnimation.ToString());
                writer.WriteAttributeString("resizeHeight", job.ResizeHeight.ToString());
                writer.WriteAttributeString("resizeWidth", job.ResizeWidth.ToString());
                writer.WriteAttributeString("trim", job.Trim.ToString());
                writer.WriteAttributeString("trimDuration", job.TrimDuration.ToString());
                writer.WriteAttributeString("trimOffset", job.TrimOffset.ToString());
                writer.WriteAttributeString("width", job.SourceWidth.ToString());
                writer.WriteString(job.SourceFilename);
                writer.WriteEndElement();

                writer.WriteEndElement();
            }
            writer.WriteEndElement();

            writer.WriteStartElement("Options");
            writer.WriteAttributeString("container", Utility.GetOutputContainerFormatName(_config.ContainerFormat));
            writer.WriteAttributeString("crop", Utility.GetCropModeName(_config.Crop));
            writer.WriteAttributeString("shutdownAfterRun", _config.ShutdownAfterRun.ToString());
            writer.WriteAttributeString("smallestFilesize", _config.SmallestFilesize.ToString());
            writer.WriteAttributeString("throttle", _config.Throttle.ToString());
            writer.WriteAttributeString("throttleSeconds", _config.ThrottleSeconds.ToString());
            writer.WriteAttributeString("videoEncoderQualityOption",
                FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOptionName(_config.VideoEncoderQualityOption));
            writer.WriteEndElement();

            writer.WriteElementString("WorkingDirectory", _workingDirectory);

            writer.WriteStartElement("X264");
            writer.WriteAttributeString("decoderLevel",
                FicSprout.Bacicworx.Media.X264.Utility.GetDecoderLevelName(_config.X264DecoderLevel));
            writer.WriteAttributeString("encoderEfficiency",
                FicSprout.Bacicworx.Media.X264.Utility.GetEncoderEfficiencyName(_config.X264EncoderEfficiency));
            writer.WriteAttributeString("inputTuning",
                FicSprout.Bacicworx.Media.X264.Utility.GetInputTuningName(_config.X264InputTuning));
            writer.WriteAttributeString("outputProfile",
                FicSprout.Bacicworx.Media.X264.Utility.GetOutputProfileName(_config.X264OutputProfile));
            writer.WriteEndElement();

            writer.WriteEndElement();
            writer.Flush();
            writer.Close();
            writer = null;

            return sb.ToString();
        }

        /// <summary>
        /// Shows the AboutBox dialog.
        /// </summary>
        private void ShowAboutBox()
        {
            DVDSwarm.AboutBoxDialog aboutDialog = new DVDSwarm.AboutBoxDialog();
            aboutDialog.ShowDialog();
            aboutDialog.Close();
            aboutDialog = null;
        }

        /// <summary>
        /// Show the Preferences dialog.
        /// </summary>
        private void ShowPreferences()
        {
            PreferencesDialog dialog = new PreferencesDialog(_config);

            if (dialog.ShowDialog() == DialogResult.OK)
            {
                _config.Copy(dialog.ConfigurationData);

                SaveConfig();
                UpdateMenus();
            }

            dialog.Close();
            dialog = null;
        }

        /// <summary>
        /// Throttles the conversion job run for the user-selected number of seconds.
        /// </summary>
        /// <returns>Returns true if the throttle completed. False is returned if the throttle was
        /// cancelled by the user.</returns>
        private bool Throttle()
        {
            bool result = true;

            // Throttling not enabled?
            if (!_config.Throttle)
            {
                return result;
            }
            // Current job index out of throttle range? Throttle for all jobs except the last.
            if (_currentJobIndex >= _jobs.Count - 1)
            {
                return result;
            }

            DateTime timestamp = DateTime.Now;

            while (DateTime.Now.Subtract(timestamp).TotalSeconds < _config.ThrottleSeconds)
            {
                Thread.Sleep(100);

                Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                                "Throttled...  " + timestamp.AddSeconds(_config.ThrottleSeconds)
                                .Subtract(DateTime.Now).ToString("m\\:ss") + " Remaining" });

                if (_cancelledWhileThrottled)
                {
                    result = false;
                    Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { string.Empty });
                    break;
                }
            }

            _cancelledWhileThrottled = false;

            return result;
        }

        private delegate void UpdateJobInListViewFromThread(Int32 index);

        /// <summary>
        /// Required for updating the job info in the ListView from a different thread.
        /// </summary>
        /// <param name="index">The index of the job.</param>
        private void UpdateJobInListView(Int32 index)
        {
            if (index < 0 || index > _jobs.Count)
            {
                return;
            }

            listViewJobs.Items[index].SubItems[1].Text = _jobs[index].Status.ToString();

            // Show start time if job is not inactive or not queued.
            if (_jobs[index].Status != FicSprout.Bacicworx.IO.JobStatus.Inactive &&
                _jobs[index].Status != FicSprout.Bacicworx.IO.JobStatus.Queued)
            {
                listViewJobs.Items[index].SubItems[2].Text = _jobs[index].StartTime.ToString();

                if (_jobs[index].Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                    _jobs[index].Status == FicSprout.Bacicworx.IO.JobStatus.Completed ||
                    _jobs[index].Status == FicSprout.Bacicworx.IO.JobStatus.Failed)
                {
                    // The job has completed/cancelled/failed, display endtime.
                    listViewJobs.Items[index].SubItems[3].Text = _jobs[index].EndTime.ToString();
                }
                else
                {
                    // Active or queued, don't display endtime.
                    listViewJobs.Items[index].SubItems[3].Text = string.Empty;
                }
            }
        }

        private delegate void UpdateMenusFromThread();

        /// <summary>
        /// Updates GUI menus.
        /// </summary>
        private void UpdateMenus()
        {
            // Default button states.
            addJobToolStripMenuItem.Enabled = false;
            addJobContextMenuItem.Enabled = false;
            cancelAfterCurrentJobContextMenuItem.Checked = _cancelAfterCurrentJobFinishes;
            cancelAfterCurrentJobContextMenuItem.Enabled = false;
            cancelAfterCurrentJobToolStripMenuItem.Checked = _cancelAfterCurrentJobFinishes;
            cancelAfterCurrentJobToolStripMenuItem.Enabled = false;
            cancelJobRunContextMenuItem.Enabled = false;
            cancelJoblRunToolStripMenuItem.Enabled = false;
            clearSelectionToolStripMenuItem.Enabled = false;
            convertAudioToolStripMenuItem.Enabled = false;
            deleteFinishedJobsToolStripMenuItem.Enabled = false;
            demuxDVDToolStripMenuItem.Enabled = false;
            editJobToolStripMenuItem.Enabled = false;
            editJobContextMenuItem.Enabled = false;
            exitToolStripMenuItem.Enabled = true;
            openFolderContextMenuItem.Enabled = false;
            preferencesToolStripMenuItem.Enabled = true;
            deleteJobToolStripMenuItem.Enabled = false;
            deleteJobContextMenuItem.Enabled = false;
            resetJobsToolStripMenuItem.Enabled = false;
            runAllJobsContextMenuItem.Enabled = false;
            runAllJobsToolStripMenuItem.Enabled = false;
            runJobToolStripMenuItem.Enabled = false;
            runJobContextMenuItem.Enabled = false;
            selectAllToolStripMenuItem.Enabled = false;
            toolStripStatusLabelShutdown.Visible = _config.ShutdownAfterRun;
            viewLogContexlMenuItem.Enabled = false;
            viewLogToolStripMenuItem.Enabled = false;

            // Running a job.
            if (_currentJobIndex != -1)
            {
                cancelAfterCurrentJobContextMenuItem.Enabled = true;
                cancelAfterCurrentJobToolStripMenuItem.Enabled = true;
                cancelJobRunContextMenuItem.Enabled = true;
                cancelJoblRunToolStripMenuItem.Enabled = true;
                exitToolStripMenuItem.Enabled = false;
                openFolderContextMenuItem.Enabled = true;
                preferencesToolStripMenuItem.Enabled = false;

                // Only one job selected? View log file if it exists.
                if (listViewJobs.SelectedIndices.Count == 1 && _currentJobIndex != listViewJobs.SelectedIndices[0] &&
                    System.IO.File.Exists(System.IO.Path.ChangeExtension(
                        _jobs[listViewJobs.SelectedIndices[0]].SourceFilename, _fileExtension_DvdSwarmLog)))
                {
                    viewLogContexlMenuItem.Enabled = true;
                    viewLogToolStripMenuItem.Enabled = true;
                }

                return;
            }

            // Not running any jobs.
            addJobToolStripMenuItem.Enabled = true;
            addJobContextMenuItem.Enabled = true;
            convertAudioToolStripMenuItem.Enabled = true;
            demuxDVDToolStripMenuItem.Enabled = true;
            
            // Any Job(s)?
            if (_jobs.Count > 0)
            {
                deleteFinishedJobsToolStripMenuItem.Enabled = true;
                resetJobsToolStripMenuItem.Enabled = true;
                selectAllToolStripMenuItem.Enabled = true;
            }

            // Multiple jobs?
            if (_jobs.Count > 1)
            {
                deleteFinishedJobsToolStripMenuItem.Enabled = true;
                resetJobsToolStripMenuItem.Enabled = true;
                runAllJobsContextMenuItem.Enabled = true;
                runAllJobsToolStripMenuItem.Enabled = true;
            }

            // One job selected?
            if (listViewJobs.SelectedIndices.Count == 1)
            {
                clearSelectionToolStripMenuItem.Enabled = true;
                editJobContextMenuItem.Enabled = true;
                editJobToolStripMenuItem.Enabled = true;
                openFolderContextMenuItem.Enabled = true;
                deleteJobContextMenuItem.Enabled = true;
                deleteJobToolStripMenuItem.Enabled = true;
                runJobContextMenuItem.Enabled = true;
                runJobToolStripMenuItem.Enabled = true;
                
                // View log file if it exists.
                if (System.IO.File.Exists(System.IO.Path.ChangeExtension(
                    _jobs[listViewJobs.SelectedIndices[0]].SourceFilename, _fileExtension_DvdSwarmLog)))
                {
                    viewLogContexlMenuItem.Enabled = true;
                    viewLogToolStripMenuItem.Enabled = true;
                }
            }

            // Multiple jobs selected?
            if (listViewJobs.SelectedIndices.Count > 1)
            {
                clearSelectionToolStripMenuItem.Enabled = true;
                deleteJobContextMenuItem.Enabled = true;
                deleteJobToolStripMenuItem.Enabled = true;
            }
        }

        private delegate void UpdateStatusBarTextFromThread(string value);

        /// <summary>
        /// Required for updating the status bar text from a different thread.
        /// </summary>
        /// <param name="value">The text to display in the control.</param>
        private void UpdateStatusBarText(string value)
        {
            if (_currentJobIndex == -1)
            {
                return;
            }

            // Show the current audio track number that is encoding.
            if (value.Contains("%TrackNumber%"))
            {
                value = value.Replace("%TrackNumber%", _currentAudioTrackNumber.ToString());
            }

            toolStripStatusLabel.Text = value;
        }

        /// <summary>
        /// Opens the job run log in a new window.
        /// </summary>
        private void ViewLog()
        {
            if (listViewJobs.SelectedIndices.Count == 1)
            {
                LogViewerDialog dialog = new LogViewerDialog(System.IO.Path.ChangeExtension(
                    _jobs[listViewJobs.SelectedIndices[0]].SourceFilename, _fileExtension_DvdSwarmLog));
                dialog.ShowDialog();
                dialog.Close();
                dialog.Dispose();
                dialog = null;
            }
        }

        #endregion

        #region Events

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowAboutBox();
        }

        private void addJobToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AddJob();
        }

        private void cancelAfterCurrentJobToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CancelAfterCurrentJobFinishes();
        }

        private void cancelJobRunToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CancelJobRun();
        }

        private void clearSelectionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetAllJobsSelected(false);
        }

        private void convertAudioToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ConvertAudio();
        }

        private void deleteFinishedJobsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DeleteCompletedJobs();
        }

        private void deleteJobToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DeleteJobs();
        }

        private void demuxDVDToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DemuxDvd();
        }

        private void editJobToolStripMenuItem_Click(object sender, EventArgs e)
        {
            EditJob();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void listViewJobs_SelectedIndexChanged(object sender, EventArgs e)
        {
            UpdateMenus();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Is a job running? Do not allow the application to close.
            if (_currentJobIndex != -1)
            {
                e.Cancel = true;
                return;
            }

            // Save configuration data.
            System.IO.File.WriteAllText(_configFilename, Serialize(), System.Text.Encoding.UTF8);

            // Allow close of application.
            e.Cancel = false;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Required tools installed?
            bool requiredToolsInstalled = true;
            StringBuilder missingTools = new StringBuilder("Required tools are missing:\n\n");

            for (int i = 0; i < _requiredTools.Length; i++)
            {
                if (!System.IO.File.Exists(Application.StartupPath + "\\Tools\\" + _requiredTools[i]))
                {
                    missingTools.AppendLine(_requiredTools[i]);
                    requiredToolsInstalled = false;
                }
            }
            if (!requiredToolsInstalled)
            {
                System.Windows.Forms.MessageBox.Show(missingTools.ToString(), "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);

                Close();
                Application.Exit();
            }

            // AviSynth must be installed.
            if (!Utility.IsAvisynthInstalled())
            {
                if (MessageBox.Show("AviSynth is required but is not installed.\nWould you like to install it?",
                    "Install AviSynth", MessageBoxButtons.YesNo, MessageBoxIcon.Question) ==
                    System.Windows.Forms.DialogResult.Yes)
                {
                    System.Diagnostics.Process.Start(Application.StartupPath + "\\Tools\\Avisynth_258.exe");
                }
            }
        }

        private void openFolderToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFolder();
        }

        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowPreferences();
        }

        private void projectWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenProjectWebsite();
        }

        private void resetJobsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ResetJobs();
        }

        private void runAllJobsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            RunAllJobs();
        }

        private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SetAllJobsSelected(true);
        }

        private void runJobToolStripMenuItem_Click(object sender, EventArgs e)
        {
            RunJob();
        }

        private void viewLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ViewLog();
        }

        #endregion

        #region Tool Events

        void ExecuteDgindex_Completed(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Indexing Completed" });

            if (_dgindex.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                _dgindex.Status == FicSprout.Bacicworx.IO.JobStatus.Failed)
            {
                _jobs[_currentJobIndex].Status = _dgindex.Status;
                FinalizeJobRun();
                _dgindex = null;
                return;
            }

            // Copy values from worker to the job.
            _jobs[_currentJobIndex].DisplayAspectRatio = _dgindex.DisplayAspectRatio;
            _jobs[_currentJobIndex].FieldOrder = _dgindex.FieldOrder;
            _jobs[_currentJobIndex].FrameCount = _dgindex.FrameCount;
            _jobs[_currentJobIndex].FrameRate = _dgindex.FrameRate;
            _jobs[_currentJobIndex].FrameType = _dgindex.FrameType;
            _jobs[_currentJobIndex].OutputFrameRate = _dgindex.FrameRate;
            _jobs[_currentJobIndex].SourceFilename = _dgindex.SourceFilename;
            _jobs[_currentJobIndex].SourceHeight = _dgindex.SourceHeight;
            _jobs[_currentJobIndex].SourceWidth = _dgindex.SourceWidth;
            _jobs[_currentJobIndex].VideoType = _dgindex.VideoType;
            _jobs[_currentJobIndex].VideoTypePercentage = _dgindex.VideoTypePercentage;

            _dgindex = null;
            Utility.DetermineVideoProcessing(_jobs[_currentJobIndex]);

            // Update log.
            _log.AppendLine("==> Video Indexing Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Display Aspect Ratio: " +
                FicSprout.Bacicworx.Media.Utility.GetVideoAspectRatioName(_jobs[_currentJobIndex].DisplayAspectRatio));
            _log.AppendLine("Field Order: " + _jobs[_currentJobIndex].FieldOrder.ToString());
            _log.AppendLine("Frame Count: " + _jobs[_currentJobIndex].FrameCount.ToString());
            _log.AppendLine("Frame Rate: " +
                FicSprout.Bacicworx.Media.Utility.GetVideoFrameRateValue(_jobs[_currentJobIndex].FrameRate, true).ToString("0.000"));
            _log.AppendLine("Frame Type: " + _jobs[_currentJobIndex].FrameType.ToString());
            _log.AppendLine("Source Height: " + _jobs[_currentJobIndex].SourceHeight.ToString());
            _log.AppendLine("Source Width: " + _jobs[_currentJobIndex].SourceWidth.ToString());
            _log.AppendLine("Video Type: " + _jobs[_currentJobIndex].VideoType.ToString() + " " +
                _jobs[_currentJobIndex].VideoTypePercentage.ToString() + "%");
            _log.AppendLine("Recommended Processing: " +
                FicSprout.Bacicworx.Media.Utility.GetVideoProcessingTypeName(_jobs[_currentJobIndex].Processing));
            if (_jobs[_currentJobIndex].ProcessingOverrideEnabled)
            {
                _log.AppendLine("Override Processing: " +
                    FicSprout.Bacicworx.Media.Utility.GetVideoProcessingTypeName(_jobs[_currentJobIndex].ProcessingOverride));
            }
            _log.AppendLine("Output Frame Rate: " +
                FicSprout.Bacicworx.Media.Utility.GetVideoFrameRateValue(_jobs[_currentJobIndex].OutputFrameRate, true).ToString("0.000"));
            _log.Append("Output Frame Count: ");
            if (_jobs[_currentJobIndex].Trim)
            {
                Int32 start = Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset, _jobs[_currentJobIndex].OutputFrameRate);
                Int32 end = Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset +
                    _jobs[_currentJobIndex].TrimDuration, _jobs[_currentJobIndex].OutputFrameRate);

                _log.Append((end - start).ToString() + " of ");
            }
            _log.AppendLine(_jobs[_currentJobIndex].OutputFrameCount.ToString());
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            // Trim offset or duration + offset greater than the video frame count?
            // Some videos may return a framecount of zero. Attempting a trimmed video run results in
            // completion instead of failure.
            if (_jobs[_currentJobIndex].Trim && _jobs[_currentJobIndex].OutputFrameCount > 0 &&
                (Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset, _jobs[_currentJobIndex].OutputFrameRate) >
                _jobs[_currentJobIndex].OutputFrameCount ||
                Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset + _jobs[_currentJobIndex].TrimDuration,
                _jobs[_currentJobIndex].OutputFrameRate) > _jobs[_currentJobIndex].OutputFrameCount))
            {
                _jobs[_currentJobIndex].Status = FicSprout.Bacicworx.IO.JobStatus.Failed;

                // Update log.
                _log.AppendLine("==> Job Run Failure - offset or duration is greater than framecount");
                _log.AppendLine("Output Frame Count: " + _jobs[_currentJobIndex].OutputFrameCount.ToString());
                _log.AppendLine("Start Frame Offset: " + _jobs[_currentJobIndex].TrimOffset.ToString() + " seconds (Frame #" +
                    Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset, _jobs[_currentJobIndex].FrameRate) + ")");
                _log.AppendLine("Duration: " + _jobs[_currentJobIndex].TrimDuration.ToString() + " seconds (Frame #" +
                    Utility.GetFrameNumber(_jobs[_currentJobIndex].TrimOffset + _jobs[_currentJobIndex].TrimDuration, _jobs[_currentJobIndex].FrameRate) + ")");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                FinalizeJobRun();
                _dgindex = null;
                return;
            }

            ExecuteX264();
        }

        void ExecuteDgindex_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Indexing Video...  " + _dgindex.ProgressPercent.ToString() + "%" });
        }

        void ExecuteFlacEncoder_Completed(object sender, EventArgs e)
        {
            if (_flacEncoder.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                _jobs[_currentJobIndex].Status = _flacEncoder.Status;
                FinalizeJobRun();
                _flacEncoder = null;
                return;
            }

            _flacEncoder = null;

            // Update log.
            _log.AppendLine("==> FLAC Encoder Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");

            // Delete temporary work files.
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Ac3)
            {
                string deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename, "avs");
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }

                deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                    Utility.GetAudioFormatExtension(AudioFormat.Wav));
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }
            }

            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Completed" });

            DoNextAudioTrack();
        }

        void ExecuteFlacEncoder_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Encoding Audio...  FLAC,  " + _flacEncoder.ProgressPercent.ToString() + "%" });
        }

        void ExecuteMkvmerge_Completed(object sender, EventArgs e)
        {
            _jobs[_currentJobIndex].Status = _mkvmerge.Status;

            if (_mkvmerge.Status != FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                // Remove original video file, rename packaged caontainer file to original filename.
                System.IO.File.Delete(_jobs[_currentJobIndex].OutputFilename);
                System.IO.File.Move(_jobs[_currentJobIndex].OutputFilename + ".tmp", _jobs[_currentJobIndex].OutputFilename);

                // Update log.
                _log.AppendLine("==> Matroska Packager Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Packaging Completed" });
            }

            _mkvmerge = null;
            FinalizeJobRun();
        }

        void ExecuteMkvmerge_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Packaging...  Matroska,  " + _mkvmerge.ProgressPercent.ToString() + "%" });
        }

        void ExecuteMp4box_Completed(object sender, EventArgs e)
        {
            _jobs[_currentJobIndex].Status = _mp4box.Status;

            if (_mp4box.Status != FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                // Remove original video file, rename packaged caontainer file to original filename.
                System.IO.File.Delete(_jobs[_currentJobIndex].OutputFilename);
                System.IO.File.Move(_jobs[_currentJobIndex].OutputFilename + ".tmp", _jobs[_currentJobIndex].OutputFilename);

                // Update log.
                _log.AppendLine("==> MPEG-4 Packager Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Packaging Completed" });
            }

            _mp4box = null;
            FinalizeJobRun();
        }

        void ExecuteMp4box_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Packaging...  MPEG-4 Video,  " + _mp4box.ProgressPercent.ToString() + "%" });
        }

        void ExecuteNeroAacEncoder_Completed(object sender, EventArgs e)
        {
            if (_neroAacEncoder.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                _jobs[_currentJobIndex].Status = _neroAacEncoder.Status;
                FinalizeJobRun();
                _neroAacEncoder = null;
                return;
            }

            _neroAacEncoder = null;

            // Update log.
            _log.AppendLine("==> AAC Encoder Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");

            // Delete temporary work files.
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Ac3)
            {
                string deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename, "avs");
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }

                deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                    Utility.GetAudioFormatExtension(AudioFormat.Wav));
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }
            }

            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Completed" });

            _log.AppendLine("==> Audio Track " + (_currentAudioTrackNumber + 1).ToString() +
                " of " + _jobs[_currentJobIndex].AudioTracks.Count.ToString() + " Encoded");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            DoNextAudioTrack();
        }

        void ExecuteNeroAacEncoder_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Encoding Audio...  AAC,  " + _neroAacEncoder.ProgressSeconds.ToString() + " Seconds"});
        }

        void ExecuteOggEncoder_Completed(object sender, EventArgs e)
        {
            if (_oggEncoder.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                _jobs[_currentJobIndex].Status = _oggEncoder.Status;
                FinalizeJobRun();
                _oggEncoder = null;
                return;
            }

            _oggEncoder = null;

            // Update log.
            _log.AppendLine("==> Vorbis Encoder Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");

            // Delete temporary work files.
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Ac3)
            {
                string deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename, "avs");
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }

                deleteFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                    Utility.GetAudioFormatExtension(AudioFormat.Wav));
                if (System.IO.File.Exists(deleteFilename))
                {
                    System.IO.File.Delete(deleteFilename);
                    _log.AppendLine("Delete: " + deleteFilename);
                }
            }

            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Completed" });

            _log.AppendLine("==> Audio Track " + (_currentAudioTrackNumber + 1).ToString() +
                " of " + _jobs[_currentJobIndex].AudioTracks.Count.ToString() + " Encoded");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            DoNextAudioTrack();
        }

        void ExecuteOggEncoder_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Encoding Audio...  Vorbis,  " + _oggEncoder.ProgressPercent.ToString("0.0") + "%,  " +
                    _oggEncoder.TimeRemaining.ToString() + " Remaining" });
        }

        void ExecuteWavi_Completed(object sender, EventArgs e)
        {
            if (_wavi.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                _wavi.Status == FicSprout.Bacicworx.IO.JobStatus.Failed)
            {
                _jobs[_currentJobIndex].Status = _wavi.Status;
                FinalizeJobRun();
                _wavi = null;
                return;
            }

            _wavi = null;

            // Update log.
            _log.AppendLine("==> WAV Converter Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Conversion Completed" });

            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Aac)
            {
                ExecuteNeroAacEncoder();
            }
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Flac)
            {
                ExecuteFlacEncoder();
            }
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Vorbis)
            {
                ExecuteOggEncoder();
            }
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Wav)
            {
                DoNextAudioTrack();
            }
        }

        void ExecuteX264_Completed(object sender, EventArgs e)
        {
            if (_x264.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                _jobs[_currentJobIndex].Status = _x264.Status;
                FinalizeJobRun();
                _x264 = null;
                return;
            }

            // Copy output video height and width.
            _jobs[_currentJobIndex].OutputHeight = _x264.OutputHeight;
            _jobs[_currentJobIndex].OutputWidth = _x264.OutputWidth;

            if (_jobs[_currentJobIndex].AudioTracks.Count == 0)
            {
                _jobs[_currentJobIndex].Status = _x264.Status;
            }

            _x264 = null;

            // Update log.
            _log.AppendLine("==> H.264 Encoder Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            // In rare cases a video will have field transitions which cause the IVTC filter to
            // quit. The filter writes a corrected 'd2v' file which must be re-run as input to
            // x264.

            // Corrected 'd2v' file found?                
            string d2vFilename = System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].OutputFilename, "d2v");
            if (System.IO.File.Exists(d2vFilename.Replace(".d2v", "-FIXED.d2v")))
            {
                // Update log.
                _log.AppendLine("==> Field Transitions Detected In Source Video!");
                _log.AppendLine("Index File Updated. Restarting H.264 Encoder.");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                // Delete the 'd2v' file and rename the corrected one to the old 'd2v' filename.
                System.IO.File.Delete(d2vFilename);
                System.IO.File.Move(d2vFilename.Replace(".d2v", "-FIXED.d2v"), d2vFilename);

                _rerunX264 = true;
                ExecuteX264();
            }
            else
            {
                Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Completed" });

                // Audio tracks to encode?
                if (_jobs[_currentJobIndex].AudioTracks.Count > 0)
                {
                    _currentAudioTrackNumber = 0;
  
                    // No conversion
                    if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format ==
                        _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)
                    {
                        // Update log.
                        _log.Append("==> No Audio Conversion, " + _jobs[_currentJobIndex].AudioTracks.Count.ToString() + " Audio " +
                            FicSprout.Bacicworx.Text.Utility.Pluralize("Track", "Tracks", _jobs[_currentJobIndex].AudioTracks.Count));
                        _log.AppendLine();
                        _log.AppendLine();
                        _log.AppendLine();
                        _log.UpdateLog();

                        // Package the video and audio.
                        if (_config.ContainerFormat == OutputContainerFormat.Matroska)
                        {
                            ExecuteMkvmerge();
                        }
                        if (_config.ContainerFormat == OutputContainerFormat.Mpeg4)
                        {
                            ExecuteMp4box();
                        }
                    }
                    else
                    {
                        // Conversion to AAC/FLAC/Vorbis format.
                        ExecuteWavi();
                    }
                }
                else
                {
                    FinalizeJobRun();
                }
            }
        }

        void ExecuteX264_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { 
                "Encoding Video...  H.264,  " + _x264.ProgressPercent.ToString("0.0") + "%,  " +
                    _x264.TimeRemaining.ToString() + " Remaining" });
        }

        void ExecuteX264Again_Completed(object sender, EventArgs e)
        {
            _rerunX264 = false;
            if (_x264.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled)
            {
                _jobs[_currentJobIndex].Status = _x264.Status;
                FinalizeJobRun();
                _x264 = null;
                return;
            }

            // Copy output video height and width.
            _jobs[_currentJobIndex].OutputHeight = _x264.OutputHeight;
            _jobs[_currentJobIndex].OutputWidth = _x264.OutputWidth;

            if (_jobs[_currentJobIndex].AudioTracks.Count == 0)
            {
                _jobs[_currentJobIndex].Status = _x264.Status;
            }

            _x264 = null;

            // Update log.
            _log.AppendLine("==> H.264 Encoder Completed (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Completed" });

            // Audio tracks to encode?
            if (_jobs[_currentJobIndex].AudioTracks.Count > 0)
            {
                _currentAudioTrackNumber = 0;

                // No conversion
                if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format ==
                    _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)
                {
                    // Update log.
                    _log.Append("==> No Audio Conversion, " + _jobs[_currentJobIndex].AudioTracks.Count.ToString() + " Audio " +
                        FicSprout.Bacicworx.Text.Utility.Pluralize("Track", "Tracks", _jobs[_currentJobIndex].AudioTracks.Count));
                    _log.AppendLine();
                    _log.AppendLine();
                    _log.AppendLine();
                    _log.UpdateLog();

                    // Package the video and audio.
                    if (_config.ContainerFormat == OutputContainerFormat.Matroska)
                    {
                        ExecuteMkvmerge();
                    }
                    if (_config.ContainerFormat == OutputContainerFormat.Mpeg4)
                    {
                        ExecuteMp4box();
                    }
                }
                else
                {
                    // Conversion to AAC/FLAC/Vorbis format.
                    ExecuteWavi();
                }
            }
            else
            {
                FinalizeJobRun();
            }
        }

        void WorkerStarted(object sender, EventArgs e)
        {
            Invoke(new UpdateJobInListViewFromThread(UpdateJobInListView), new object[] { _currentJobIndex });
            Invoke(new UpdateMenusFromThread(UpdateMenus));
        }

        #endregion

        #region Tool Methods

        /// <summary>
        /// Increments the current audio track index and executes tools as needed.
        /// </summary>
        private void DoNextAudioTrack()
        {
            // Increment audio track number.
            _currentAudioTrackNumber++;

            // All tracks encoded? End audio encoding (set to -1);
            if (_currentAudioTrackNumber == _jobs[_currentJobIndex].AudioTracks.Count)
            {
                _log.AppendLine("==> End Audio Encoding  (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                _currentAudioTrackNumber = -1;

                // Package the video and audio.
                if (_config.ContainerFormat == OutputContainerFormat.Matroska)
                {
                    ExecuteMkvmerge();
                }
                if (_config.ContainerFormat == OutputContainerFormat.Mpeg4)
                {
                    ExecuteMp4box();
                }
            }
            else
            {
                // More tracks to encode.
                ExecuteWavi();
            }
        }

        /// <summary>
        /// Runs DGIndex to index the video.
        /// </summary>
        private void ExecuteDgindex()
        {
            _dgindex = new DgindexBackgroundWorker(Application.StartupPath + "\\Tools\\DGIndex.exe",
                _jobs[_currentJobIndex].SourceFilename, false);

            // Update log.
            _log.AppendLine("==> Video Indexing Started (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Indexing Video... This may take a few minutes." });

            _dgindex.Completed += new EventHandler(ExecuteDgindex_Completed);
            _dgindex.Progress += new EventHandler(ExecuteDgindex_Progress);
            _dgindex.Started += new EventHandler(WorkerStarted);
            _dgindex.Start();
        }

        /// <summary>
        /// Runs FLAC Encoder to encode the WAV.
        /// </summary>
        private void ExecuteFlacEncoder()
        {
            _flacEncoder = new FicSprout.Bacicworx.Media.Flac.FlacEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\flac.exe",
                FicSprout.Bacicworx.Media.Flac.Utility.GenerateArguments(
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)),
                _config.AudioEncoderQualityOption));

            // Update log.
            _log.AppendLine("==> Start FLAC Encoder (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + _flacEncoder.Arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Audio..." });
            _flacEncoder.Progress += new EventHandler(ExecuteFlacEncoder_Progress);
            _flacEncoder.Completed += new EventHandler(ExecuteFlacEncoder_Completed);
            _flacEncoder.Started += new EventHandler(WorkerStarted);
            _flacEncoder.Start();
        }

        /// <summary>
        /// Runs MvkMerge to package the video and audio.
        /// </summary>
        private void ExecuteMkvmerge()
        {
            _mkvmerge = new FicSprout.Bacicworx.Media.Matroska.MkvmergeBackgroundWorker(Application.StartupPath + "\\Tools\\mkvmerge.exe",
                _jobs[_currentJobIndex].OutputFilename, _jobs[_currentJobIndex].OutputFilename + ".tmp",
                _jobs[_currentJobIndex].OutputWidth, _jobs[_currentJobIndex].OutputHeight,
                _jobs[_currentJobIndex].OutputFrameRate, _jobs[_currentJobIndex].GetAudoTrackOutputFilenames(),
                _jobs[_currentJobIndex].GetAudoTrackDelays(_config.AudioDelay));

            // Update log.
            _log.AppendLine("==> Start Matroska Packager (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + _mkvmerge.Arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Packaging..." });

            _mkvmerge.Progress += new EventHandler(ExecuteMkvmerge_Progress);
            _mkvmerge.Completed += new EventHandler(ExecuteMkvmerge_Completed);
            _mkvmerge.Started += new EventHandler(WorkerStarted);
            _mkvmerge.Start();
        }

        /// <summary>
        /// Runs MP4Box to package the video and audio.
        /// </summary>
        private void ExecuteMp4box()
        {
            _mp4box = new Mp4boxBackgroundWorker(Application.StartupPath + "\\Tools\\MP4Box.exe",
                _jobs[_currentJobIndex].OutputFilename, _jobs[_currentJobIndex].OutputFilename + ".tmp",
                _jobs[_currentJobIndex].OutputWidth, _jobs[_currentJobIndex].OutputHeight,
                _jobs[_currentJobIndex].OutputFrameRate, _jobs[_currentJobIndex].GetAudoTrackOutputFilenames(),
                _jobs[_currentJobIndex].GetAudoTrackDelays(_config.AudioDelay));

            // Update log.
            _log.AppendLine("==> Start MPEG-4 Packager (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + _mp4box.Arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Packaging..." });

            _mp4box.Progress += new EventHandler(ExecuteMp4box_Progress);
            _mp4box.Completed += new EventHandler(ExecuteMp4box_Completed);
            _mp4box.Started += new EventHandler(WorkerStarted);
            _mp4box.Start();
        }

        /// <summary>
        /// Runs Nero AAC Encoder to encode the WAV.
        /// </summary>
        private void ExecuteNeroAacEncoder()
        {
            _neroAacEncoder = new NeroAacEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\NeroAacEnc.exe",
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)),
                _config.AudioEncoderQualityOption);

            // Update log.
            _log.AppendLine("==> Start AAC Encoder (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + _neroAacEncoder.Arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Audio..." });

            _neroAacEncoder.Completed += new EventHandler(ExecuteNeroAacEncoder_Completed);
            _neroAacEncoder.Progress += new EventHandler(ExecuteNeroAacEncoder_Progress);
            _neroAacEncoder.Started += new EventHandler(WorkerStarted);
            _neroAacEncoder.Start();
        }

        /// <summary>
        /// Runs Ogg Encoder to encode the WAV.
        /// </summary>
        private void ExecuteOggEncoder()
        {
            _oggEncoder = new FicSprout.Bacicworx.Media.Vorbis.OggEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\oggenc.exe",
                FicSprout.Bacicworx.Media.Vorbis.Utility.GenerateArguments(
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                Utility.GetAudioFormatExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)),
                _config.AudioEncoderQualityOption));

            // Update log.
            _log.AppendLine("==> Start Vorbis Encoder (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + _oggEncoder.Arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Audio..." });
            _oggEncoder.Progress += new EventHandler(ExecuteOggEncoder_Progress);
            _oggEncoder.Completed += new EventHandler(ExecuteOggEncoder_Completed);
            _oggEncoder.Started += new EventHandler(WorkerStarted);
            _oggEncoder.Start();
        }

        /// <summary>
        /// Runs Wavi to convert AC3 audio to WAV.
        /// </summary>
        private void ExecuteWavi()
        {
            if (_currentAudioTrackNumber == 0)
            {
                // Update log.
                _log.AppendLine("==> Begin Audio Encoding (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();
            }

            // Update log.
            _log.AppendLine("==> Audio Track " + (_currentAudioTrackNumber + 1).ToString() +
                " of " + _jobs[_currentJobIndex].AudioTracks.Count.ToString() + ", Delay " +
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Delay.ToString() + "ms, Source: " +
                Utility.GetAudioFormatName(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format) + ", Output:" +
                Utility.GetAudioFormatName(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat));
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            if ((_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Ac3 ||
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Dts) &&
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format !=
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)
            {
                // Generate an AviSynth script and write it to the source folder.
                Utility.WriteAvisynthAudioScript(_jobs[_currentJobIndex], _currentAudioTrackNumber, _log);

                _wavi = new WaviBackgroundWorker(Application.StartupPath + "\\Tools\\wavi.exe",
                    System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename, "avs"),
                    System.IO.Path.ChangeExtension(_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Filename,
                    Utility.GetAudioFormatExtension(AudioFormat.Wav)));

                // Update log.
                _log.AppendLine("==> Start WAV Converter (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
                _log.AppendLine("Arguments: " + _wavi.Arguments);
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Converting Audio... This may take a few minutes." });

                _wavi.Completed += new EventHandler(ExecuteWavi_Completed);
                _wavi.Started += new EventHandler(WorkerStarted);
                _wavi.Start();
            }

            // Source is a WAV file, skip conversion and go directly to encoding.
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format == AudioFormat.Wav &&
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat != AudioFormat.Wav)
            {
                // Update log.
                _log.AppendLine("==> WAV Source. Skipping Conversion To WAV.");
                _log.AppendLine();
                _log.AppendLine();
                _log.UpdateLog();

                if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Aac)
                {
                    ExecuteNeroAacEncoder();
                }
                if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Flac)
                {
                    ExecuteFlacEncoder();
                }
                if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat == AudioFormat.Vorbis)
                {
                    ExecuteOggEncoder();
                }
            }

            // Source and output are same format - passthrough, no onversion or encoding.
            if (_jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].Format ==
                _jobs[_currentJobIndex].AudioTracks[_currentAudioTrackNumber].OutputFormat)
            {
                DoNextAudioTrack();
            }
        }

        /// <summary>
        /// Runs x264 to encode the video.
        /// </summary>
        private void ExecuteX264()
        {
            // Generate an AviSynth script and write it to the source folder.
            if (!_rerunX264)
            {
                Utility.WriteAvisynthVideoScript(_jobs[_currentJobIndex], _log);
            }

            // Generate arguments.
            string arguments = FicSprout.Bacicworx.Media.X264.Utility.GenerateArguments(System.IO.Path.ChangeExtension(
                _jobs[_currentJobIndex].SourceFilename, "avs"), _jobs[_currentJobIndex].OutputFilename,
                _config.VideoEncoderQualityOption, _config.X264DecoderLevel, _config.X264EncoderEfficiency,
                _config.X264InputTuning, _config.X264OutputProfile, _jobs[_currentJobIndex].SourceHeight,
                _config.SmallestFilesize, _jobs[_currentJobIndex].IsAnimation);

            // Update log.
            _log.AppendLine("==> Start H.264 Encoder (" + DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + ")");
            _log.AppendLine("Arguments: " + arguments);
            _log.AppendLine();
            _log.AppendLine();
            _log.UpdateLog();

            Invoke(new UpdateStatusBarTextFromThread(UpdateStatusBarText), new object[] { "Encoding Video..." });

            _x264 = new FicSprout.Bacicworx.Media.X264.X264BackgroundWorker(Application.StartupPath + "\\Tools\\x264.exe", arguments);

            if (!_rerunX264)
            {
                _x264.Completed += new EventHandler(ExecuteX264_Completed);
            }
            else
            {
                _x264.Completed += new EventHandler(ExecuteX264Again_Completed);
            }

            _x264.Progress += new EventHandler(ExecuteX264_Progress);
            _x264.Started += new EventHandler(WorkerStarted);
            _x264.Start();
        }

        #endregion
    }
}
