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

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

        private string _appFilename = string.Empty;
        private string _arguments = string.Empty;
        private string[] _audioDelays = new string[8];
        private DateTime _lastRaisedDateTime = DateTime.Now;
        private string _outputPath = string.Empty;
        private System.Diagnostics.Process _process = null;
        private Int32 _progressPercent = 0;
        private DemuxPgcSelection _selection = null;
        private string _sourceFilename = string.Empty;
        private FicSprout.Bacicworx.IO.JobStatus _status = FicSprout.Bacicworx.IO.JobStatus.Inactive;
        private BackgroundWorker _worker = null;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the CLI arguments.
        /// </summary>
        public string Arguments
        {
            get
            {
                return _arguments;
            }
        }
        /// <summary>
        /// Gets the output path.
        /// </summary>
        public string OutputPath
        {
            get
            {
                return _outputPath;
            }
        }
        /// <summary>
        /// Gets the percentage of the operation completed. The value range is 0 to 100.
        /// </summary>
        public Int32 ProgressPercent
        {
            get
            {
                return _progressPercent;
            }
        }
        /// <summary>
        /// Gets the source video filename.
        /// </summary>
        public string SourceFilename
        {
            get
            {
                return _sourceFilename;
            }
        }
        /// <summary>
        /// Gets the status of the worker.
        /// </summary>
        public FicSprout.Bacicworx.IO.JobStatus Status
        {
            get
            {
                return _status;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the PgcdemuxBackgroundWorker class to demux vob streams.
        /// </summary>
        /// <param name="appFilename">The application path including the filename.</param>
        /// <param name="source">The IFO filename.</param>
        /// <param name="output">The output folder for the demuxed streams.</param>
        /// <param name="selection">A DemuxPgcSelection instance containing required demux information.</param>
        /// <exception cref="System.ArgumentException">Thrown when appFilename is null or empty.</exception>
        /// <exception cref="System.ArgumentException">Thrown when source is null or empty.</exception>
        /// <exception cref="System.ArgumentException">Thrown when output is null or empty.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when appFilename is not found.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when source is not found.</exception>
        /// <exception cref="System.IO.FileNotFoundException">Thrown when output is not found.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when selection is null.</exception>
        public PgcdemuxBackgroundWorker(string appFilename, string source, string output, DemuxPgcSelection selection)
        {
            if (string.IsNullOrEmpty(appFilename))
            {
                throw new ArgumentException("appFilename is null or empty.");
            }
            if (string.IsNullOrEmpty(source))
            {
                throw new ArgumentException("source is null or empty.");
            }
            if (string.IsNullOrEmpty(output))
            {
                throw new ArgumentException("output is null or empty.");
            }
            if (!System.IO.File.Exists(appFilename))
            {
                throw new System.IO.FileNotFoundException("'" + appFilename + "' not found.");
            }
            if (!System.IO.Directory.Exists(output))
            {
                throw new System.IO.FileNotFoundException("'" + output + "' not found.");
            }
            if (selection == null)
            {
                throw new ArgumentNullException("selection");
            }

            _appFilename = appFilename;
            _outputPath = output;
            _selection = selection;
            _sourceFilename = source;

            // "pgcdemux -pgc #pgcNumber -ang #angleNumber -m2v -aud -sub "input.ifo" "drive:\Output\Path"
            StringBuilder sb = new StringBuilder();

            sb.Append("-pgc " + _selection.Pgc.ToString());
            if (_selection.Angle != 1)
            {
                sb.Append(" -ang " + _selection.Angle.ToString());
            }
            if (!string.IsNullOrEmpty(_selection.VideoID))
            {
                sb.Append(" -m2v");
            }
            if (_selection.AudioIDs.Length > 0)
            {
                sb.Append(" -aud");
            }
            if (_selection.SubtitleIDs.Length > 0)
            {
                sb.Append(" -sub");
            }
            sb.Append(" \"" + _sourceFilename + "\" \"" + _outputPath + "\"");

            _arguments = sb.ToString();
        }

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

                _worker.CancelAsync();

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

        /// <summary>
        /// Deletes any temporary files.
        /// </summary>
        private void CleanupFiles()
        {
            if (System.IO.File.Exists(_outputPath + "\\LogFile.txt"))
            {
                System.IO.File.Delete(_outputPath + "\\LogFile.txt");
            }
            if (System.IO.File.Exists(_outputPath + "\\Celltimes.txt"))
            {
                System.IO.File.Delete(_outputPath + "\\Celltimes.txt");
            }
            if (System.IO.File.Exists(_outputPath + "\\VideoFile.m2v"))
            {
                System.IO.File.Delete(_outputPath + "\\VideoFile.m2v");
            }

            string[] files = System.IO.Directory.GetFiles(_outputPath, "AudioFile_*.*");
            for (int i = 0; i < files.Length; i++)
            {
                System.IO.File.Delete(files[i]);
            }
            files = null;

            files = System.IO.Directory.GetFiles(_outputPath, "Subpictures_*.*");
            for (int i = 0; i < files.Length; i++)
            {
                System.IO.File.Delete(files[i]);
            }
            files = null;
        }

        /// <summary>
        /// Parses the log file.
        /// </summary>
        private void ParseLog()
        {
            // Initiialize the array.
            for (int i = 0; i < _audioDelays.Length; i++)
            {
                _audioDelays[i] = "0";
            }

            // Load the log.
            string[] lines = System.IO.File.ReadAllLines(_outputPath + "\\LogFile.txt");
            bool isAudioDelaysSection = false;
            Int32 index = 0;

            // Parse each line.
            for (int i = 0; i < lines.Length; i++)
            {
                // Aspect Ratio
                if (lines[i].StartsWith("[Audio Delays]"))
                {
                    isAudioDelaysSection = true;
                    continue;
                }

                if (lines[i].StartsWith("[Subs Streams]"))
                {
                    isAudioDelaysSection = false;
                    continue;
                }

                if (isAudioDelaysSection && lines[i].StartsWith("Audio_"))
                {
                    // Audio_1=-9
                    // Audio_1=None
                    string[] values = lines[i].Split(new char[] { '=' });
                    if (values.Length == 2)
                    {
                        _audioDelays[index] = values[1];
                        index++;
                    }
                }
            }
        }

        /// <summary>
        /// Starts the worker.
        /// </summary>
        public void Start()
        {
            _worker = new BackgroundWorker();

            _worker.WorkerSupportsCancellation = true;
            _worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            _worker.RunWorkerAsync();
        }

        #endregion

        #region Published Events

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

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

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

        #endregion

        #region Subscribed Events

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

            // Start the process.
            _process = new System.Diagnostics.Process();
            _process.StartInfo.CreateNoWindow = true;
            _process.StartInfo.FileName = _appFilename;

            _process.StartInfo.Arguments = _arguments;
            _process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
            _process.StartInfo.UseShellExecute = false;

            // Start the process and wait for it to finish.
            _process.Start();
            _process.WaitForExit();

            // Cancelled? Failure? Cleanup folder and exit.
            if (_status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                !System.IO.File.Exists(_outputPath + "\\LogFile.txt"))
            {
                _status = FicSprout.Bacicworx.IO.JobStatus.Failed;

                // Delete output files from the destination folder.
                CleanupFiles();

                string[] files = System.IO.Directory.GetFiles(_outputPath, "VTS_" + _selection.Titleset.ToString("00") +
                    "_PGC_" + _selection.Pgc.ToString("00") + " - Chapter Information - *.txt");
                for (int i = 0; i < files.Length; i++)
                {
                    System.IO.File.Delete(files[i]);
                }
                files = null;

                OnCompleted();
                return;
            }

            // Completed demux. Parse log.
            _status = FicSprout.Bacicworx.IO.JobStatus.Completed;
            ParseLog();

            if (!string.IsNullOrEmpty(_selection.VideoID))
            {
                // Rename Video file: 
                // From: VideoFile.m2v
                // TO:   VTS_02_PGC_01_1 - 0xE0 - VIDEO - MPEG-2 (NTSC) - 720x480 - 4~3 Fullscreen.m2v
                if (System.IO.File.Exists(_outputPath + "\\VideoFile.m2v"))
                {
                    FicSprout.Bacicworx.IO.Utility.RenameFile(_outputPath + "\\VideoFile.m2v", _outputPath + "\\" + _selection.VideoOutputFilename, true);
                }
            }

            if (_selection.AudioIDs.Length > 0)
            {
                // Rename selected audio files...
                // From: AudioFile_80.ac3
                // TO:   VTS_02_PGC_01_1 - 0x80 - AUDIO - AC3 - 2ch - 48kHz - DRC - English -Director's Comments - DELAY 0ms.AC3
                for (int i = 0; i < _selection.AudioIDs.Length; i++)
                {
                    string source = _outputPath + "\\AudioFile_" + _selection.AudioIDs[i];
                    string output = _outputPath + "\\" + _selection.AudioOutputFilenames[i].Replace("%DELAY%",
                            _audioDelays[(Convert.ToInt32(_selection.AudioIDs[i]) & 0x7)].ToString());

                    if (System.IO.File.Exists(source + System.IO.Path.GetExtension(output)))
                    {
                        FicSprout.Bacicworx.IO.Utility.RenameFile(source + System.IO.Path.GetExtension(output), output, true);
                    }
                    else
                    {
                        // Zero length files are not demuxed. Create an empty file.
                        System.IO.File.Create(output);
                    }
                }
            }

            if (_selection.SubtitleIDs.Length > 0)
            {
                // Rename selected subtitle files...
                // From: Subpictures_20.sup
                // TO:   VTS_02_PGC_01_1 - 0x20 - SUBTITLE - Widescreen - English - Director's Comments.sup
                for (int i = 0; i < _selection.SubtitleIDs.Length; i++)
                {
                    if (System.IO.File.Exists(_outputPath + "\\Subpictures_" + _selection.SubtitleIDs[i] + ".sup"))
                    {
                        FicSprout.Bacicworx.IO.Utility.RenameFile(_outputPath + "\\Subpictures_" + _selection.SubtitleIDs[i] + ".sup",
                            _outputPath + "\\" + _selection.SubtitleOutputFilenames[i], true);
                    }
                    else
                    {
                        // Zero length files are not demuxed. Create an empty file.
                        System.IO.File.Create(_outputPath + "\\" + _selection.SubtitleOutputFilenames[i]);
                    }
                }
            }

            // Any unselected streams were not renamed. Delete them.
            CleanupFiles();

            OnCompleted();
        }

        #endregion
    }
}
