using System;
using System.Windows.Forms;

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

        private bool _audioDrc = true;
        private FicSprout.Bacicworx.Media.EncoderQualityOption _audioEncoderQualityOption =
            FicSprout.Bacicworx.Media.EncoderQualityOption.High;
        private AudioFormat _inputFormat = AudioFormat.Ac3;
        private string _filename = string.Empty;
        private FicSprout.Snapzworx.Audio.Flac.FlacEncoderBackgroundWorker _flacEncoder = null;
        private bool _isConversionRunning = false;
        private bool _normalizeAudio = true;
        private AudioFormat _outputFormat = AudioFormat.Vorbis;
        private NeroAacEncoderBackgroundWorker _neroAacEncoder = null;
        private FicSprout.Snapzworx.Audio.Vorbis.OggEncoderBackgroundWorker _oggEncoder = null;
        private FicSprout.Bacicworx.IO.JobStatus _status = FicSprout.Bacicworx.IO.JobStatus.Inactive;
        private WaviBackgroundWorker _wavi = null;

        #endregion

        #region Properties

        /// <summary>
        /// Sets a value indicating whether AC3 audio should have Dynamic Range
        /// Compression (DRC) applied.
        /// </summary>
        public bool DynamicRangeCompression
        {
            set
            {
                checkBoxAudioDrc.Checked = value;
            }
        }
        /// <summary>
        /// Sets the audio encoder quality option.
        /// </summary>
        public FicSprout.Bacicworx.Media.EncoderQualityOption AudioEncoderQualityOption
        {
            set
            {
                comboBoxAudioQuality.Text = FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOptionName(value);
            }
        }
        /// <summary>
        /// Sets a value indicating whether the output audio levels should be normalized.
        /// </summary>
        public bool NormalizeAudio
        {
            set
            {
                checkBoxAudioNormalize.Checked = value;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new instance of the ConvertAudioDialog.
        /// </summary>
        public ConvertAudioDialog()
        {
            InitializeComponent();

            openFileDialog.Filter = Utility.SupportedAudioDialogFilters;
            openFileDialog.FilterIndex = 2;
            comboBoxAudioQuality.Items.AddRange(FicSprout.Bacicworx.Media.Utility.EncoderQualityOptionNames);
        }

        /// <summary>
        /// Cancels the conversion.
        /// </summary>
        private void CancelConversion()
        {
            if (_flacEncoder != null)
            {
                _flacEncoder.Cancel();
            }
            if (_neroAacEncoder != null)
            {
                _neroAacEncoder.Cancel();
            }
            if (_oggEncoder != null)
            {
                _oggEncoder.Cancel();
            }
            if (_wavi != null)
            {
                _wavi.Cancel();
            }
        }

        /// <summary>
        /// Finalizes the conversion.
        /// </summary>
        private void FinalizeConversion()
        {
            // Delete temporary work files if source is not wav.
            if (_inputFormat != AudioFormat.Wav)
            {
                if (System.IO.File.Exists(System.IO.Path.ChangeExtension(_filename, "avs")))
                {
                    System.IO.File.Delete(System.IO.Path.ChangeExtension(_filename, "avs"));
                }

                if (_outputFormat != AudioFormat.Wav)
                {
                    if (System.IO.File.Exists(System.IO.Path.ChangeExtension(_filename,
                        Utility.GetAudioFormatExtension(AudioFormat.Wav))))
                    {
                        System.IO.File.Delete(System.IO.Path.ChangeExtension(_filename,
                            Utility.GetAudioFormatExtension(AudioFormat.Wav)));
                    }
                }
            }

            _isConversionRunning = false;
            Invoke(new UpdateUIFromThread(UpdateUI));
        }

        /// <summary>
        /// Initializes the conversion.
        /// </summary>
        private void InitializeConversion()
        {
            _isConversionRunning = true;
            Invoke(new UpdateUIFromThread(UpdateUI));

            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { "Starting Conversion." });

            if (_inputFormat != AudioFormat.Wav)
            {
                // Convert to WAV
                ExecuteWavi();
            }
            else
            {
                // Source is a WAV file, skip conversion and go directly to encoding.
                if (_outputFormat == AudioFormat.Aac)
                {
                    ExecuteNeroAacEncoder();
                }
                if (_outputFormat == AudioFormat.Flac)
                {
                    ExecuteFlacEncoder();
                }
                if (_outputFormat == AudioFormat.Vorbis)
                {
                    ExecuteOggEncoder();
                }
            }
        }

        private delegate void UpdateStatusLabelTextFromThread(string value);

        /// <summary>
        /// Required for updating the status label text from a different thread.
        /// </summary>
        /// <param name="value">The text to display in the control.</param>
        private void UpdateStatusLabelText(string value)
        {
            labelProgress.Text = value;
        }

        private delegate void UpdateUIFromThread();

        /// <summary>
        /// Required for updating the UI from a different thread.
        /// </summary>
        private void UpdateUI()
        {
            buttonBrowse.Enabled = !_isConversionRunning;
            buttonCancel.Visible = _isConversionRunning;
            buttonClose.Enabled = !_isConversionRunning;
            buttonConvert.Visible = !_isConversionRunning;
            checkBoxAudioDrc.Enabled = !_isConversionRunning;
            checkBoxAudioNormalize.Enabled = !_isConversionRunning;
            comboBoxAudioQuality.Enabled = !_isConversionRunning;
            comboBoxOutputFormat.Enabled = !_isConversionRunning;
            labelAudioSource.Enabled = !_isConversionRunning;
            labelAudioQuality.Enabled = !_isConversionRunning;
            labelOutputFormat.Enabled = !_isConversionRunning;
            textBoxAudioSource.Enabled = !_isConversionRunning;
            labelProgress.Visible = _isConversionRunning;
        }

        #endregion

        #region Events

        private void buttonCancel_Click(object sender, EventArgs e)
        {
            CancelConversion();
        }

        private void buttonBrowse_Click(object sender, EventArgs e)
        {
            // file exists?
            openFileDialog.Title = "Convert Audio";
            openFileDialog.Multiselect = false;
           
            buttonConvert.Enabled = false;
            checkBoxAudioDrc.Enabled = false;
            checkBoxAudioNormalize.Enabled = false;
            comboBoxAudioQuality.Enabled = false;
            comboBoxOutputFormat.Enabled = false;
            comboBoxOutputFormat.Items.Clear();
            comboBoxOutputFormat.Text = string.Empty;
            labelAudioQuality.Enabled = false;
            labelOutputFormat.Enabled = false;

            if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
            {
                return;
            }
            if (string.IsNullOrEmpty(openFileDialog.FileName))
            {
                return;
            }
            
            _filename = openFileDialog.FileName;
            textBoxAudioSource.Text = _filename;

            _inputFormat = Utility.GetAudioFormatFromExtension(_filename);
            comboBoxOutputFormat.Items.AddRange(Utility.GetAudioFormatNames(Utility.GetAudioOutputFormats(
                _inputFormat, OutputContainerFormat.None)));

            if (comboBoxOutputFormat.Items.Count > 0)
            {
                buttonConvert.Enabled = true;
                buttonConvert.Enabled = false;
                checkBoxAudioDrc.Enabled = true;
                checkBoxAudioNormalize.Enabled = true;
                comboBoxAudioQuality.Enabled = true;
                comboBoxOutputFormat.Enabled = true;

                comboBoxOutputFormat.Text = (string)comboBoxOutputFormat.Items[0];
                Int32 index = comboBoxOutputFormat.Items.IndexOf((string)Utility.AudioFormatNames[5]);
                if (index != -1)
                {
                    comboBoxOutputFormat.Text = (string)comboBoxOutputFormat.Items[index];
                }
                
                labelAudioQuality.Enabled = true;
                labelOutputFormat.Enabled = true;
            }
        }

        private void buttonConvert_Click(object sender, EventArgs e)
        {
            InitializeConversion();
        }

        private void checkBoxAudioDrc_CheckedChanged(object sender, EventArgs e)
        {
            _audioDrc = checkBoxAudioDrc.Checked;
        }

        private void checkBoxAudioNormalize_CheckedChanged(object sender, EventArgs e)
        {
            _normalizeAudio = checkBoxAudioNormalize.Checked;
        }

        private void comboBoxAudioQuality_SelectedIndexChanged(object sender, EventArgs e)
        {
            _audioEncoderQualityOption = FicSprout.Bacicworx.Media.Utility.GetEncoderQualityOption(
                comboBoxAudioQuality.Text);
        }

        private void comboBoxOutputFormat_SelectedIndexChanged(object sender, EventArgs e)
        {
            buttonConvert.Enabled = false;

            if (System.IO.File.Exists(textBoxAudioSource.Text))
            {
                buttonConvert.Enabled = true;
                _outputFormat = Utility.GetAudioFormat(comboBoxOutputFormat.Text);
            }
        }

        #endregion

        #region Tool Events

        void ExecuteFlacEncoder_Completed(object sender, EventArgs e)
        {
            _status = _flacEncoder.Status;
            _flacEncoder = null;

            Utility.WaitForFile(System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)));
            FinalizeConversion();
        }

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

        void ExecuteNeroAacEncoder_Completed(object sender, EventArgs e)
        {
            _status = _neroAacEncoder.Status;
            _neroAacEncoder = null;

            Utility.WaitForFile(System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)));
            FinalizeConversion();
        }

        void ExecuteNeroAacEncoder_Progress(object sender, EventArgs e)
        {
            FicSprout.Bacicworx.Media.Timecode timecode = new FicSprout.Bacicworx.Media.Timecode(0, 0, _neroAacEncoder.ProgressSeconds, 0);
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { 
                "Encoding Audio...  " + timecode.ToString("hh:mm:ss") + " Encoded"});
        }

        void ExecuteOggEncoder_Completed(object sender, EventArgs e)
        {
            _status = _oggEncoder.Status;
            _oggEncoder = null;

            Utility.WaitForFile(System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)));
            FinalizeConversion();
        }

        void ExecuteOggEncoder_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { 
                "Encoding Audio...  " + _oggEncoder.ProgressPercent.ToString("0.0") + "%,  " +
                _oggEncoder.TimeRemaining.ToString("hh:mm:ss") + " Remaining"});
        }

        void ExecuteWavi_Completed(object sender, EventArgs e)
        {
            if (_wavi.Status == FicSprout.Bacicworx.IO.JobStatus.Cancelled ||
                _wavi.Status == FicSprout.Bacicworx.IO.JobStatus.Failed)
            {
                _status = _wavi.Status;
                FinalizeConversion();
                _wavi = null;
                return;
            }

            _wavi = null;
            Utility.WaitForFile(System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(AudioFormat.Wav)));

            // Encode WAV file.
            if (_outputFormat == AudioFormat.Aac)
            {
                ExecuteNeroAacEncoder();
            }
            if (_outputFormat == AudioFormat.Flac)
            {
                ExecuteFlacEncoder();
            }
            if (_outputFormat == AudioFormat.Vorbis)
            {
                ExecuteOggEncoder();
            }
            if (_outputFormat == AudioFormat.Wav)
            {
                FinalizeConversion();
            }
        }

        #endregion

        #region Tool Methods

        /// <summary>
        /// Runs FLAC Encoder to encode the WAV.
        /// </summary>
        private void ExecuteFlacEncoder()
        {
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { "Encoding Audio...  FLAC" });

            _flacEncoder = new FicSprout.Snapzworx.Audio.Flac.FlacEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\flac.exe",
                FicSprout.Snapzworx.Audio.Flac.Utility.GenerateArguments(
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)),
                _audioEncoderQualityOption));

            _flacEncoder.Progress += new EventHandler(ExecuteFlacEncoder_Progress);
            _flacEncoder.Completed += new EventHandler(ExecuteFlacEncoder_Completed);
            _flacEncoder.Start();
        }

        /// <summary>
        /// Runs Nero AAC Encoder to encode the WAV.
        /// </summary>
        private void ExecuteNeroAacEncoder()
        {
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { "Encoding Audio...  AAC" });

            _neroAacEncoder = new NeroAacEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\NeroAacEnc.exe",
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)),
                _audioEncoderQualityOption);

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

        /// <summary>
        /// Runs Ogg Encoder to encode the WAV.
        /// </summary>
        private void ExecuteOggEncoder()
        {
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { "Encoding Audio...  Vorbis" });

            _oggEncoder = new FicSprout.Snapzworx.Audio.Vorbis.OggEncoderBackgroundWorker(Application.StartupPath + "\\Tools\\oggenc.exe",
                FicSprout.Snapzworx.Audio.Vorbis.Utility.GenerateArguments(
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(AudioFormat.Wav)),
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(_outputFormat)),
                _audioEncoderQualityOption));

            _oggEncoder.Progress += new EventHandler(ExecuteOggEncoder_Progress);
            _oggEncoder.Completed += new EventHandler(ExecuteOggEncoder_Completed);
            _oggEncoder.Start();
        }

        /// <summary>
        /// Runs Wavi to convert AC3 audio to WAV.
        /// </summary>
        private void ExecuteWavi()
        {
            Invoke(new UpdateStatusLabelTextFromThread(UpdateStatusLabelText), new object[] { 
                    "Converting Audio... This may take a few minutes." });

            // Generate an AviSynth script and write it to the source folder.
            Utility.WriteAvisynthAudioScript(_filename, _inputFormat, _audioDrc, _normalizeAudio);

            _wavi = new WaviBackgroundWorker(Application.StartupPath + "\\Tools\\wavi.exe",
                System.IO.Path.ChangeExtension(_filename, "avs"),
                System.IO.Path.ChangeExtension(_filename, Utility.GetAudioFormatExtension(AudioFormat.Wav)));

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

        #endregion
    }
}
