#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to 
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

using OpenTK;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;

namespace Examples
{
    [Example("Parrot Audio Capture", ExampleCategory.OpenAL, "1.1", Documentation = "Parrot")]
    public partial class Parrot : Form
    {
        #region Fields

        AudioContext audio_context;
        AudioCapture audio_capture;

        int src;
        short[] buffer = new short[512];
        const byte SampleToByte = 2;

        #endregion

        #region Constructors

        public Parrot()
        {
            InitializeComponent();

            this.Text = "OpenAL Parrot (" + (IntPtr.Size == 4 ? "x86" : "x64") + ")";

            // Add available capture devices to the combo box.
            IList<string> recorders = AudioCapture.AvailableDevices;
            for (int i = 0; i < recorders.Count; i++)
            {
                if (!String.IsNullOrEmpty(recorders[i]))
                    comboBox_RecorderSelection.Items.Add(recorders[i]);
            }
            if (comboBox_RecorderSelection.Items.Count > 0)
                comboBox_RecorderSelection.SelectedIndex = 0;
        }

        #endregion

        #region Events

        private void button_Start_Click(object sender, EventArgs e)
        {
            if (audio_capture == null || !audio_capture.IsRunning)
            {
                button_Start.Text = "Stop Recording";
                groupBox_RecorderParameters.Enabled = false;
                this.StartRecording();
            }
            else
            {
                button_Start.Text = "Start Recording";
                groupBox_RecorderParameters.Enabled = true;
                this.StopRecording();
            }
        }

        private void timer_UpdateSamples_Tick(object sender, EventArgs e)
        {
            this.UpdateSamples();
        }

        private void Parrot_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.StopRecording();
        }

        #endregion

        #region Private Members

        void StartRecording()
        {
            try
            {
                audio_context = new AudioContext();
            }
            catch (AudioException ae)
            {
                MessageBox.Show("Fatal: Cannot continue without a playback device.\nException caught when opening playback device.\n" + ae.Message);
                Application.Exit();
            }

            AL.Listener(ALListenerf.Gain, (float)numericUpDown_PlaybackGain.Value);
            src = AL.GenSource();

            int sampling_rate = (int)numericUpDown_Frequency.Value;
            double buffer_length_ms = (double)numericUpDown_BufferLength.Value;
            int buffer_length_samples = (int)((double)numericUpDown_BufferLength.Value * sampling_rate * 0.001 / BlittableValueType.StrideOf(buffer));

            try
            {
                audio_capture = new AudioCapture((string)comboBox_RecorderSelection.SelectedItem,
                    sampling_rate, ALFormat.Mono16, buffer_length_samples);
            }
            catch (AudioDeviceException ade)
            {
                MessageBox.Show("Exception caught when opening recording device.\n" + ade.Message);
                audio_capture = null;
            }

            if (audio_capture == null)
                return;

            audio_capture.Start();

            timer_GetSamples.Start();
            timer_GetSamples.Interval = (int)(buffer_length_ms / 2 + 0.5);   // Tick when half the buffer is full.
        }

        void StopRecording()
        {
            timer_GetSamples.Stop();

            if (audio_capture != null)
            {
                audio_capture.Stop();
                audio_capture.Dispose();
                audio_capture = null;
            }

            if (audio_context != null)
            {
                int r;
                AL.GetSource(src, ALGetSourcei.BuffersQueued, out r);
                ClearBuffers(r);

                AL.DeleteSource(src);

                audio_context.Dispose();
                audio_context = null;
            }
        }

        void UpdateSamples()
        {
            if (audio_capture == null)
                return;

            int available_samples = audio_capture.AvailableSamples;

            if (available_samples * SampleToByte > buffer.Length * BlittableValueType.StrideOf(buffer))
            {
                buffer = new short[MathHelper.NextPowerOfTwo(
                    (int)(available_samples * SampleToByte / (double)BlittableValueType.StrideOf(buffer) + 0.5))];
            }

            if (available_samples > 0)
            {
                audio_capture.ReadSamples(buffer, available_samples);

                int buf = AL.GenBuffer();
                AL.BufferData(buf, ALFormat.Mono16, buffer, (int)(available_samples * BlittableValueType.StrideOf(buffer)), audio_capture.SampleFrequency);
                AL.SourceQueueBuffer(src, buf);

                label_SamplesConsumed.Text = "Samples consumed: " + available_samples;

                if (AL.GetSourceState(src) != ALSourceState.Playing)
                    AL.SourcePlay(src);
            }
            
            ClearBuffers(0);
        }

        void ClearBuffers(int input)
        {
            if (audio_context == null || audio_context == null)
                return;

            int[] freedbuffers;
            if (input == 0)
            {
                int BuffersProcessed;
                AL.GetSource(src, ALGetSourcei.BuffersProcessed, out BuffersProcessed);
                if (BuffersProcessed == 0)
                    return;
                freedbuffers = AL.SourceUnqueueBuffers(src, BuffersProcessed);
            }
            else
            {
                freedbuffers = AL.SourceUnqueueBuffers(src, input);
            }
            AL.DeleteBuffers(freedbuffers);
        }

        #endregion

        #region Main

        [STAThread]
        static void Main()
        {
            using (Parrot parrot = new Parrot())
            {
                parrot.ShowDialog();
            }
        }

        #endregion
    }
}