#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.Text;
using System.Diagnostics;

using OpenTK.Audio.OpenAL;

namespace OpenTK.Audio
{
    /// <summary>
    /// Provides methods to instantiate, use and destroy an audio context for playback.
    /// Static methods are provided to list available devices known by the driver.
    /// </summary>
    public sealed class AudioContext : IDisposable
    {
        #region --- Fields ---

        bool disposed;
        bool is_processing, is_synchronized;
        IntPtr device_handle;
        ContextHandle context_handle;
        bool context_exists;

        string device_name;
        static object audio_context_lock = new object();
        static Dictionary<ContextHandle, AudioContext> available_contexts = new Dictionary<ContextHandle, AudioContext>();

        #endregion

        #region --- Constructors ---

        #region static AudioContext()

        /// \internal
        /// <summary>
        /// Runs before the actual class constructor, to load available devices.
        /// </summary>
        static AudioContext()
        {
            if (AudioDeviceEnumerator.IsOpenALSupported) // forces enumeration
            { }
        }

        #endregion static AudioContext()

        #region public AudioContext()

        /// <summary>Constructs a new AudioContext, using the default audio device.</summary>
        public AudioContext()
            : this(null, 0, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }

        #endregion

        #region public AudioContext(string device)

        /// <summary>
        /// Constructs a new AudioContext instance.
        /// </summary>
        /// <param name="device">The device name that will host this instance.</param>
        public AudioContext(string device) : this(device, 0, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }

        #endregion

        #region public AudioContext(string device, int freq)

        /// <summary>Constructs a new AudioContext, using the specified audio device and device parameters.</summary>
        /// <param name="device">The name of the audio device to use.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <remarks>
        /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
        /// devices.
        /// </remarks>
        public AudioContext(string device, int freq) : this(device, freq, 0, false, true, MaxAuxiliarySends.UseDriverDefault) { }

        #endregion

        #region public AudioContext(string device, int freq, int refresh)

        /// <summary>Constructs a new AudioContext, using the specified audio device and device parameters.</summary>
        /// <param name="device">The name of the audio device to use.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="refresh">Refresh intervals, in units of Hz. Pass 0 for driver default.</param>
        /// <remarks>
        /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
        /// devices.
        /// </remarks>
        public AudioContext(string device, int freq, int refresh)
            : this(device, freq, refresh, false, true, MaxAuxiliarySends.UseDriverDefault) { }

        #endregion

        #region public AudioContext(string device, int freq, int refresh, bool sync)

        /// <summary>Constructs a new AudioContext, using the specified audio device and device parameters.</summary>
        /// <param name="device">The name of the audio device to use.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="refresh">Refresh intervals, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="sync">Flag, indicating a synchronous context.</param>
        /// <remarks>
        /// Use AudioContext.AvailableDevices to obtain a list of all available audio devices.
        /// devices.
        /// </remarks>
        public AudioContext(string device, int freq, int refresh, bool sync)
            : this(AudioDeviceEnumerator.AvailablePlaybackDevices[0], freq, refresh, sync, true) { }

        #endregion

        #region public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx)

        /// <summary>Creates the audio context using the specified device and device parameters.</summary>
        /// <param name="device">The device descriptor obtained through AudioContext.AvailableDevices.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="refresh">Refresh intervals, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="sync">Flag, indicating a synchronous context.</param>
        /// <param name="enableEfx">Indicates whether the EFX extension should be initialized, if present.</param>
        /// <exception cref="ArgumentNullException">Occurs when the device string is invalid.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Occurs when a specified parameter is invalid.</exception>
        /// <exception cref="AudioDeviceException">
        /// Occurs when the specified device is not available, or is in use by another program.
        /// </exception>
        /// <exception cref="AudioContextException">
        /// Occurs when an audio context could not be created with the specified parameters.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// Occurs when an AudioContext already exists.</exception>
        /// <remarks>
        /// <para>For maximum compatibility, you are strongly recommended to use the default constructor.</para>
        /// <para>Multiple AudioContexts are not supported at this point.</para>
        /// <para>
        /// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
        /// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
        /// Values higher than supported will be clamped by the driver.
        /// </para>
        /// </remarks>
        public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx)
        {
            CreateContext(device, freq, refresh, sync, enableEfx, MaxAuxiliarySends.UseDriverDefault);
        }

        #endregion

        #region public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxMaxAuxSends)

        /// <summary>Creates the audio context using the specified device and device parameters.</summary>
        /// <param name="device">The device descriptor obtained through AudioContext.AvailableDevices.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="refresh">Refresh intervals, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="sync">Flag, indicating a synchronous context.</param>
        /// <param name="enableEfx">Indicates whether the EFX extension should be initialized, if present.</param>
        /// <param name="efxMaxAuxSends">Requires EFX enabled. The number of desired Auxiliary Sends per source.</param>
        /// <exception cref="ArgumentNullException">Occurs when the device string is invalid.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Occurs when a specified parameter is invalid.</exception>
        /// <exception cref="AudioDeviceException">
        /// Occurs when the specified device is not available, or is in use by another program.
        /// </exception>
        /// <exception cref="AudioContextException">
        /// Occurs when an audio context could not be created with the specified parameters.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// Occurs when an AudioContext already exists.</exception>
        /// <remarks>
        /// <para>For maximum compatibility, you are strongly recommended to use the default constructor.</para>
        /// <para>Multiple AudioContexts are not supported at this point.</para>
        /// <para>
        /// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
        /// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
        /// Values higher than supported will be clamped by the driver.
        /// </para>
        /// </remarks>
        public AudioContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxMaxAuxSends)
        {
            CreateContext(device, freq, refresh, sync, enableEfx, efxMaxAuxSends);
        }

        #endregion

        #endregion --- Constructors ---

        #region --- Private Methods ---

        #region CreateContext

        /// <summary>May be passed at context construction time to indicate the number of desired auxiliary effect slot sends per source.</summary>
        public enum MaxAuxiliarySends:int
        {
            /// <summary>Will chose a reliably working parameter.</summary>
            UseDriverDefault = 0,
            /// <summary>One send per source.</summary>
            One = 1,
            /// <summary>Two sends per source.</summary>
            Two = 2,
            /// <summary>Three sends per source.</summary>
            Three = 3,
            /// <summary>Four sends per source.</summary>
            Four = 4,
        }

        /// \internal
        /// <summary>Creates the audio context using the specified device.</summary>
        /// <param name="device">The device descriptor obtained through AudioContext.AvailableDevices, or null for the default device.</param>
        /// <param name="freq">Frequency for mixing output buffer, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="refresh">Refresh intervals, in units of Hz. Pass 0 for driver default.</param>
        /// <param name="sync">Flag, indicating a synchronous context.</param>
        /// <param name="enableEfx">Indicates whether the EFX extension should be initialized, if present.</param>
        /// <param name="efxAuxiliarySends">Requires EFX enabled. The number of desired Auxiliary Sends per source.</param>
        /// <exception cref="ArgumentOutOfRangeException">Occurs when a specified parameter is invalid.</exception>
        /// <exception cref="AudioDeviceException">
        /// Occurs when the specified device is not available, or is in use by another program.
        /// </exception>
        /// <exception cref="AudioContextException">
        /// Occurs when an audio context could not be created with the specified parameters.
        /// </exception>
        /// <exception cref="NotSupportedException">
        /// Occurs when an AudioContext already exists.</exception>
        /// <remarks>
        /// <para>For maximum compatibility, you are strongly recommended to use the default constructor.</para>
        /// <para>Multiple AudioContexts are not supported at this point.</para>
        /// <para>
        /// The number of auxilliary EFX sends depends on the audio hardware and drivers. Most Realtek devices, as well
        /// as the Creative SB Live!, support 1 auxilliary send. Creative's Audigy and X-Fi series support 4 sends.
        /// Values higher than supported will be clamped by the driver.
        /// </para>
        /// </remarks>
        void CreateContext(string device, int freq, int refresh, bool sync, bool enableEfx, MaxAuxiliarySends efxAuxiliarySends)
        {
            if (!AudioDeviceEnumerator.IsOpenALSupported)
                throw new DllNotFoundException("openal32.dll");

            if (AudioDeviceEnumerator.Version == AudioDeviceEnumerator.AlcVersion.Alc1_1 && AudioDeviceEnumerator.AvailablePlaybackDevices.Count == 0)    // Alc 1.0 does not support device enumeration.
                throw new NotSupportedException("No audio hardware is available.");
            if (context_exists) throw new NotSupportedException("Multiple AudioContexts are not supported.");
            if (freq < 0) throw new ArgumentOutOfRangeException("freq", freq, "Should be greater than zero.");
            if (refresh < 0) throw new ArgumentOutOfRangeException("refresh", refresh, "Should be greater than zero.");


            if (!String.IsNullOrEmpty(device))
            {
                device_name = device;
                device_handle = Alc.OpenDevice(device); // try to open device by name
            }
            if (device_handle == IntPtr.Zero)
            {
                device_name = "IntPtr.Zero (null string)";
                device_handle = Alc.OpenDevice(null); // try to open unnamed default device
            }
            if (device_handle == IntPtr.Zero)
            {
                device_name = AudioContext.DefaultDevice;
                device_handle = Alc.OpenDevice(AudioContext.DefaultDevice); // try to open named default device
            }
            if (device_handle == IntPtr.Zero)
            {
                device_name = "None";
                throw new AudioDeviceException(String.Format("Audio device '{0}' does not exist or is tied up by another application.",
                    String.IsNullOrEmpty(device) ? "default" : device));
            }

            CheckErrors();

            // Build the attribute list
            List<int> attributes = new List<int>();

            if (freq != 0)
            {
                attributes.Add((int)AlcContextAttributes.Frequency);
                attributes.Add(freq);
            }

            if (refresh != 0)
            {
                attributes.Add((int)AlcContextAttributes.Refresh);
                attributes.Add(refresh);
            }

            attributes.Add((int)AlcContextAttributes.Sync);
            attributes.Add(sync ? 1 : 0);

            if (enableEfx && Alc.IsExtensionPresent(device_handle, "ALC_EXT_EFX"))
            {
                int num_slots;
                switch (efxAuxiliarySends)
                {
                    case MaxAuxiliarySends.One:
                    case MaxAuxiliarySends.Two:
                    case MaxAuxiliarySends.Three:
                    case MaxAuxiliarySends.Four:
                        num_slots = (int)efxAuxiliarySends;
                        break;
                    default:
                    case MaxAuxiliarySends.UseDriverDefault:
                        Alc.GetInteger(device_handle, AlcGetInteger.EfxMaxAuxiliarySends, 1, out num_slots);
                        break;
                }
              
                attributes.Add((int)AlcContextAttributes.EfxMaxAuxiliarySends);
                attributes.Add(num_slots);
            }
            attributes.Add(0);

            context_handle = Alc.CreateContext(device_handle, attributes.ToArray());

            if (context_handle == ContextHandle.Zero)
            {
                Alc.CloseDevice(device_handle);
                throw new AudioContextException("The audio context could not be created with the specified parameters.");
            }

            CheckErrors();

            // HACK: OpenAL SI on Linux/ALSA crashes on MakeCurrent. This hack avoids calling MakeCurrent when
            // an old OpenAL version is detect - it may affect outdated OpenAL versions different than OpenAL SI,
            // but it looks like a good compromise for now.
            if (AudioDeviceEnumerator.AvailablePlaybackDevices.Count > 0)
                MakeCurrent();

            CheckErrors();

            device_name = Alc.GetString(device_handle, AlcGetString.DeviceSpecifier);
 

            lock (audio_context_lock)
            {
                available_contexts.Add(this.context_handle, this);
                context_exists = true;
            }
        }

        #endregion --- Private Methods ---

        #region static void MakeCurrent(AudioContext context)

        /// \internal
        /// <summary>Makes the specified AudioContext current in the calling thread.</summary>
        /// <param name="context">The OpenTK.Audio.AudioContext to make current, or null.</param>
        /// <exception cref="ObjectDisposedException">
        /// Occurs if this function is called after the AudioContext has been disposed.
        /// </exception>
        /// <exception cref="AudioContextException">
        /// Occurs when the AudioContext could not be made current.
        /// </exception>
        static void MakeCurrent(AudioContext context)
        {
            lock (audio_context_lock)
            {
                if (!Alc.MakeContextCurrent(context != null ? context.context_handle : ContextHandle.Zero))
                    throw new AudioContextException(String.Format("ALC {0} error detected at {1}.",
                        Alc.GetError(context != null ? (IntPtr)context.context_handle : IntPtr.Zero).ToString(),
                        context != null ? context.ToString() : "null"));
            }
        }

        #endregion

        #region internal bool IsCurrent

        /// <summary>
        /// Gets or sets a System.Boolean indicating whether the AudioContext
        /// is current.
        /// </summary>
        /// <remarks>
        /// Only one AudioContext can be current in the application at any time,
        /// <b>regardless of the number of threads</b>.
        /// </remarks>
        internal bool IsCurrent
        {
            get
            {
                lock (audio_context_lock)
                {
                    if (available_contexts.Count == 0)
                        return false;
                    else
                    {
                        return AudioContext.CurrentContext == this;
                    }
                }
            }
            set
            {
                if (value) AudioContext.MakeCurrent(this);
                else AudioContext.MakeCurrent(null);
            }
        }

        #endregion

        #region IntPtr Device

        IntPtr Device { get { return device_handle; } }

        #endregion

        #endregion

        #region --- Public Members ---

        #region CheckErrors

        /// <summary>
        /// Checks for ALC error conditions.
        /// </summary>
        /// <exception cref="OutOfMemoryException">Raised when an out of memory error is detected.</exception>
        /// <exception cref="AudioValueException">Raised when an invalid value is detected.</exception>
        /// <exception cref="AudioDeviceException">Raised when an invalid device is detected.</exception>
        /// <exception cref="AudioContextException">Raised when an invalid context is detected.</exception>
        public void CheckErrors()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().FullName);

            new AudioDeviceErrorChecker(device_handle).Dispose();
        }

        #endregion

        #region CurrentError

        /// <summary>
        /// Returns the ALC error code for this instance.
        /// </summary>
        public AlcError CurrentError
        {
            get
            {
                if (disposed)
                    throw new ObjectDisposedException(this.GetType().FullName);

                return Alc.GetError(device_handle);
            }
        }

        #endregion

        #region MakeCurrent

        /// <summary>Makes the AudioContext current in the calling thread.</summary>
        /// <exception cref="ObjectDisposedException">
        /// Occurs if this function is called after the AudioContext has been disposed.
        /// </exception>
        /// <exception cref="AudioContextException">
        /// Occurs when the AudioContext could not be made current.
        /// </exception>
        /// <remarks>
        /// Only one AudioContext can be current in the application at any time,
        /// <b>regardless of the number of threads</b>.
        /// </remarks>
        public void MakeCurrent()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().FullName);

            AudioContext.MakeCurrent(this);
        }

        #endregion

        #region IsProcessing

        /// <summary>
        /// Gets a System.Boolean indicating whether the AudioContext is
        /// currently processing audio events.
        /// </summary>
        /// <seealso cref="Process"/>
        /// <seealso cref="Suspend"/>
        public bool IsProcessing
        {
            get
            {
                if (disposed)
                    throw new ObjectDisposedException(this.GetType().FullName);

                return is_processing;
            }
            private set { is_processing = value; }
        }

        #endregion

        #region IsSynchronized

        /// <summary>
        /// Gets a System.Boolean indicating whether the AudioContext is
        /// synchronized.
        /// </summary>
        /// <seealso cref="Process"/>
        public bool IsSynchronized
        {
            get
            {
                if (disposed)
                    throw new ObjectDisposedException(this.GetType().FullName);

                return is_synchronized;
            }
            private set { is_synchronized = value; }
        }

        #endregion

        #region public void Process

        /// <summary>
        /// Processes queued audio events.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If AudioContext.IsSynchronized is true, this function will resume
        /// the internal audio processing thread. If AudioContext.IsSynchronized is false,
        /// you will need to call this function multiple times per second to process
        /// audio events.
        /// </para>
        /// <para>
        /// In some implementations this function may have no effect.
        /// </para>
        /// </remarks>
        /// <exception cref="ObjectDisposedException">Occurs when this function is called after the AudioContext had been disposed.</exception>
        /// <seealso cref="Suspend"/>
        /// <seealso cref="IsProcessing"/>
        /// <seealso cref="IsSynchronized"/>
        public void Process()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().FullName);

            Alc.ProcessContext(this.context_handle);
            IsProcessing = true;
        }

        #endregion

        #region public void Suspend

        /// <summary>
        /// Suspends processing of audio events.
        /// </summary>
        /// <remarks>
        /// <para>
        /// To avoid audio artifacts when calling this function, set audio gain to zero before
        /// suspending an AudioContext.
        /// </para>
        /// <para>
        /// In some implementations, it can be faster to suspend processing before changing
        /// AudioContext state.
        /// </para>
        /// <para>
        /// In some implementations this function may have no effect.
        /// </para>
        /// </remarks>
        /// <exception cref="ObjectDisposedException">Occurs when this function is called after the AudioContext had been disposed.</exception>
        /// <seealso cref="Process"/>
        /// <seealso cref="IsProcessing"/>
        /// <seealso cref="IsSynchronized"/>
        public void Suspend()
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().FullName);

            Alc.SuspendContext(this.context_handle);
            IsProcessing = false;
        }

        #endregion

        #region public bool SupportsExtension(string extension)

        /// <summary>
        /// Checks whether the specified OpenAL extension is supported.
        /// </summary>
        /// <param name="extension">The name of the extension to check (e.g. "ALC_EXT_EFX").</param>
        /// <returns>true if the extension is supported; false otherwise.</returns>
        public bool SupportsExtension(string extension)
        {
            if (disposed)
                throw new ObjectDisposedException(this.GetType().FullName);

            return Alc.IsExtensionPresent(this.Device, extension);
        }

        #endregion

        #region CurrentDevice

        /// <summary>
        /// Gets a System.String with the name of the device used in this context.
        /// </summary>
        public string CurrentDevice
        {
            get
            {
                if (disposed)
                    throw new ObjectDisposedException(this.GetType().FullName);

                return device_name;
            }
        }

        #endregion

        #endregion --- Public Members ---

        #region --- Static Members ---

        #region public static AudioContext CurrentContext

        /// <summary>
        /// Gets the OpenTK.Audio.AudioContext which is current in the application.
        /// </summary>
        /// <remarks>
        /// Only one AudioContext can be current in the application at any time,
        /// <b>regardless of the number of threads</b>.
        /// </remarks>
        public static AudioContext CurrentContext
        {
            get
            {
                lock (audio_context_lock)
                {
                    if (available_contexts.Count == 0)
                        return null;
                    else
                    {
                        AudioContext context;
                        AudioContext.available_contexts.TryGetValue(
                            (ContextHandle)Alc.GetCurrentContext(),
                            out context);
                        return context;
                    }
                }
            }
        }

        #endregion

        #region AvailableDevices

        /// <summary>
        /// Returns a list of strings containing all known playback devices.
        /// </summary>
        public static IList<string> AvailableDevices
        {
            get
            {
                return AudioDeviceEnumerator.AvailablePlaybackDevices;
            }
        }
        #endregion public static IList<string> AvailablePlaybackDevices

        #region DefaultDevice

        /// <summary>
        /// Returns the name of the device that will be used as playback default.
        /// </summary>
        public static string DefaultDevice
        {
            get
            {
                return AudioDeviceEnumerator.DefaultPlaybackDevice;
            }
        }

        #endregion

        #endregion

        #region --- IDisposable Members ---

        /// <summary>
        /// Disposes of the AudioContext, cleaning up all resources consumed by it.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        void Dispose(bool manual)
        {
            if (!disposed)
            {
                if (this.IsCurrent)
                    this.IsCurrent = false;

                if (context_handle != ContextHandle.Zero)
                {
                    available_contexts.Remove(context_handle);
                    Alc.DestroyContext(context_handle);
                }

                if (device_handle != IntPtr.Zero)
                    Alc.CloseDevice(device_handle);

                if (manual)
                {
                }
                disposed = true;
            }
        }

        /// <summary>
        /// Finalizes this instance.
        /// </summary>
        ~AudioContext()
        {
            this.Dispose(false);
        }

        #endregion

        #region --- Overrides ---

        /// <summary>
        /// Calculates the hash code for this instance.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Compares this instance with another.
        /// </summary>
        /// <param name="obj">The instance to compare to.</param>
        /// <returns>True, if obj refers to this instance; false otherwise.</returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that desrcibes this instance.
        /// </summary>
        /// <returns>A <see cref="System.String"/> that desrcibes this instance.</returns>
        public override string ToString()
        {
            return String.Format("{0} (handle: {1}, device: {2})",
                                 this.device_name, this.context_handle, this.device_handle);
        }

        #endregion
    }
}
