using System;
using System.Collections.Generic;
using System.Text;

/*
    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.Dvd
{
    /// <summary>
    /// Demuxes the video, audio, and subpicture streams from the DVD.
    /// </summary>
    sealed public class Demuxer
    {
        #region Fields

        private Int32[] _audioDelays = new Int32[8];
        private Int32[] _audioIds = new Int32[8];
        private Int32 _audioPackets = 0;
        private Int64[] _audioTimeStamps = new Int64[8];
        private Boolean _cancelled = false;
        private Int32 _currentIndex = 0;
        private List<string> _damagedPrograms = new List<string>();
        private Dvd.IfoReader _dvd = null;
        private Boolean _hasExited = false;
        private Int32 _indexCount = 0;
        private Int32 _navigationPackets = 0;
        private string _outputPath = string.Empty;
        private Int32 _paddingPackets = 0;
        private Boolean _paused = false;
        private Int32 _programChain = 0;
        private Int64 _programChainLength = 0;
        private Int64 _programChainProcessedLength = 0;
        private Int32 _programChainProgress = 0;
        private Timecode _programChainTimeRemaining = new Timecode();
        private Int32[] _subpictureDelays = new Int32[32];
        private Int32[] _subpictureIds = new Int32[32];
        private Int32 _subpicturePackets = 0;
        private Int64[] _subpictureTimeStamps = new Int64[32];
        private Int32 _titleSet = 0;
        private Int32 _totalProgress = 0;
        private Int32 _unknownPackets = 0;
        private string _videoFilename = string.Empty;
        private Int32 _videoPackets = 0;
        private string _vobFilename = string.Empty;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the audio stream delays in milliseconds. The array contains eight items that are synced
        /// to the AudioIds property. Any AudioId with a value of zero indicates that that stream is not
        /// present and the delay value is invalid.
        /// </summary>
        public Int32[] AudioDelays
        {
            get
            {
                return _audioDelays;
            }
        }
        /// <summary>
        /// Gets the audio stream IDs. The array contains eight items. Any item with
        /// a value of zero indicates that that stream is not present.
        /// 
        /// </summary>
        public Int32[] AudioIds
        {
            get
            {
                return _audioIds;
            }
        }
        /// <summary>
        /// Gets the number of audio packets detected.
        /// </summary>
        public Int32 AudioPackets
        {
            get
            {
                return _audioPackets;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the demuxer was cancelled.
        /// </summary>
        public Boolean Cancelled
        {
            get
            {
                return _cancelled;
            }
        }
        /// <summary>
        /// Gets a list of programs that have damaged packets. If none were detected, an empty
        /// list is returned.
        /// </summary>
        public List<string> DamagedPrograms
        {
            get
            {
                return _damagedPrograms;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the worker has exited.
        /// </summary>
        public Boolean HasExited
        {
            get
            {
                return _hasExited;
            }
        }
        /// <summary>
        /// Gets the number of navigation packets detected.
        /// </summary>
        public Int32 NavigationPackets
        {
            get
            {
                return _navigationPackets;
            }
        }
        /// <summary>
        /// Gets the number of padding packets detected.
        /// </summary>
        public Int32 PaddingPackets
        {
            get
            {
                return _paddingPackets;
            }
        }
        /// <summary>
        /// Gets a value indicating whether the demuxer is paused.
        /// </summary>
        public Boolean Paused
        {
            get
            {
                return _paused;
            }
        }
        /// <summary>
        /// Gets the id of the program chain currently being processed. If Inactive, zero is returned.
        /// </summary>
        public Int32 ProgramChain
        {
            get
            {
                return _programChain;
            }
        }
        /// <summary>
        /// Gets the length in bytes of the current program chain. If Inactive, zero is returned.
        /// </summary>
        public Int64 ProgramChainLength
        {
            get
            {
                return _programChainLength;
            }
        }
        /// <summary>
        /// Gets the number in bytes that have been processed for the current program chain. If
        /// Inactive, zero is returned.
        /// </summary>
        public Int64 ProgramChainProcessedLength
        {
            get
            {
                return _programChainProcessedLength;
            }
        }
        /// <summary>
        /// Gets the progress percentage for the current program chain. If
        /// Inactive, zero is returned.
        /// </summary>
        public Int32 ProgramChainProgress
        {
            get
            {
                return _programChainProgress;
            }
        }
        /// <summary>
        /// Gets the time remaining for completion of the current program chain. If
        /// Inactive, zero is returned.
        /// </summary>
        public Timecode ProgramChainTimeRemaining
        {
            get
            {
                return _programChainTimeRemaining;
            }
        }
        /// <summary>
        /// Gets the subpicture stream delays in milliseconds. The array contains thirty-two items
        /// that are synced to the SubpictureIds property. Any SubpictureId with a value of zero
        /// indicates that that stream is not present and the delay value is invalid.
        /// </summary>
        public Int32[] SubpictureDelays
        {
            get
            {
                return _subpictureDelays;
            }
        }
        /// <summary>
        /// Gets the subpicture stream IDs. The array contains thirty-two items. Any item with
        /// a value of zero indicates that that stream is not present.
        /// 
        /// </summary>
        public Int32[] SubpictureIds
        {
            get
            {
                return _subpictureIds;
            }
        }
        /// <summary>
        /// Gets the number of subpicture packets detected.
        /// </summary>
        public Int32 SubpicturePackets
        {
            get
            {
                return _subpicturePackets;
            }
        }
        /// <summary>
        /// Gets the id of the title set currently being processed. If Inactive, zero is returned.
        /// </summary>
        public Int32 TitleSet
        {
            get
            {
                return _titleSet;
            }
        }
        /// <summary>
        /// Gets the total number of packets detected.
        /// </summary>
        public Int32 TotalPackets
        {
            get
            {
                return _audioPackets + _navigationPackets + _paddingPackets + _subpicturePackets +
                    _unknownPackets + _videoPackets;
            }
        }
        /// <summary>
        /// Gets the total progress percentage based on the number of programs chains that
        /// have been processed. If Inactive, zero is returned.
        /// </summary>
        public Int32 TotalProgress
        {
            get
            {
                return _totalProgress;
            }
        }
        /// <summary>
        /// Gets the number of unknown packets detected. The value should be zero.
        /// </summary>
        public Int32 UnknownPackets
        {
            get
            {
                return _unknownPackets;
            }
        }
        /// <summary>
        /// Gets the output filename of the video currently being processed. If Inactive,
        /// an empty string is returned.
        /// </summary>
        public string VideoFilename
        {
            get
            {
                return _videoFilename;
            }
        }
        /// <summary>
        /// Gets the number of video packets detected.
        /// </summary>
        public Int32 VideoPackets
        {
            get
            {
                return _videoPackets;
            }
        }
        /// <summary>
        /// Gets the filename of the vob currently being processed. If Inactive,
        /// an empty string is returned.
        /// </summary>
        public string VobFilename
        {
            get
            {
                return _vobFilename;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the DVD Demuxer class.
        /// </summary>
        /// <param name="dvd">A InfoReader instance contianing information about the currently open DVD.</param>
        /// <param name="path">The path that the files will be demuxed to.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when dvd is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when path is null or empty.</exception>
        public Demuxer(Dvd.IfoReader dvd, string path)
        {
            if (dvd == null)
            {
                throw new ArgumentNullException("dvd");
            }
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException("path is null or empty.");
            }

            _dvd = dvd;
            _outputPath = path;
        }

        /// <summary>
        /// Cancels the demux operation.
        /// </summary>
        public void Cancel()
        {
            _cancelled = true;
        }

        /// <summary>
        /// Returns the absolute byte offset into 'lengths' which coincides with the
        /// vob part filename that contains the sector.
        /// </summary>
        /// <param name="lengths">A list containing the vob part lengths.</param>
        /// <param name="vobPartId">An index into 'lengths' which coincides with the
        /// vob part filename which contains the sector.</param>
        /// <param name="sector">The sector to find.</param>
        /// <returns>The index into 'filenames'0.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when lengths is null.</exception>
        private Int64 ComputeVobOffsetFromSector(List<Int64> lengths, Int64 vobPartId, Int64 sector)
        {
            if (lengths == null)
            {
                throw new ArgumentNullException("lengths");
            }

            // Get the absolute offset into the vob file parts.
            Int64 offset = sector * 0x800;

            // Offset in first vob?
            if (offset < lengths[0])
            {
                return Convert.ToInt32(offset);
            }

            // find offset.
            Int64 bytes = 0;
            for (Int32 i = 0; i < vobPartId; i++)
            {
                bytes += lengths[i];
            }

            return Convert.ToInt64(offset - bytes);
        }

        /// <summary>
        /// Returns an index into 'lengths' which coincides with the vob part filename
        /// that contains the sector.
        /// </summary>
        /// <param name="lengths">A list containing the vob part lengths.</param>
        /// <param name="sector">The sector to find.</param>
        /// <returns>The index into 'filenames'.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when lengths is null.</exception>
        private Int32 ComputeVobPartIdFromSector(List<Int64> lengths, Int64 sector)
        {
            if (lengths == null)
            {
                throw new ArgumentNullException("lengths");
            }

            Int64 result = 0;

            // Get the absolute offset into the vob file parts.
            Int64 offset = sector * 0x800;
            Int64 bytes = 0;

            for (Int32 i = 0; i < lengths.Count; i++)
            {
                bytes += lengths[i];
                if (bytes > offset)
                {
                    result = i;
                    break;
                }
            }

            return Convert.ToInt32(result);
        }

        /// <summary>
        /// Demuxes the selected programs and streams within the specified program chain.
        /// </summary>
        /// <param name="vts">The id of the title set that the program chain belongs to.</param>
        /// <param name="pgc">The id of the program chain to demux.</param>
        /// <param name="path">The folder where the files will be demuxed.</param>
        /// <exception cref="System.ArgumentException">Thrown when path is null or empty.</exception>
        private void DemuxProgramChain(Int32 vts, Int32 pgc, string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new ArgumentException("path is null or empty.");
            }

            // Nothing selected in the pgc?
            if (!_dvd.TitleSets[vts].ProgramChains[pgc].Checked)
            {
                return;
            }

            // Titlesets are split into 1 GB vob files. A program chain may span one or more
            // vobs. The simplest way to access the files is with a FileStream array and
            // use an offset to determine which vob to read from. Since a 1 GB vob can contain
            // at most 524287 sectors (1 GB vob / 2KB in a sector), calculating the offset
            // is straightforward. Any unused array members are null.


            //
            // Create arrays to manage the FileStreams for the output files.
            // Maximum buffer size is 96MB (video: 32MB, audio: 8 x 4MB, subpicture: 32 x 1MB)
            // Average buffer usage: 36-48MB
            //
            System.IO.FileStream outFileVideo = null;
            System.IO.FileStream[] outFileAudio = new System.IO.FileStream[8];
            System.IO.FileStream[] outFileSubtitle = new System.IO.FileStream[32];

            // Overwrite output file.
            if (_dvd.TitleSets[vts].ProgramChains[pgc].VideoChecked)
            {
                if (System.IO.File.Exists(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].VideoOutputFilename))
                {
                    System.IO.File.Delete(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].VideoOutputFilename);
                }

                // Create an ouput stream with a 32 MB write buffer.
                outFileVideo = new System.IO.FileStream(path + "\\" +
                    _dvd.TitleSets[vts].ProgramChains[pgc].VideoOutputFilename,
                    System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Write, 0x2000000);
            }

            // Only the audio streams to be demuxed have FileStreams created. All others are null.
            for (Int32 i = 0; i < _dvd.TitleSets[vts].ProgramChains[pgc].AudioStreamRefs.Length; i++)
            {
                if (_dvd.TitleSets[vts].ProgramChains[pgc].AudioStreamRefs[i].Checked)
                {
                    // Overwrite output file.
                    if (System.IO.File.Exists(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatAudioOutputFilename(i)))
                    {
                        System.IO.File.Delete(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatAudioOutputFilename(i));
                    }

                    // Create an ouput stream with a 4 MB write buffer.
                    outFileAudio[i] = new System.IO.FileStream(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatAudioOutputFilename(i),
                        System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Write, 0x400000);
                }
            }

            // Only the subpicture streams to be demuxed have FileStreams created. All others are null.
            for (Int32 i = 0; i < _dvd.TitleSets[vts].ProgramChains[pgc].Subtitles.Length; i++)
            {
                if (_dvd.TitleSets[vts].ProgramChains[pgc].Subtitles[i].Checked)
                {
                    // Overwrite output file.
                    if (System.IO.File.Exists(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatSubtitleOutputFilename(i)))
                    {
                        System.IO.File.Delete(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatSubtitleOutputFilename(i));
                    }

                    // Create an ouput stream with a 1 MB write buffer.
                    outFileSubtitle[i] = new System.IO.FileStream(path + "\\" + _dvd.TitleSets[vts].ProgramChains[pgc].FormatSubtitleOutputFilename(i),
                        System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.Write, 0x100000);
                }
            }

            // Init
            byte[] buffer = new byte[2048];
            Int32 currentVobPartId = -1;
            Int64 currentVobLength = 0;
            Int64 currentVobPosition = 0;
            Boolean foundNavPack = false;
            Int64 navInitialPresentationStartTimeStamp = 0;
            Int64 navPresentationEndTimeStamp = 0;
            Int64 navPreviousPresentationEndTimeStamp = 0;
            Int32 nextVobPartId = ComputeVobPartIdFromSector(_dvd.TitleSets[vts].VobLengths,
                _dvd.TitleSets[vts].ProgramChains[pgc].Cells[0].StartSector);
            _programChain = _dvd.TitleSets[vts].ProgramChains[pgc].Id;
            _programChainLength = _dvd.TitleSets[vts].ProgramChains[pgc].Length;
            _programChainProcessedLength = 0;
            _programChainProgress = 0;
            DateTime programChainStartTime = DateTime.Now;
            double tempValue = 0.0;
            Int64 timeStampOffset = 0;
            _titleSet = _dvd.TitleSets[vts].Id;
            _vobFilename = string.Empty;
            System.IO.FileStream vob = null;

            try
            {
                // Loop through all programs in the pgc.
                for (Int64 i = 0; i < _dvd.TitleSets[vts].ProgramChains[pgc].Programs.Length; i++)
                {
                    // Program not selected? Skip to next program.
                    if (!_dvd.TitleSets[vts].ProgramChains[pgc].Programs[i].Checked)
                    {
                        continue;
                    }

                    // Loop through all cells in the program.
                    for (Int64 j = _dvd.TitleSets[vts].ProgramChains[pgc].Programs[i].StartCell - 1;
                        j < _dvd.TitleSets[vts].ProgramChains[pgc].Programs[i].EndCell; j++)
                    {

                        nextVobPartId = ComputeVobPartIdFromSector(_dvd.TitleSets[vts].VobLengths,
                            _dvd.TitleSets[vts].ProgramChains[pgc].Cells[j].StartSector);

                        // Loop through all sectors of each cell.
                        for (Int64 k = _dvd.TitleSets[vts].ProgramChains[pgc].Cells[j].StartSector;
                            k <= _dvd.TitleSets[vts].ProgramChains[pgc].Cells[j].EndSector; k++)
                        {
                            // Check for change of vob part # file when current position equals end of file.
                            if (currentVobPosition == currentVobLength)
                            {
                                nextVobPartId = ComputeVobPartIdFromSector(_dvd.TitleSets[vts].VobLengths, k);
                            }

                            // Is this sector in the current vob file? If not, open the appropriate vob file.
                            if (currentVobPartId != nextVobPartId)
                            {
                                // Close current vob file.
                                if (vob != null)
                                {
                                    vob.Dispose();
                                    vob = null;
                                }

                                // Open new vob file with a 32 MB read buffer.
                                vob = new System.IO.FileStream(_dvd.TitleSets[vts].VobFilenames[nextVobPartId],
                                    System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read, 0x10000);
                                _vobFilename = vob.Name;
                                currentVobLength = vob.Length;
                                currentVobPartId = nextVobPartId;
                                currentVobPosition = ComputeVobOffsetFromSector(_dvd.TitleSets[vts].VobLengths, currentVobPartId, k);
                                vob.Seek(currentVobPosition, System.IO.SeekOrigin.Begin);
                            }
                         
                            // Read sector.
                            Int64 bytesRead = vob.Read(buffer, 0, 2048);
                            currentVobPosition += bytesRead;
                            if (bytesRead != 2048)
                            {
                                //
                                // This still occurs for some media.
                                //
                                System.Windows.Forms.MessageBox.Show("Unexpected end of vob occurred.");
                                return;
                            }

                            // Has required pack header? If not found skip to next sector.
                            if ((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[03] != 0x01ba)
                            {
                                // Add damaged programs to list.
                                if (!_damagedPrograms.Contains("Title " + _dvd.TitleSets[vts].Id.ToString("00") +
                                        ", Program " + _dvd.TitleSets[vts].ProgramChains[pgc].Id.ToString("00")))
                                {
                                    _damagedPrograms.Add("Title " + _dvd.TitleSets[vts].Id.ToString("00") +
                                        ", Program " + _dvd.TitleSets[vts].ProgramChains[pgc].Id.ToString("00"));
                                }

                                continue;
                            }

                            //
                            // Parse Packets
                            //

                            TransportPacket packet = new TransportPacket(buffer, timeStampOffset);
                            // Is a system header (navigation pack)?
                            if (packet.PacketType == TransportPacketType.Navigation)
                            {
                                if (packet.NavigationVobId == _dvd.TitleSets[vts].ProgramChains[pgc].Cells[j].VobID &&
                                    packet.NavigationCellId == _dvd.TitleSets[vts].ProgramChains[pgc].Cells[j].ID)
                                {
                                    // This prevents any video, audio, or subpicture packs from being processed
                                    // until the first navigation pack is found.
                                    foundNavPack = true;
                                }

                                if (packet.HasPresentationTimeStamp)
                                {
                                    if (navInitialPresentationStartTimeStamp == 0)
                                    {
                                        navInitialPresentationStartTimeStamp = packet.NavigationPresentationStartTimeStamp;
                                    }

                                    navPreviousPresentationEndTimeStamp = navPresentationEndTimeStamp;
                                    navPresentationEndTimeStamp = packet.NavigationPresentationEndTimeStamp;

                                    // Discontinuity?
                                    if (navPreviousPresentationEndTimeStamp > packet.NavigationPresentationStartTimeStamp)
                                    {
                                        timeStampOffset += (navPreviousPresentationEndTimeStamp - packet.NavigationPresentationStartTimeStamp);
                                    }
                                }

                                _navigationPackets++;
                            }

                            // Video packet?
                            if (foundNavPack && packet.PacketType == TransportPacketType.Video)
                            {
                                if (outFileVideo != null)
                                {
                                    packet.Demux(outFileVideo);
                                }

                                _videoPackets++;
                            }

                            // Audio packet?
                            if (foundNavPack && packet.PacketType == TransportPacketType.Audio)
                            {
                                if (_audioTimeStamps[(packet.Id & 7)] == 0)
                                {
                                    // Set PTS and Id.
                                    _audioTimeStamps[(packet.Id & 7)] = packet.PresentationTimeStamp;
                                    _audioIds[(packet.Id & 0x07)] = packet.Id;

                                    // Any audio delay greater than 1000ms is incorrect. This usually occurs because
                                    // of black video or logos with silence at the start of the video. The only solution
                                    // is to repeatedly get new nav and audio PTSs and calculate the audio delay until it
                                    // is in range.
                                    //
                                    // If all this fails, a final check after demuxing will set the audio delay to zero.
                                    // We did all we could but now the user will have to fix it as best they can. :(
                                    if (navInitialPresentationStartTimeStamp > 0 && Convert.ToInt32((_audioTimeStamps[i] -
                                        navInitialPresentationStartTimeStamp) / 90) > 1000)
                                    {
                                        navInitialPresentationStartTimeStamp = 0;
                                        _audioTimeStamps[(packet.Id & 7)] = 0;
                                    }
                                }

                                if (outFileAudio[(packet.Id & 7)] != null)
                                {
                                    packet.Demux(outFileAudio[(packet.Id & 7)]);
                                }

                                _audioPackets++;
                            }

                            // Subpicture?
                            if (foundNavPack && packet.PacketType == TransportPacketType.Subpicture)
                            {
                                if (_subpictureTimeStamps[(packet.Id & 0x1f)] == 0)
                                {
                                    // Set PTS and Id.
                                    _subpictureTimeStamps[(packet.Id & 0x1f)] = packet.PresentationTimeStamp;
                                    _subpictureIds[(packet.Id & 0x1f)] = packet.Id;
                                }

                                if (outFileSubtitle[(packet.Id & 0x1f)] != null)
                                {
                                    packet.Demux(outFileSubtitle[(packet.Id & 0x1f)]);
                                }

                                _subpicturePackets++;
                            }

                            // Padding packet?
                            if (packet.PacketType == TransportPacketType.Padding)
                            {
                                _paddingPackets++;
                            }

                            // Unknown packet?
                            if (packet.PacketType == TransportPacketType.Unknown)
                            {
                                _unknownPackets++;
                            }

                            _programChainProcessedLength += packet.PayloadLength;
                            packet = null;
                            
                            // Compute progress percent.
                            tempValue = _programChainProcessedLength;
                            tempValue = (tempValue / _programChainLength) * 100;
                            // Prevent underflow.
                            if (Double.IsNaN(tempValue) || Double.IsInfinity(tempValue) || tempValue < 1)
                            {
                                tempValue = 1;
                            }
                            _programChainProgress = Convert.ToInt32(tempValue);

                            // Compute total progress.
                            tempValue = _indexCount;
                            tempValue = 1 / tempValue;
                            tempValue = tempValue * (_currentIndex + (Convert.ToDouble(_programChainProgress) / 100));
                            _totalProgress = Convert.ToInt32(tempValue * 100.0);
                                             
                            // Compute time remaining for the PGC.
                            tempValue = DateTime.Now.Subtract(programChainStartTime).TotalMilliseconds / _programChainProgress;
                            tempValue = tempValue * (100 - _programChainProgress);
                            _programChainTimeRemaining = new Timecode(0, 0, 0, Convert.ToInt32(tempValue));

                            // Paused?
                            while (_paused)
                            {
                                System.Threading.Thread.Sleep(200);

                                // Cancelled?
                                if (_cancelled)
                                {
                                    return;
                                }
                            }

                            // Cancelled?
                            if (_cancelled)
                            {
                                return;
                            }
                        }
                    }
                }
            }
            finally
            {
                if (vob != null)
                {
                    vob.Dispose();
                }

                if (outFileVideo != null)
                {
                    outFileVideo.Flush();
                    outFileVideo.Dispose();
                }

                for (Int32 i = 0; i < outFileAudio.Length; i++)
                {
                    if (outFileAudio[i] != null)
                    {
                        // Populate delays array.
                        _audioDelays[i] = Convert.ToInt32((_audioTimeStamps[i] - navInitialPresentationStartTimeStamp) / 90);
                        
                        // Delay out of range? Then we were unable to detect the correct audio delay or the video was
                        // _REALLY_ messed up. Set the delay to zero. The user will have to sort it out themselves. :(
                        if (_audioDelays[i] > 1000)
                        {
                            _audioDelays[i] = 0;
                        }
                             
                        // Flush data to file.
                        outFileAudio[i].Flush();
                        outFileAudio[i].Dispose();

                        // Rename if delay > 0ms.
                        if (_audioDelays[i] > 0)
                        {
                            // Overwrite output file.
                            if (System.IO.File.Exists(outFileAudio[i].Name.Replace("0ms", _audioDelays[i].ToString() + "ms")))
                            {
                                System.IO.File.Delete(outFileAudio[i].Name.Replace("0ms", _audioDelays[i].ToString() + "ms"));
                            }

                            System.IO.File.Move(outFileAudio[i].Name, outFileAudio[i].Name.Replace("0ms",
                                _audioDelays[i].ToString() + "ms"));
                        }
                    }
                }

                for (Int32 i = 0; i < outFileSubtitle.Length; i++)
                {
                    if (outFileSubtitle[i] != null)
                    {
                        // Populate delays array.
                        _subpictureDelays[i] = Convert.ToInt32((_subpictureTimeStamps[i]
                            - navInitialPresentationStartTimeStamp) / 90);

                        // Flush data to file.
                        outFileSubtitle[i].Flush();
                        outFileSubtitle[i].Dispose();

                        // Rename if delay > 0ms.
                        if (_audioDelays[i] > 0)
                        {

                            // Overwrite output file.
                            if (System.IO.File.Exists(outFileAudio[i].Name.Replace("0ms", _subpictureDelays[i].ToString() + "ms")))
                            {
                                System.IO.File.Delete(outFileAudio[i].Name.Replace("0ms", _subpictureDelays[i].ToString() + "ms"));
                            }

                            System.IO.File.Move(outFileSubtitle[i].Name, outFileSubtitle[i].Name.Replace("0ms",
                                _subpictureDelays[i].ToString() + "ms"));
                        }
                    }
                }

                _programChain = 0;
                _programChainLength = 0;
                _programChainProcessedLength = 0;
                _titleSet = 0;
                _vobFilename = string.Empty;
            }
        }

        /// <summary>
        /// Toggles the demuxer between pause and resume.
        /// </summary>
        public void Pause()
        {
            _paused = !_paused;
        }

        /// <summary>
        /// Starts demuxing of the selected DVD program chains.
        /// </summary>
        public void Start()
        {
            _currentIndex = 0;
            _hasExited = false;
            _indexCount = _dvd.CheckedProgramChainsCount;
            _totalProgress = 0;
            Double value = 0.0;

            for (Int32 i = 0; i < _dvd.TitleSets.Length; i++)
            {
                for (Int32 j = 0; j < _dvd.TitleSets[i].ProgramChains.Length; j++)
                {
                    if (_dvd.TitleSets[i].ProgramChains[j].Checked)
                    {
                        _videoFilename = _dvd.TitleSets[i].ProgramChains[j].VideoOutputFilename;
                        DemuxProgramChain(i, j, _outputPath);

                        value = _indexCount;
                        value = 1 / value;
                        value = value * (_currentIndex + (Convert.ToDouble(_programChainProgress) / 100));
                        _totalProgress = Convert.ToInt32(value * 100.0);
                        _currentIndex++;
                    }

                    if (_cancelled)
                    {
                        break;
                    }
                }

                if (_cancelled)
                {
                    break;
                }
            }

            _currentIndex = 0;
            _indexCount = 0;
            _programChain = 0;
            _programChainLength = 0;
            _programChainProcessedLength = 0;
            _programChainProgress = 100;
            _titleSet = 0;
            _totalProgress = 100;
            _videoFilename = string.Empty;
            _hasExited = true;
        }

        #endregion
    }
}
