using System;
using System.Text;

/*
    Copyright (c) 2011 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>
    /// SmpteTimecode represents a timecode as defined by the Society of Motion Picture and
    /// Television Engineers in the SMPTE 12M specification. SmpteTimecode has a resolution
    /// based on frames with an associated framerate. The maximum value is '99:59:59:59' at
    /// 50 fps.
    /// </summary>
    /// <remarks>
    /// 
    /// </remarks>
    sealed public class SmpteTimecode
    {
        #region Fields

        private Boolean _dropFrames = false;
        private VideoFramerate _framerate = VideoFramerate.Fps30;
        private Int32 _framerateValue = 30;
        private Int32 _frames = 0;
        private const Int32 _maximumFrames = 21599999; // 99:59:59.59 @ 60fps

        #endregion

        #region Properties

        /// <summary>
        /// Gets whether frames are droppped for the specified framerate. Only valid for
        /// Fps24 and Fps30 framerates.
        /// </summary>
        public Boolean DropFrames
        {
            get
            {
                return _dropFrames;
            }
        }
        /// <summary>
        /// Gets the framerate for the timecode.
        /// </summary>
        public VideoFramerate Framerate
        {
            get
            {
                return _framerate;
            }
        }
        /// <summary>
        /// Gets the frames portion of the timecode.
        /// </summary>
        public Int32 Frames
        {
            get
            {
                return ConvertToFrames();
            }
        }
        /// <summary>
        /// Gets the hours portion of the timecode. The maximum value is 99 hours.
        /// </summary>
        public Int32 Hours
        {
            get
            {
                return ConvertToHours();
            }
        }
        /// <summary>
        /// Gets the maximum number of frames that the timecode can hold.
        /// </summary>
        public Int32 MaximumFrames
        {
            get
            {
                return _maximumFrames;
            }
        }
        /// <summary>
        /// Gets the minutes portion of the timecode.
        /// </summary>
        public Int32 Minutes
        {
            get
            {
                return ConvertToMinutes();
            }
        }
        /// <summary>
        /// Gets the seconds portion of the timecode.
        /// </summary>
        public Int32 Seconds
        {
            get
            {
                return ConvertToSeconds();
            }
        }
        /// <summary>
        /// Gets the total number of frames.
        /// </summary>
        public Int32 TotalFrames
        {
            get
            {
                return _frames;
            }
        }
        /// <summary>
        /// Gets the total number of hours.
        /// </summary>
        public Int32 TotalHours
        {
            get
            {
                return Convert.ToInt32(_frames / (3600 * _framerateValue));
            }
        }
        /// <summary>
        /// Gets the total number of minutes.
        /// </summary>
        public Int32 TotalMinutes
        {
            get
            {
                return Convert.ToInt32(_frames / (60 * _framerateValue));
            }
        }
        /// <summary>
        /// Gets the total number of seconds.
        /// </summary>
        public Int32 TotalSeconds
        {
            get
            {
                return Convert.ToInt32(_frames / _framerateValue);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized from a byte array
        /// created by the ToBinary method.
        /// </summary>
        /// <param name="bytes">The bytes containing the encoded state values for the timecode.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when bytes is null.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when bytes length is not four bytes.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        /// <remarks>
        /// The frames value is located in bytes 0-3 of the array. The framerate enumeration is stored as bits 28-30
        /// of the array. The drop frames boolean is stored as bit 32 of the array.
        /// </remarks>
        public SmpteTimecode(byte[] bytes)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            if (bytes.Length != 4)
            {
                throw new ArgumentException("bytes length must be four bytes.");
            }

            _frames = (Int32)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | (bytes[3] & 0xf) << 24);

            if (_frames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _dropFrames = false;
            if (((bytes[3] >> 7) & 1) == 1)
            {
                _dropFrames = true;
            }
            _framerate = (VideoFramerate)((bytes[3] >> 4) & 7);
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));
        }

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized from a byte array
        /// containing Binary Coded Decimal (BCD) values.
        /// </summary>
        /// <param name="bytes">The bytes containing the Binary Coded Decimal (BCD) values for the timecode.</param>
        /// <param name="framerate">The framerate for the timecode.</param>
        /// <param name="dropFrames">Whether frames are droppped for the specified framerate.
        /// Only valid for Fps24 and Fps30 framerates.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when bytes is null.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when bytes length is not four bytes.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public SmpteTimecode(byte[] bytes, VideoFramerate framerate, Boolean dropFrames)
        {
            if (bytes == null)
            {
                throw new ArgumentNullException("bytes");
            }
            if (bytes.Length != 4)
            {
                throw new ArgumentException("bytes length must be four bytes.");
            }

            _dropFrames = dropFrames;
            _framerate = framerate;
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));

            // Convert to hours, minutes, seconds, and frames.
            Int32 hours = (((bytes[0] & 0xf0) >> 4) * 10) + (bytes[0] & 0xf);
            Int32 minutes = (((bytes[1] & 0xf0) >> 4) * 10) + (bytes[1] & 0xf);
            Int32 seconds = (((bytes[2] & 0xf0) >> 4) * 10) + (bytes[2] & 0xf);
            Int32 frames = ((((bytes[3] & 0xf0) >> 4) & 3) * 10) + (bytes[3] & 0xf);

            _frames = ConvertToTotalFrames(hours, minutes, seconds, frames);

            if (_frames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }
        }

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized to the specified
        /// number of frames.
        /// </summary>
        /// <param name="value">The number of frames.</param>
        /// <param name="framerate">The framerate for the timecode.</param>
        /// <param name="dropFrames">Whether frames are droppped for the specified framerate.
        /// Only valid for Fps24 and Fps30 framerates.</param>
        /// <exception cref="System.ArgumentException">Thrown when value less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public SmpteTimecode(Int32 value, VideoFramerate framerate, Boolean dropFrames)
        {
            if (value < 0)
            {
                throw new Exception("value less than zero.");
            }

            _dropFrames = dropFrames;
            _framerate = framerate;
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));
            _frames = value;

            if (_frames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }
        }

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized to the specified
        /// values.
        /// </summary>
        /// <param name="hours">The number of hours.</param>
        /// <param name="minutes">The number of minutes.</param>
        /// <param name="seconds">The number of seconds.</param>
        /// <param name="frames">The number of frames.</param>
        /// <param name="framerate">The framerate for the timecode.</param>
        /// <param name="dropFrames">Whether frames are droppped for the specified framerate.
        /// Only valid for Fps24 and Fps30 framerates.</param>
        /// <exception cref="System.ArgumentException">Thrown when hours is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when minutes is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when seconds is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when frames is less than zero.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the total frames causes an
        /// overflow because the result is too long.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public SmpteTimecode(Int32 hours, Int32 minutes, Int32 seconds, Int32 frames, VideoFramerate framerate, Boolean dropFrames)
        {
            if (hours < 0)
            {
                throw new Exception("hours less than zero.");
            }
            if (minutes < 0)
            {
                throw new Exception("minutes less than zero.");
            }
            if (seconds < 0)
            {
                throw new Exception("seconds less than zero.");
            }
            if (frames < 0)
            {
                throw new Exception("frames less than zero.");
            }

            _dropFrames = dropFrames;
            _framerate = framerate;
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));

            Int32 totalFrames = ConvertToTotalFrames(hours, minutes, seconds, frames);

            if (totalFrames < Int32.MinValue || totalFrames > Int32.MaxValue)
            {
                throw new ArgumentOutOfRangeException("total frames", "Overflow - result too long.");
            }
            if (totalFrames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _frames = totalFrames;
        }

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized to the value of the
        /// specified SmpteTimecode.
        /// </summary>
        /// <param name="value">The SmpteTimecode used to initialize this instance.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when value is null.</exception>
        /// <exception cref="System.ArgumentException">Thrown when value.Frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when value.Frames is greater than '99:59:59:59'.</exception>
        public SmpteTimecode(SmpteTimecode value)
        {
            if (value._frames < 0)
            {
                throw new ArgumentException("value frames is less than '00:00:00:00'");
            }
            if (value._frames > _maximumFrames)
            {
                throw new ArgumentException("value frames is greater than '99:59:59:59'");
            }

            _dropFrames = value._dropFrames;
            _framerate = value._framerate;
            _framerateValue = value._framerateValue;
            _frames = value._frames;
        }

        /// <summary>
        /// Creates a new instance of the SmpteTimecode class initialized to zero frames.
        /// </summary>
        /// <param name="framerate">The framerate for the timecode.</param>
        /// <param name="dropFrames">Whether frames are droppped for the specified framerate.
        /// Only valid for Fps24 and Fps30 framerates.</param>
        public SmpteTimecode(VideoFramerate framerate, Boolean dropFrames)
        {
            _dropFrames = dropFrames;
            _framerate = framerate;
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));
            _frames = 0;
        }

        /// <summary>
        /// Adds the specifed hours, minutes, seconds, and frames to the current value using the current Framerate
        /// and DropFrames values.
        /// </summary>
        /// <param name="hours">The number of hours to add.</param>
        /// <param name="minutes">The number of minutes to add.</param>
        /// <param name="seconds">The number of seconds to add.</param>
        /// <param name="frames">The number of frames to add.</param>
        /// <exception cref="System.ArgumentException">Thrown when hours is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when minutes is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when seconds is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when frames is less than zero.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the total frames causes an
        /// overflow because the result is too long.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public void Add(Int32 hours, Int32 minutes, Int32 seconds, Int32 frames)
        {
            if (hours < 0)
            {
                throw new Exception("hours less than zero.");
            }
            if (minutes < 0)
            {
                throw new Exception("minutes less than zero.");
            }
            if (seconds < 0)
            {
                throw new Exception("seconds less than zero.");
            }
            if (frames < 0)
            {
                throw new Exception("frames less than zero.");
            }

            Int32 totalFrames = ConvertToTotalFrames(hours, minutes, seconds, frames);

            if (_frames + totalFrames < Int32.MinValue || _frames + totalFrames > Int32.MaxValue)
            {
                throw new ArgumentOutOfRangeException("total frames", "Overflow - result too long.");
            }
            if (_frames + totalFrames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames + totalFrames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _frames = _frames + totalFrames;
        }

        /// <summary>
        /// Adds the value of a SmpteTimecode instance to this instance.
        /// </summary>
        /// <param name="value">The SmpteTimecode to add.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when value is null.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when value causes an overflow
        /// because the result is too long.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public void Add(SmpteTimecode value)
        {
            if (_frames + value._frames < Int32.MinValue || _frames + value._frames > Int32.MaxValue)
            {
                throw new ArgumentOutOfRangeException("value", "Overflow - result too long.");
            }
            if (_frames + value._frames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames + value._frames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _frames = _frames + value._frames;
        }

        /// <summary>
        /// Compares the specified SmpteTimecode to this instance and returns a value indicating
        /// the relationship between the values.
        /// </summary>
        /// <param name="value">The SmpteTimecode to compare to this instance.</param>
        /// <returns>Less than zero if this SmpteTimecode is less than value; zero if the SmpteTimecodes
        /// are identical; and greater than zero if this SmpteTimecode is greater than value.</returns>
        /// <exception cref="System.ArgumentNullException">Thrown when value is null.</exception>
        public int Compare(SmpteTimecode value)
        {
            if (_frames > value._frames)
            {
                return 1;
            }
            if (_frames < value._frames)
            {
                return -1;
            }

            return 0;
        }

        /// <summary>
        /// Converts the current framerate to the specified framerate by changing the number
        /// of frames for the timecode.
        /// </summary>
        /// <param name="value">The new framerate value.</param>
        /// <remarks>
        /// Converting the framerate more than once will cause the resulting frame count to be lower than
        /// the original starting frame count by one or more frames due to round off.
        /// </remarks>
        public void ConvertFramerate(VideoFramerate value)
        {
            // Only convert if the number of frames is greater than the fps.
            if (_frames > _framerateValue)
            {
                _frames = Convert.ToInt32(System.Math.Floor(_frames * (
                    DVDSwarm.Utility.ConvertVideoFramerateToValue(value, _dropFrames) /
                    DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames))));
            }

            _framerate = value;
            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));
        }

        /// <summary>
        /// Converts total frames to a timecode and returns the frames portion.
        /// </summary>
        /// <returns>The frames as an Int32.</returns>
        private Int32 ConvertToFrames()
        {
            return Convert.ToInt32(_frames % _framerateValue);
        }

        /// <summary>
        /// Converts total frames to a timecode and returns the hours portion.
        /// </summary>
        /// <returns>The hours as an Int32.</returns>
        private Int32 ConvertToHours()
        {
            return Convert.ToInt32((_frames / (3600 * _framerateValue)) % 100);
        }

        /// <summary>
        /// Converts total frames to a timecode and returns the minutes portion.
        /// </summary>
        /// <returns>The minutes as an Int32.</returns>
        private Int32 ConvertToMinutes()
        {
            return Convert.ToInt32((_frames / (60 * _framerateValue)) % 60);
        }

        /// <summary>
        /// Converts total frames to a timecode and returns the seconds portion.
        /// </summary>
        /// <returns>The seconds as an Int32.</returns>
        private Int32 ConvertToSeconds()
        {
            return Convert.ToInt32(_frames / _framerateValue) % 60;
        }

        /// <summary>
        /// Converts the supplied interval values to to a total frames value. 
        /// </summary>
        /// <param name="hours">The hours to convert.</param>
        /// <param name="minutes">The minutes to convert.</param>
        /// <param name="seconds">The seconds to convert.</param>
        /// <param name="frames">The frames to convert.</param>
        /// <returns>The total milliseconds as an Int32.</returns>
        private Int32 ConvertToTotalFrames(Int32 hours, Int32 minutes, Int32 seconds, Int32 frames)
        {
            return (hours * 3600 * _framerateValue) + (minutes * 60 * _framerateValue) +
                (seconds * _framerateValue) + frames;
        }

        /// <summary>
        /// Subtracts the specifed hours, minutes, seconds, and frames from the current value using the current Framerate
        /// and DropFrames values.
        /// </summary>
        /// <param name="hours">The number of hours to subtract.</param>
        /// <param name="minutes">The number of minutes to subtract.</param>
        /// <param name="seconds">The number of seconds to subtract.</param>
        /// <param name="frames">The number of frames to subtract.</param>
        /// <exception cref="System.ArgumentException">Thrown when hours is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when minutes is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when seconds is less than zero.</exception>
        /// <exception cref="System.ArgumentException">Thrown when frames is less than zero.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the total frames causes an
        /// overflow because the result is too long.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        /// <remarks>
        /// If the result is less than zero, the timecode will be zero.
        /// </remarks>
        public void Subtract(Int32 hours, Int32 minutes, Int32 seconds, Int32 frames)
        {
            if (hours < 0)
            {
                throw new Exception("hours less than zero.");
            }
            if (minutes < 0)
            {
                throw new Exception("minutes less than zero.");
            }
            if (seconds < 0)
            {
                throw new Exception("seconds less than zero.");
            }
            if (frames < 0)
            {
                throw new Exception("frames less than zero.");
            }

            _framerateValue = Convert.ToInt32(DVDSwarm.Utility.ConvertVideoFramerateToValue(_framerate, _dropFrames));

            Int32 totalFrames = ConvertToTotalFrames(hours, minutes, seconds, frames);

            if (_frames - totalFrames < Int32.MinValue || _frames - totalFrames > Int32.MaxValue)
            {
                throw new ArgumentOutOfRangeException("total frames", "Overflow - result too long.");
            }
            if (_frames - totalFrames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames - totalFrames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _frames = _frames - totalFrames;

            if (_frames < 0)
            {
                _frames = 0;
            }
        }

        /// <summary>
        /// Subtracts the value of a SmpteTimecode instance from this instance.
        /// </summary>
        /// <param name="value">The SmpteTimecode to subtract.</param>
        /// <exception cref="System.ArgumentNullException">Thrown when value is null.</exception>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when value causes an overflow
        /// because the result is too long.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is less than '00:00:00:00'.</exception>
        /// <exception cref="System.ArgumentException">Thrown when total frames is greater than '99:59:59:59'.</exception>
        public void Subtract(SmpteTimecode value)
        {
            if (_frames - value._frames < Int32.MinValue || _frames - value._frames > Int32.MaxValue)
            {
                throw new ArgumentOutOfRangeException("value", "Overflow - result too long.");
            }
            if (_frames - value._frames < 0)
            {
                throw new ArgumentException("total frames is less than '00:00:00:00'");
            }
            if (_frames - value._frames > _maximumFrames)
            {
                throw new ArgumentException("total frames is greater than '99:59:59:59'");
            }

            _frames = _frames - value._frames;
        }

        /// <summary>
        /// Returns the timecode as a byte array containing the state values including
        /// Framerate and DropFrames.
        /// </summary>
        /// <returns>A byte array with a length of nine bytes.</returns>
        public byte[] ToBinary()
        {
            byte[] result = new byte[4];

            // Store frames value.
            result[0] = (byte)_frames;
            result[1] = (byte)(_frames >> 8);
            result[2] = (byte)(_frames >> 16);

            Int32 framerateEnumValue = (Int32)_framerate << 4;
            if (_dropFrames)
            {
                framerateEnumValue = framerateEnumValue + (1 << 7);
            }
            result[3] = (byte)((_frames >> 24) + framerateEnumValue);

            return result;
        }

        /// <summary>
        /// Returns the timecode as a byte array containing Binary Coded Decimal (BCD) values.
        /// </summary>
        /// <returns>A byte array with a length of four bytes.</returns>
        /// <remarks>
        /// The Framerate and DropFrames values are not encoded into the BCD value and must be
        /// managed seperately.
        /// </remarks>
        public byte[] ToBinaryCodedDecimal()
        {
            byte[] result = new byte[4];

            result[0] = Convert.ToByte((Convert.ToInt32(ConvertToHours() / 10) << 4) + (ConvertToHours() % 10));
            result[1] = Convert.ToByte((Convert.ToInt32(ConvertToMinutes() / 10) << 4) + (ConvertToMinutes() % 10));
            result[2] = Convert.ToByte((Convert.ToInt32(ConvertToSeconds() / 10) << 4) + (ConvertToSeconds() % 10));
            result[3] = Convert.ToByte((Convert.ToInt32(ConvertToFrames() / 10) << 4) + (ConvertToFrames() % 10));

            return result;
        }

        /// <summary>
        /// Returns the SmpteTimecode as a formatted string ('hh:mm:ss:ff'). If DropFrames is true,
        /// the time seperator (':') in the format string is replaced by a period ('.').
        /// </summary>
        /// <returns>A string.</returns>
        public override string ToString()
        {
            string seperator = ":";
            if (_dropFrames)
            {
                seperator = ".";
            }

            return ConvertToHours().ToString("00") + seperator + ConvertToMinutes().ToString("00") +
                seperator + ConvertToSeconds().ToString("00") + seperator + ConvertToFrames().ToString("00");
        }

        /// <summary>
        /// Returns the SmpteTimecode as a custom formatted string. If DropFrames is true,
        /// the time seperator (':') in the format string is replaced by a period ('.').
        /// </summary>
        /// <param name="format">A format string.</param>
        /// <returns>A string.</returns>
        /// <exception cref="System.ArgumentException">Thrown when format is null or empty.</exception>
        /// <remarks>
        /// <list type="table">
        /// <listheader>
        /// <term>Format Specifier</term>
        /// <term>Description</term>
        /// </listheader>
        /// <item>
        ///   <term>f</term>
        ///   <description>Displays the number of frames. If less than ten, only a sigle digit
        ///   is displayed.</description>
        /// </item>
        /// <item>
        ///   <term>ff</term>
        ///   <description>Displays the number of frames as a two-digit value from 0 to the framerate
        ///    minus one.</description>
        /// </item>
        /// <item>
        ///   <term>h</term>
        ///   <description>Displays the number of hours from 0 to 99. If less than ten, only a sigle digit
        ///   is displayed.</description>
        /// </item>
        /// <item>
        ///   <term>hh</term>
        ///   <description>Displays the number of hours as a two-digit value from 0 to 99.</description>
        /// </item>
        /// <item>
        ///   <term>m</term>
        ///   <description>Displays the number of minutes. If less than ten, only a sigle digit is displayed.</description>
        /// </item>
        /// <item>
        ///   <term>mm</term>
        ///   <description>Displays the number of minutes.</description>
        /// </item>
        /// <item>
        ///   <term>s</term>
        ///   <description>Displays the number of seconds. If less than ten, only a sigle digit is displayed.</description>
        /// </item>
        /// <item>
        ///   <term>ss</term>
        ///   <description>Displays the number of seconds.</description>
        /// </item>
        /// <item>
        ///   <term>\</term>
        ///   <description>Escapes the following character.</description>
        /// </item>
        /// </list>
        /// </remarks>
        public string ToString(string format)
        {
            if (string.IsNullOrEmpty(format))
            {
                throw new ArgumentException("format is null or empty.");
            }

            StringBuilder sb = new StringBuilder();
            Int32 i = 0;
            string tempChars = string.Empty;

            while (i < format.Length)
            {
                // Get current character.
                tempChars = format.Substring(i, 1);

                // Escape character? Output it and next character. Otherwise is last character
                // so fallthrough and loop code will output the backslash.
                if (tempChars == "\\" && i < format.Length - 1)
                {
                    tempChars = format.Substring(i, 2);
                    i++; // Two chars pulled, must increment here to make i=i+2
                }

                // DropFrames? Repace colon with period.
                if (_dropFrames && tempChars == ":")
                {
                    tempChars = ".";
                }

                // Hours?
                if (tempChars == "h")
                {
                    // If not at end of string AND next char is 'h', update tempChars to 'hh'.
                    if (i < format.Length - 1 && format[i + 1] == 'h')
                    {
                        tempChars = format.Substring(i, 2);
                    }

                    // Display single digit hours?
                    if (tempChars == "h")
                    {
                        tempChars = ConvertToHours().ToString();
                    }

                    // Display two-digit hours?
                    if (tempChars == "hh")
                    {
                        tempChars = ConvertToHours().ToString("00");
                        i++; // Two chars pulled, must increment here to make i=i+2
                    }
                }

                // Minutes?
                if (tempChars == "m")
                {
                    // If not at end of string AND next char is 'm', update tempChars to 'mm'.
                    if (i < format.Length - 1 && format[i + 1] == 'm')
                    {
                        tempChars = format.Substring(i, 2);
                    }

                    // Display single digit minutes?
                    if (tempChars == "m")
                    {
                        tempChars = ConvertToMinutes().ToString();
                    }

                    // Display two-digit minutes?
                    if (tempChars == "mm")
                    {
                        tempChars = ConvertToMinutes().ToString("00");
                        i++; // Two chars pulled, must increment here to make i=i+2
                    }
                }

                // Seconds?
                if (tempChars == "s")
                {
                    // If not at end of string AND next char is 's', update tempChars to 'ss'.
                    if (i < format.Length - 1 && format[i + 1] == 's')
                    {
                        tempChars = format.Substring(i, 2);
                    }

                    // Display single digit seconds?
                    if (tempChars == "s")
                    {
                        tempChars = ConvertToSeconds().ToString();
                    }

                    // Display two-digit seconds?
                    if (tempChars == "ss")
                    {
                        tempChars = ConvertToSeconds().ToString("00");
                        i++; // Two chars pulled, must increment here to make i=i+2
                    }
                }

                // Frames?
                if (tempChars == "f")
                {
                    // If not at end of string AND next char is 'f', update tempChars to 'ff'.
                    if (i < format.Length - 1 && format[i + 1] == 'f')
                    {
                        tempChars = format.Substring(i, 2);
                    }

                    // Display single digit frames?
                    if (tempChars == "f")
                    {
                        tempChars = ConvertToFrames().ToString();
                    }

                    // Display two-digit frames?
                    if (tempChars == "ff")
                    {
                        tempChars = ConvertToFrames().ToString("00");
                        i++; // Two chars pulled, must increment here to make i=i+2
                    }
                }

                sb.Append(tempChars);
                i++;
            }

            return sb.ToString();
        }

        #endregion
    }
}