/* -LICENSE-START-
** Copyright (c) 2018 Blackmagic Design
**  
** Permission is hereby granted, free of charge, to any person or organization 
** obtaining a copy of the software and accompanying documentation (the 
** "Software") to use, reproduce, display, distribute, sub-license, execute, 
** and transmit the Software, and to prepare derivative works of the Software, 
** and to permit third-parties to whom the Software is furnished to do so, in 
** accordance with:
** 
** (1) if the Software is obtained from Blackmagic Design, the End User License 
** Agreement for the Software Development Kit ("EULA") available at 
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
** 
** (2) if the Software is obtained from any third party, such licensing terms 
** as notified by that third party,
** 
** and all subject to the following:
** 
** (3) the copyright notices in the Software and this entire statement, 
** including the above license grant, this restriction and the following 
** disclaimer, must be included in all copies of the Software, in whole or in 
** part, and all derivative works of the Software, unless such copies or 
** derivative works are solely in the form of machine-executable object code 
** generated by a source language processor.
** 
** (4) THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
** DEALINGS IN THE SOFTWARE.
** 
** A copy of the Software is available free of charge at 
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
** 
** -LICENSE-END-
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

using DeckLinkAPI;

namespace StillsCSharp
{
    public partial class Stills : Form
    {
        const int kStillsPreviewHorizontalSpacing = 10;
        const int kStillsPreviewVerticalSpacing = 10;
        const int kInputInvalidFrameTimeout = 60;

        delegate void DirectoryPathStringDelegate(string path);
        delegate void ControlEnableDelegate(Control control, bool enable);

        private IReadOnlyList<StringObjectPair<_BMDPixelFormat>> kPixelFormatList = new List<StringObjectPair<_BMDPixelFormat>>
        {
            new StringObjectPair<_BMDPixelFormat>("8-Bit YUV", _BMDPixelFormat.bmdFormat8BitYUV),
            new StringObjectPair<_BMDPixelFormat>("10-Bit YUV", _BMDPixelFormat.bmdFormat10BitYUV),
            new StringObjectPair<_BMDPixelFormat>("8-Bit ARGB", _BMDPixelFormat.bmdFormat8BitARGB),
            new StringObjectPair<_BMDPixelFormat>("8-Bit BGRA", _BMDPixelFormat.bmdFormat8BitBGRA),
            new StringObjectPair<_BMDPixelFormat>("10-Bit RGB", _BMDPixelFormat.bmdFormat10BitRGB),
            new StringObjectPair<_BMDPixelFormat>("12-Bit RGB", _BMDPixelFormat.bmdFormat12BitRGB),
            new StringObjectPair<_BMDPixelFormat>("12-Bit RGB LE", _BMDPixelFormat.bmdFormat12BitRGBLE),
            new StringObjectPair<_BMDPixelFormat>("10-Bit RGBX", _BMDPixelFormat.bmdFormat10BitRGBX),
            new StringObjectPair<_BMDPixelFormat>("10-Bit RGBX LE", _BMDPixelFormat.bmdFormat10BitRGBXLE)
        };

        private DeckLinkDeviceDiscovery m_deckLinkDiscovery;
        private DeckLinkInputDevice m_selectedCaptureDevice;
        private DeckLinkOutputDevice m_selectedPlaybackDevice;

        private int m_captureFrameIntervalCount = 0;
        private int m_captureStillsCount = 0;
        private int m_invalidFrameTimeout = 0;

        private string m_selectedFolder;
        private ImageList m_folderImageList;

        private CountdownEvent m_captureCountdown;
        private CancellationTokenSource m_captureCancel;
        private CancellationTokenSource m_captureFileExists;
        private CancellationTokenSource m_captureDeviceRemovedCancel;
        private CancellationTokenSource m_captureInvalidCancel;

        private CancellationTokenSource m_playbackCancel;
        private CancellationTokenSource m_playbackDeviceRemovedCancel;

        public Stills()
        {
            InitializeComponent();

            m_deckLinkDiscovery = new DeckLinkDeviceDiscovery();
            m_deckLinkDiscovery.DeviceArrived += new EventHandler<DeckLinkDiscoveryEventArgs>((s,e) => this.Invoke((Action)(() => AddDevice(s,e))));
            m_deckLinkDiscovery.DeviceRemoved += new EventHandler<DeckLinkDiscoveryEventArgs>((s,e) => this.Invoke((Action)(() => RemoveDevice(s,e))));

            m_folderImageList = new ImageList();
            m_folderImageList.ImageSize = new Size(160, 90);

            // Update file type Combo-box
            comboBoxFileType.BeginUpdate();
            comboBoxFileType.Items.Clear();
            comboBoxFileType.Items.Add(ImageFormat.Bmp);
            comboBoxFileType.Items.Add(ImageFormat.Gif);
            comboBoxFileType.Items.Add(ImageFormat.Jpeg);
            comboBoxFileType.Items.Add(ImageFormat.Png);
            comboBoxFileType.Items.Add(ImageFormat.Tiff);
            comboBoxFileType.EndUpdate();
            comboBoxFileType.SelectedIndex = 0;

            panelStillsViewer.AutoScroll = true;
            panelStillsViewer.VerticalScroll.Visible = true;
        }

        private void RefreshCaptureVideoModeList()
        {
            if (m_selectedCaptureDevice != null)
            {
                comboBoxCaptureVideoMode.BeginUpdate();
                comboBoxCaptureVideoMode.Items.Clear();

                foreach (IDeckLinkDisplayMode displayMode in m_selectedCaptureDevice)
                    comboBoxCaptureVideoMode.Items.Add(new DisplayModeEntry(displayMode));

                comboBoxCaptureVideoMode.SelectedIndex = 0;
                comboBoxCaptureVideoMode.EndUpdate();

                // Need to refresh pixel format list
                RefreshCapturePixelFormatList();
            }
        }

        private void RefreshCapturePixelFormatList()
        {
            comboBoxCapturePixelFormat.BeginUpdate();
            comboBoxCapturePixelFormat.Items.Clear();
            
            foreach (StringObjectPair<_BMDPixelFormat> pixelFormat in kPixelFormatList.Where((pf, ret) => { return (m_selectedCaptureDevice.IsVideoModeSupported(((DisplayModeEntry)comboBoxCaptureVideoMode.SelectedItem).displayMode, pf.value)); }))
                comboBoxCapturePixelFormat.Items.Add(pixelFormat);

            comboBoxCapturePixelFormat.SelectedIndex = 0;
            comboBoxCapturePixelFormat.EndUpdate();
        }

        private void RefreshPlaybackVideoModeList()
        {
            if ((m_selectedCaptureDevice != null) && (m_folderImageList.Images.Count > 0))
            {
                comboBoxPlaybackVideoMode.BeginUpdate();
                comboBoxPlaybackVideoMode.Items.Clear();

                // Get first image from image list to get resolution
                string inputFileName = m_folderImageList.Images.Keys[0].ToString();
                using (Image playbackImage = new Bitmap(inputFileName))
                {
                    // Iterate through display modes with matching resolution 
                    foreach (IDeckLinkDisplayMode displayMode in m_selectedPlaybackDevice.Where((dm, ret) => { return ((dm.GetWidth() == playbackImage.Width) && (dm.GetHeight() == playbackImage.Height)); }))
                        comboBoxPlaybackVideoMode.Items.Add(new DisplayModeEntry(displayMode));
                }

                if (comboBoxPlaybackVideoMode.Items.Count > 0)
                {
                    comboBoxPlaybackVideoMode.SelectedIndex = 0;
                    RefreshPlaybackPixelFormatList();
                }
                comboBoxPlaybackVideoMode.EndUpdate();
            }
        }
            
        private void RefreshPlaybackPixelFormatList()
        {
            comboBoxPlaybackPixelFormat.BeginUpdate();
            comboBoxPlaybackPixelFormat.Items.Clear();

            foreach (StringObjectPair<_BMDPixelFormat> pixelFormat in kPixelFormatList.Where((pf, ret) => { return (m_selectedPlaybackDevice.IsVideoModeSupported(((DisplayModeEntry)comboBoxPlaybackVideoMode.SelectedItem).displayMode, pf.value)); }))
                comboBoxPlaybackPixelFormat.Items.Add(pixelFormat);

            comboBoxPlaybackPixelFormat.SelectedIndex = 0;
            comboBoxPlaybackPixelFormat.EndUpdate();
        }

        private async void ToggleCapture()
        {
            if (m_captureCancel != null)
            {
                m_captureCancel.Cancel();
            }
            else if (m_selectedCaptureDevice != null)
            {
                try
                {
                    var displayMode = ((DisplayModeEntry)comboBoxCaptureVideoMode.SelectedItem).displayMode;

                    m_captureFrameIntervalCount = 0;
                    m_captureStillsCount = 0;
                    m_invalidFrameTimeout = 0;

                    m_selectedCaptureDevice.VideoFrameArrivedHandler += new EventHandler<DeckLinkVideoFrameArrivedEventArgs>((s, e) => this.Invoke((Action)(() => { VideoFrameArrived(s, e); })));
                    m_selectedCaptureDevice.InputFormatChangedHandler += new EventHandler<DeckLinkInputFormatChangedEventArgs>((s, e) => this.Invoke((Action)(() => { DisplayModeChanged(s, e); })));

                    m_selectedCaptureDevice.StartCapture(displayMode, null, checkBoxEnableFormatDetection.Checked);

                    // Update UI
                    buttonCaptureStartStop.Text = "Stop Capture";
                    EnableGroupInterface(groupBoxCapture, false);

                    m_captureCountdown = new CountdownEvent((int)numericUpDownCaptureNumberStills.Value);
                    m_captureCancel = new CancellationTokenSource();
                    m_captureFileExists = new CancellationTokenSource();
                    m_captureDeviceRemovedCancel = new CancellationTokenSource();
                    m_captureInvalidCancel = new CancellationTokenSource();

                    // Wait for capture to finish in task.
                    await Task.Run(() => 
                    {
                        using (CancellationTokenSource linkedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(m_captureCancel.Token, m_captureFileExists.Token, m_captureDeviceRemovedCancel.Token, m_captureInvalidCancel.Token))
                        {
                            try {
                                m_captureCountdown.Wait(linkedCancelToken.Token);
                            }
                            catch (OperationCanceledException) {}
                        }
                    });
                }
                catch (TaskCanceledException) {}
                finally
                {
                    m_captureCountdown.Dispose();
                    m_captureCountdown = null;

                    m_captureCancel.Dispose();
                    m_captureCancel = null;

                    m_captureFileExists.Dispose();
                    m_captureFileExists = null;

                    m_captureInvalidCancel.Dispose();
                    m_captureInvalidCancel = null;

                    m_captureDeviceRemovedCancel.Dispose();
                    m_captureDeviceRemovedCancel = null;
                    
                    if (m_selectedCaptureDevice != null)
                        m_selectedCaptureDevice.StopCapture();

                    RescanFolder();

                    // Update UI
                    buttonCaptureStartStop.Text = "Start Capture";
                    EnableGroupInterface(groupBoxCapture, true);

                    checkBoxInputValid.Checked = false;
                    checkBoxInputValid.Enabled = false;

                    EnableComponent(comboBoxCaptureVideoMode, !checkBoxEnableFormatDetection.Checked);
                    EnableComponent(comboBoxCapturePixelFormat, !checkBoxEnableFormatDetection.Checked);
                }
            }
        }

        private async void TogglePlayback()
        {
            // If currently playing, cancel it.
            if (m_playbackCancel != null)
            {
                m_playbackCancel.Cancel();
            } 
            else if (m_selectedPlaybackDevice != null)
            {
                try
                {
                    var displayMode = ((DisplayModeEntry)comboBoxPlaybackVideoMode.SelectedItem).displayMode;
                    
                    // Update UI
                    buttonPlaybackStartStop.Text = "Stop Playback";
                    EnableGroupInterface(groupBoxPlayback, false);

                    // Enable output
                    m_selectedPlaybackDevice.EnableVideoOutput(displayMode);

                    m_playbackCancel = new CancellationTokenSource();
                    m_playbackDeviceRemovedCancel = new CancellationTokenSource();

                    // Run playback as a task.
                    await TaskPlaybackStills(m_playbackCancel.Token, m_playbackDeviceRemovedCancel.Token);
                }
                catch (TaskCanceledException)
                {

                }
                finally
                {
                    m_playbackCancel.Dispose();
                    m_playbackCancel = null;

                    m_playbackDeviceRemovedCancel.Dispose();
                    m_playbackDeviceRemovedCancel = null;

                    // Playback stopped
                    m_selectedPlaybackDevice.DisableVideoOutput();

                    // Update UI
                    buttonPlaybackStartStop.Text = "Start Playback";
                    EnableGroupInterface(groupBoxPlayback, true);
                }
            }
        }

        private async Task TaskPlaybackStills(CancellationToken cancelToken, CancellationToken deviceRemovedToken)
        {
            int playbackStillsCount = 0;

            using (CancellationTokenSource linkedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, deviceRemovedToken))
            {
                while (true)
                {
                    if (playbackStillsCount >= m_folderImageList.Images.Count)
                    {
                        if (checkBoxLoopPlayback.Checked)
                            playbackStillsCount = 0;
                        else
                            break;
                    }

                    // Playback next image
                    DisplayVideoFrameFromFile(m_folderImageList.Images.Keys[playbackStillsCount].ToString());
                    
                    playbackStillsCount++;

                    await Task.Delay(TimeSpan.FromMilliseconds(m_selectedPlaybackDevice.FrameDurationMs * (double) numericUpDownPlaybackFrameInterval.Value), linkedCancelToken.Token);
                }
            }
        }

        private void DisplayVideoFrameFromFile(string inputFileName)
        {
            Bitmap playbackBitmap = new Bitmap(inputFileName);

            // Create BGRA32 video frame buffer based on bitmap data
            var playbackBuffer = new Bgra32VideoBuffer(playbackBitmap);

            m_selectedPlaybackDevice.DeckLinkOutput.RowBytesForPixelFormat(_BMDPixelFormat.bmdFormat8BitBGRA, playbackBitmap.Width, out int rowBytes);

            m_selectedPlaybackDevice.DeckLinkOutput.CreateVideoFrameWithBuffer(playbackBitmap.Width, playbackBitmap.Height, rowBytes, _BMDPixelFormat.bmdFormat8BitBGRA, 
                                                                                _BMDFrameFlags.bmdFrameFlagDefault, playbackBuffer, out IDeckLinkMutableVideoFrame playbackFrame);

            m_selectedPlaybackDevice.DisplayVideoFrame(playbackFrame);
        }

        private void EnableGroupInterface(GroupBox group, bool enable)
        {
            // Get list of groupbox child controls, filter out button
            var groupControls = group.Controls.Cast<Control>();
            foreach (Control control in groupControls.Where((ctrl,ret) => { return (ctrl.GetType() != typeof(Button));}))
                EnableComponent(control, enable);
        }

        private void RescanFolder()
        {
            // Clean ImageList 
            foreach (Image image in m_folderImageList.Images)
                image.Dispose();
            m_folderImageList.Images.Clear();

            while (true)
            {
                string fileName = m_selectedFolder + "\\image_" + m_folderImageList.Images.Count.ToString("D4") + "." + comboBoxFileType.SelectedItem.ToString().ToLower();

                if (!File.Exists(fileName))
                    break;

                using (Image image = Image.FromFile(fileName))
                {
                    // Store out thumbnails to list to avoid out of memory exception
                    Image thumbnail = image.GetThumbnailImage(160, 90, () => false, IntPtr.Zero);
                    m_folderImageList.Images.Add(fileName, thumbnail);
                }
            }

            RefreshImageViewer();

            if (m_folderImageList.Images.Count > 0)
            {
                RefreshPlaybackVideoModeList();
            }

            // If files available and with compatible playback video mode, enable playback start button
            EnableComponent(buttonPlaybackStartStop, ((m_folderImageList.Images.Count > 0) && (comboBoxPlaybackVideoMode.Items.Count > 0)));
        }

        private void RefreshImageViewer()
        {
            int x = kStillsPreviewHorizontalSpacing;
            int y = kStillsPreviewVerticalSpacing;

            panelStillsViewer.Controls.Clear();

            foreach (Image image in m_folderImageList.Images)
            {
                PictureBox picture = new PictureBox();
                picture.Location = new Point(x, y);
                picture.Image = image;
                picture.Size = image.Size;

                panelStillsViewer.Controls.Add(picture);

                x += (m_folderImageList.ImageSize.Width + kStillsPreviewHorizontalSpacing);
                if ( (x + m_folderImageList.ImageSize.Width) > panelStillsViewer.Width)
                {
                    x = kStillsPreviewHorizontalSpacing;
                    y += (m_folderImageList.ImageSize.Height + kStillsPreviewVerticalSpacing);
                }
            }
        }

        private void SetDirectoryPathText(string path)
        {
            // Update the textBox with directory path, determine whether safe to call in same thread or new thread.
            if (textBoxDirectoryPath.InvokeRequired)
            {
                this.Invoke(new DirectoryPathStringDelegate(SetDirectoryPathText), new object[] { path });
            }
            else
            {
                m_selectedFolder = path;
                textBoxDirectoryPath.Text = m_selectedFolder;
                RescanFolder();
            }
        }

        // Create thread safe enable for WinForms control
        private void EnableComponent(Control control, bool enable)
        {
            if (control.InvokeRequired)
                this.Invoke(new ControlEnableDelegate(EnableComponent), new object[] { control, enable });
            else
                control.Enabled = enable;
        }

        #region events
        void AddDevice(object sender, DeckLinkDiscoveryEventArgs e)
        {
            try 
            {
                DeckLinkInputDevice deckLink = new DeckLinkInputDevice(e.deckLink);

                comboBoxCaptureDevice.BeginUpdate();
                comboBoxCaptureDevice.Items.Add(new StringObjectPair<DeckLinkInputDevice>(deckLink.DeviceName, deckLink));
                comboBoxCaptureDevice.EndUpdate();

                // If first device, then enable capture interface
                if (comboBoxCaptureDevice.Items.Count == 1)
                {
                    comboBoxCaptureDevice.SelectedIndex = 0;
                    EnableComponent(groupBoxCapture, true);
                }
            }
            catch (DeckLinkInputInvalidException)
            {
                // Device likely playback only, eg DeckLink Mini Monitor
            }

            try 
            {
                DeckLinkOutputDevice deckLink = new DeckLinkOutputDevice(e.deckLink);

                comboBoxPlaybackDevice.BeginUpdate();
                comboBoxPlaybackDevice.Items.Add(new StringObjectPair<DeckLinkOutputDevice>(deckLink.DeviceName, deckLink));
                comboBoxPlaybackDevice.EndUpdate();

                if (comboBoxPlaybackDevice.Items.Count == 1)
                {
                    comboBoxPlaybackDevice.SelectedIndex = 0;
                    EnableComponent(groupBoxPlayback, true); 
                }
            }
            catch (DeckLinkOutputInvalidException)
            {
                // Device likely capture only, eg DeckLink Mini Recorder
            }
        }

        void RemoveDevice(object sender, DeckLinkDiscoveryEventArgs e)
        {
            // Stop capture thread if the selected capture device was removed
            if (m_selectedCaptureDevice != null && m_selectedCaptureDevice.DeckLink == e.deckLink && m_captureDeviceRemovedCancel != null)
            {
                m_captureDeviceRemovedCancel.Cancel();
            }

            // Stop playback thread if the selected playback device was removed
            if (m_selectedPlaybackDevice != null && m_selectedPlaybackDevice.DeckLink == e.deckLink && m_playbackDeviceRemovedCancel != null)
            {
                m_playbackDeviceRemovedCancel.Cancel();
            }

            // Remove the device from the capture dropdown
            comboBoxCaptureDevice.BeginUpdate();
            foreach (StringObjectPair<DeckLinkInputDevice> item in comboBoxCaptureDevice.Items)
            {
                if (item.value.DeckLink == e.deckLink)
                {
                    comboBoxCaptureDevice.Items.Remove(item);
                    break;
                }
            }
            comboBoxCaptureDevice.EndUpdate();

            if (comboBoxCaptureDevice.Items.Count == 0)
            {
                EnableComponent(groupBoxCapture, false);
                m_selectedCaptureDevice = null;
            }
            else if (m_selectedCaptureDevice.DeckLink == e.deckLink)
            {
                comboBoxCaptureDevice.SelectedIndex = 0;
            }

            // Remove the device from the playback dropdown
            comboBoxPlaybackDevice.BeginUpdate();
            foreach (StringObjectPair<DeckLinkOutputDevice> item in comboBoxPlaybackDevice.Items)
            {
                if (item.value.DeckLink == e.deckLink)
                {
                    comboBoxPlaybackDevice.Items.Remove(item);
                    break;
                }
            }
            comboBoxPlaybackDevice.EndUpdate();

            if (comboBoxPlaybackDevice.Items.Count == 0)
            {
                EnableComponent(groupBoxPlayback, false);
                m_selectedPlaybackDevice = null;
            }
            else if (m_selectedPlaybackDevice.DeckLink == e.deckLink)
            {
                comboBoxPlaybackDevice.SelectedIndex = 0;
            }
        }

        private void DisplayModeChanged(object sender, DeckLinkInputFormatChangedEventArgs e)
        {
            if (e.notificationEvents.HasFlag(_BMDVideoInputFormatChangedEvents.bmdVideoInputDisplayModeChanged) )
            {
                // Video input mode has changed, update combo-box
                foreach (DisplayModeEntry item in comboBoxCaptureVideoMode.Items)
                {
                    if (item.displayMode.GetDisplayMode() == e.displayMode)
                        comboBoxCaptureVideoMode.SelectedItem = item;
                }
            }

            if (e.notificationEvents.HasFlag(_BMDVideoInputFormatChangedEvents.bmdVideoInputColorspaceChanged))
            {
                // Input pixel format has changed, update combo-box
                foreach (StringObjectPair<_BMDPixelFormat> item in comboBoxCapturePixelFormat.Items)
                {
                    if (item.value == e.pixelFormat)
						comboBoxCapturePixelFormat.SelectedItem = item;
                }
            }
        }

        private void VideoFrameArrived(object sender, DeckLinkVideoFrameArrivedEventArgs e)
        {
            checkBoxInputValid.Checked = !e.inputInvalid;

            if (e.inputInvalid)
            {
                if (++m_invalidFrameTimeout == kInputInvalidFrameTimeout)
                {
                    if (MessageBox.Show("Timeout waiting for valid input signal.", "Invalid frame timeout") == DialogResult.OK)
                    {
                        if (m_captureInvalidCancel != null)
                            m_captureInvalidCancel.Cancel();
                    }
                }
            }
            else if (++m_captureFrameIntervalCount == numericUpDownCaptureFrameInterval.Value)
            {
                String outputFileName = m_selectedFolder + "\\image_" + m_captureStillsCount.ToString("D4") + "." + comboBoxFileType.SelectedItem.ToString().ToLower();

                if (File.Exists(outputFileName))
                {
                    if (MessageBox.Show("File " + outputFileName + " already exists, stopping capture", "File Exists") == DialogResult.OK)
                    {
                        if (m_captureFileExists != null)
                            m_captureFileExists.Cancel();
                        return;
                    }
                }

                // Create video frame buffer to convert into BGRA32 format
                var videoBuffer = new Bgra32VideoBuffer(e.videoFrame.GetWidth(), e.videoFrame.GetHeight());

                var videoConversion = new CDeckLinkVideoConversion();
                videoConversion.ConvertNewFrame(e.videoFrame, _BMDPixelFormat.bmdFormat8BitBGRA, _BMDColorspace.bmdColorspaceUnknown, videoBuffer, out IDeckLinkVideoFrame bgra32Frame);

                videoBuffer.Bitmap.Save(outputFileName, (ImageFormat)comboBoxFileType.SelectedItem);

                m_captureFrameIntervalCount = 0;
                m_captureStillsCount++;

                if (m_captureCountdown != null)
                    m_captureCountdown.Signal();
            }
        }

        private void comboBoxCaptureDevice_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_selectedCaptureDevice = null;

            if (comboBoxCaptureDevice.SelectedIndex < 0)
                return;

            m_selectedCaptureDevice = ((StringObjectPair<DeckLinkInputDevice>)comboBoxCaptureDevice.SelectedItem).value;

            RefreshCaptureVideoModeList();
        }

        private void comboBoxPlaybackDevice_SelectedIndexChanged(object sender, EventArgs e)
        {
            m_selectedPlaybackDevice = null;

            if (comboBoxPlaybackDevice.SelectedIndex < 0)
                return;

            m_selectedPlaybackDevice = ((StringObjectPair<DeckLinkOutputDevice>)comboBoxPlaybackDevice.SelectedItem).value;
            RefreshPlaybackVideoModeList();
        }

        private void comboBoxCaptureVideoMode_SelectedIndexChanged(object sender, EventArgs e)
        {
            RefreshCapturePixelFormatList();
        }

        private void Stills_Load(object sender, EventArgs e)
        {
            EnableComponent(groupBoxCapture, false);
            EnableComponent(groupBoxPlayback, false);

            m_deckLinkDiscovery.Enable();
        }

        private void buttonBrowseDirectory_Click(object sender, EventArgs e)
        {
            // Must open Folder browse dialog in STA thread
            var dialogThread = new Thread(new ThreadStart(ShowFolderBrowserThread));
            dialogThread.SetApartmentState(ApartmentState.STA);
            dialogThread.Start();  
        }

        private void ShowFolderBrowserThread()
        {
            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
            {
                SetDirectoryPathText(folderBrowserDialog1.SelectedPath);
                
                // With directory selected, enable capture start button
                EnableComponent(buttonCaptureStartStop, true);
            }
        }


        private void comboBoxFileType_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (m_selectedFolder != null)
                RescanFolder();
        }

        private void buttonStartPlayback_Click(object sender, EventArgs e)
        {
            TogglePlayback();
        }

        private void buttonStartCapture_Click(object sender, EventArgs e)
        {
            ToggleCapture();
        }

        private void checkBoxEnableFormatDetection_CheckedChanged(object sender, EventArgs e)
        {
            EnableComponent(comboBoxCaptureVideoMode, !checkBoxEnableFormatDetection.Checked);
            EnableComponent(comboBoxCapturePixelFormat, !checkBoxEnableFormatDetection.Checked);
        }
        #endregion


        /// <summary>
        /// Used for putting the IDeckLinkDisplayMode objects into the video format
        /// combo box.
        /// </summary>

        struct DisplayModeEntry
        {
            public IDeckLinkDisplayMode displayMode;

            public DisplayModeEntry(IDeckLinkDisplayMode displayMode)
            {
                this.displayMode = displayMode;
            }

            public override string ToString()
            {
                string str;

                displayMode.GetName(out str);

                return str;
            }
        }

        /// <summary>
        /// Used for putting other object types into combo boxes.
        /// </summary>
        struct StringObjectPair<T>
        {
            public string name;
            public T value;

            public StringObjectPair(string name, T value)
            {
                this.name = name;
                this.value = value;
            }

            public override string ToString()
            {
                return name;
            }
        }


    }
}
