using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;

/*
    Copyright (c) 2008-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 VariableFramerateRx
{
    public partial class Form1 : Form
    {
        #region Fields

        private string _avisythScript = "LoadPlugin(\"" + Application.StartupPath + "\\Tools\\vinverse.dll\")\r\n" +
            "DirectShowSource(\"%FILENAME%\", fps=%FPS%, convertfps=true, " +
            "framecount=%FRAMECOUNT%).ChangeFPS(%FPS%)\r\nvinverse()";
        private string _avisythScriptFilename = "VariableFramerateRx.avs";
        private FicSprout.Bacicworx.Media.SmpteTimecode _duration = null;
        private bool[] _framerateDropFrames = new bool[10]
            {
                true, false, false, true, false, false, true, false, true, false
            };
        private FicSprout.Bacicworx.Media.VideoFramerate[] _framerateEnums = new
            FicSprout.Bacicworx.Media.VideoFramerate[10]
            {
                FicSprout.Bacicworx.Media.VideoFramerate.Fps24,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps24,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps25,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps30,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps30,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps50,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps60,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps60,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps120,
                FicSprout.Bacicworx.Media.VideoFramerate.Fps120
            };
        private string[] _framerateStrings = new string[10]
            {
                "23.976", "24", "25", "29.970", "30", "50", "59.940", "60", "119.880", "120"
            };
        private string _outputFilename = string.Empty;
        private ProgressDialog _progressDialog = null;

        private string[] _requiredTools = new string[] { "Avisynth_258.exe", "vinverse.dll", "x264.exe" };

        private string _sourceFilename = string.Empty;
        private FicSprout.Bacicworx.IO.JobStatus _status = FicSprout.Bacicworx.IO.JobStatus.Inactive;
        private FicSprout.Snapzworx.Video.X264.X264BackgroundWorker _x264 = null;
        
        #endregion

        #region Methods

        public Form1()
        {
            InitializeComponent();

            comboBoxFramerate.Items.AddRange(_framerateStrings);
            comboBoxFramerate.SelectedIndex = 0;
            labelOutputFilenameValue.Text = string.Empty;

            pictureBox1.Image = imageList1.Images[0];
        }

        private delegate void CloseProgressDialogFromThread();

        /// <summary>
        /// Required for closing the ProgressDialog.
        /// </summary>
        private void CloseProgressDialog()
        {
            if (_progressDialog != null)
            {
                _progressDialog.CloseDialog();
            }

            // If attempted elsewhere, the MessageBox will not appear because the wrong thread is
            // trying to show it.
            MessageBox.Show("Video Encoding " + _status.ToString());
        }

        /// <summary>
        /// Returns a value indicating whether AviSynth is installed on the current system.
        /// </summary>
        /// <returns></returns>
        public static bool IsAvisynthInstalled()
        {
            bool result = false;

            // DLL found? Registry key exists?
            if (System.IO.File.Exists(Environment.SystemDirectory + "\\avisynth.dll") &&
                Registry.LocalMachine.OpenSubKey("Software\\AviSynth") != null)
            {
                result = true;
            }

            return result;
        }

        private delegate void UpdateProgressDialogFromThread(Int32 value, FicSprout.Bacicworx.Media.Timecode time);

        /// <summary>
        /// Required for updating the ProgressDialog text from a different thread.
        /// </summary>
        /// <param name="value">The progress percent.</param>
        /// <param name="value">The text to display.</param>
        private void UpdateProgressDialog(Int32 value, FicSprout.Bacicworx.Media.Timecode time)
        {
            if (_progressDialog != null)
            {
                _progressDialog.ProgressValue = value;
                _progressDialog.TimeRemaining = time;
            }
        }

        #endregion

        #region Events

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            AboutBox1 dialog = new AboutBox1();
            dialog.ShowDialog();
            dialog.Dispose();
            dialog = null;
        }

        private void buttonBrowse_Click(object sender, EventArgs e)
        {
            // file exists?
            openFileDialog1.Title = "Open Video";
            openFileDialog1.Filter =  "Video Files (*.*)|*.*";
            openFileDialog1.Multiselect = false;

            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
            {
                return;
            }

            _sourceFilename = openFileDialog1.FileName;
            _outputFilename = System.IO.Path.ChangeExtension(_sourceFilename, "raw");

            textBoxVideo.Text = _sourceFilename;
            labelOutputFilenameValue.Text = System.IO.Path.GetFileName(_outputFilename);

            // Enable UI.
            buttonConvert.Enabled = true;
            checkBoxIsAnimation.Enabled = true;
            comboBoxFormat.Enabled = true;
            comboBoxFramerate.Enabled = true;
            numericUpDownHours.Enabled = true;
            numericUpDownMinutes.Enabled = true;
            numericUpDownSeconds.Enabled = true;
            numericUpDownSourceHeight.Enabled = true;
        }

        private void buttonClose_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void buttonConvert_Click(object sender, EventArgs e)
        {
            if (_duration == null)
            {
                MessageBox.Show("The duration of the clip must be at least one second.", "Duration Missing",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            Opacity = 0.0;
            _progressDialog = new ProgressDialog();
            _progressDialog.ProgressValue = 0;

            ExecuteX264();

            if (_progressDialog.ShowDialog() == System.Windows.Forms.DialogResult.Cancel)
            {
                if (_x264 != null)
                {
                    _x264.Cancel();
                }
            }

            _progressDialog.ProgressValue = 0;
            _progressDialog.Dispose();
            _progressDialog = null;
            Opacity = 1.0;

            _status = FicSprout.Bacicworx.IO.JobStatus.Inactive; 
        }

        private void comboBoxFormat_SelectedIndexChanged(object sender, EventArgs e)
        {
            _outputFilename = System.IO.Path.ChangeExtension(_outputFilename, comboBoxFormat.Text);
            labelOutputFilenameValue.Text = System.IO.Path.GetFileName(_outputFilename);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Cannot close the app if a job is active.
            if (_status == FicSprout.Bacicworx.IO.JobStatus.Active)
            {
                e.Cancel = true;
            }
        }

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

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

                Close();
                Application.Exit();
            }

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

        private void numericUpDownHours_ValueChanged(object sender, EventArgs e)
        {
            _duration = new FicSprout.Bacicworx.Media.SmpteTimecode(Convert.ToInt32(numericUpDownHours.Value),
                Convert.ToInt32(numericUpDownMinutes.Value), Convert.ToInt32(numericUpDownSeconds.Value), 0,
                _framerateEnums[comboBoxFramerate.SelectedIndex], _framerateDropFrames[comboBoxFramerate.SelectedIndex]);
        }

        private void pictureBox1_Click(object sender, EventArgs e)
        {
            contextMenuStrip1.Show(pictureBox1, new Point(10, 10));
        }

        private void projectWebsiteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("http://VariableFramerateRx.codeplex.com");
        }
  
        #endregion

        #region Tool Events

        void ExecuteX264_Completed(object sender, EventArgs e)
        {
            _status = _x264.Status;
            _x264 = null;

            Invoke(new CloseProgressDialogFromThread(CloseProgressDialog));

            // Cleanup.
            if (System.IO.File.Exists(System.IO.Path.GetDirectoryName(_sourceFilename) + "\\" + _avisythScriptFilename))
            {
                System.IO.File.Delete(System.IO.Path.GetDirectoryName(_sourceFilename) + "\\" + _avisythScriptFilename);
            }
        }

        void ExecuteX264_Progress(object sender, EventArgs e)
        {
            Invoke(new UpdateProgressDialogFromThread(UpdateProgressDialog), new object[] { 
                Convert.ToInt32(_x264.ProgressPercent), _x264.TimeRemaining } );
        }

        #endregion

        #region Tool Methods

        /// <summary>
        /// Runs x264 to encode the video.
        /// </summary>
        private void ExecuteX264()
        {
            // Generate script and write to source folder.
            string script = _avisythScript.Replace("%FILENAME%", _sourceFilename).
                Replace("%FPS%", _framerateStrings[comboBoxFramerate.SelectedIndex]).Replace("%FRAMECOUNT%",
                _duration.TotalFrames.ToString());

            System.IO.File.WriteAllText(System.IO.Path.GetDirectoryName(_sourceFilename) + "\\" +
                _avisythScriptFilename, script);
            
            // Generate arguments.
            string arguments = FicSprout.Snapzworx.Video.X264.Utility.GenerateArguments(System.IO.Path.GetDirectoryName(_sourceFilename) +
                "\\" + _avisythScriptFilename, _outputFilename, FicSprout.Bacicworx.Media.EncoderQualityOption.High,
                FicSprout.Snapzworx.Video.X264.DecoderLevel.Auto, FicSprout.Snapzworx.Video.X264.EncoderEfficiency.Default,
                 FicSprout.Snapzworx.Video.X264.InputTuning.Film, FicSprout.Snapzworx.Video.X264.OutputProfile.None,
                 Convert.ToInt32(numericUpDownSourceHeight.Value), false, checkBoxIsAnimation.Checked, 0,
                 _duration.TotalFrames);

            _x264 = new FicSprout.Snapzworx.Video.X264.X264BackgroundWorker(Application.StartupPath + "\\Tools\\x264.exe", arguments);
            _x264.Completed += new EventHandler(ExecuteX264_Completed);
            _x264.Progress += new EventHandler(ExecuteX264_Progress);
            _x264.Start();
        }

        #endregion
    }
}
