/// Version 1.0.0
/// Last modified 24.5.2014
///
/// Copyright (C) 2014 Veli-Mikko Puupponen
///
/// Halyri-system is a prototype emergency call system. Its purpose is to
/// demonstrate the use of the advanced capabilities available in the current
/// generation smartphones in facilitating the emergency service dispatcher's
/// capability to determine the nature of the emergency and to dispatch help.
///
/// For more information, see the README file of this package.
///
/// The MIT License (MIT)
///
/// 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.
///
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Hake_WPF.AudioVideoManagers
{
    /// Veli-Mikko Puupponen
    /// 
    /// This class provides a NAudio WaveStream with an infinite length to
    /// facilitate streaming audio playback. It contains an internal 
    /// buffer for the audio samples. If the buffer is empty, all 
    /// read requests will return the desired length of silence.
    /// If there is a pcm audio available in the buffer, it will
    /// be returned to the reader, possibly padded with silence to
    /// meet the desired read length.
    /// 
    /// 
    class BufferWaveStream: WaveStream
    {
 
        private Queue completePcmSegments;
        private byte[] overflowFromLastSegment;
        private readonly WaveFormat waveFormat;
        private object queueLock;
        private long lengthInSamples = 1;
        private long defaultPosition = 0;
        /// 
        /// Gets the streams format type.
        /// 
        public override WaveFormat WaveFormat
        {
            get { return waveFormat; }
        }
        /// 
        /// Gets the length of stream.
        /// 
        public override long Length
        {
            get { return lengthInSamples; }
        }
        /// 
        /// Sets and gets the position. It specifies the position of stream.
        /// 
        public override long Position
        {
            get
            {
                return defaultPosition;
            }
            set
            {
                throw new NotImplementedException();
            }
        }
        /// 
        /// The function initializes a new BufferWaveStream that has a WaveFormat
        /// defined by the provided parameters. The audio buffer is 
        /// empty and has no length limit.
        /// 
        /// 
        /// The aample rate im samples per second.
        /// The number of bytes per PCM sample.
        /// The number of channels.
        public BufferWaveStream(int sampleRate, int bytesPerSample, int channels) 
        {
            completePcmSegments = new Queue();
            waveFormat = new WaveFormat(sampleRate, bytesPerSample * 8, channels);
            queueLock = new object();
        }
        /// 
        /// The function writes the provided audio data to the outgoing PCM segment
        /// buffer. 
        /// 
        /// The bytes to write.
        /// The offset at which the bytes to write start.
        /// The count of the bytes to write
        public override void Write(byte[] buffer, int offset, int count)
        {
            Monitor.Enter(queueLock);
            try
            {
                byte[] newSamples = new byte[count];
                Array.Copy(buffer, offset, newSamples, 0, count);
                completePcmSegments.Enqueue(newSamples);
            } 
            finally 
            {
                Monitor.Exit(queueLock);
            }
        }
        /// 
        /// The function reads PCM samples from the underlying buffer. If no data is
        /// available, returns silent simples.
        /// 
        /// The bffer to which the data is copied to.
        /// The offset for the data at the target buffer.
        /// The desired count of data.
        /// Processed data.
        public override int Read(byte[] buffer, int offset, int count)
        {
            Monitor.Enter(queueLock);
            int actualRead = 0;
            try
            {
                if (overflowFromLastSegment != null && overflowFromLastSegment.Length > 0) 
                {
                    int fromOverflow = overflowFromLastSegment.Length > count ? count : overflowFromLastSegment.Length;
                    Array.Copy(overflowFromLastSegment, 0, buffer, offset, fromOverflow);
                    actualRead += fromOverflow;
                    if (fromOverflow < overflowFromLastSegment.Length)
                    {
                        byte[] temporaryArray = overflowFromLastSegment;
                        overflowFromLastSegment = new byte[temporaryArray.Length - fromOverflow];
                        Array.Copy(temporaryArray, fromOverflow, overflowFromLastSegment, 0, overflowFromLastSegment.Length);
                    }
                    else
                        overflowFromLastSegment = null;
                }
                if (actualRead < count)
                {
                    if (completePcmSegments.Count > 0) 
                    {
                        byte[] outgoing = completePcmSegments.Dequeue();
                        int fromComplete = outgoing.Length < (count - actualRead) ? outgoing.Length : (count - actualRead);
                        Array.Copy(outgoing, 0, buffer, offset + actualRead, fromComplete);
                        if (fromComplete < outgoing.Length) 
                        {
                            overflowFromLastSegment = new byte[outgoing.Length - fromComplete];
                            Array.Copy(outgoing, fromComplete, overflowFromLastSegment, 0, overflowFromLastSegment.Length);
                        }
                        actualRead += fromComplete;
                    }
                }
                if (actualRead < count) 
                {
                    short zero = 0;
                    byte[] zeroBytes = BitConverter.GetBytes(zero);
                    int addedZero = 0;
                    for (int i = (offset + actualRead); i < (offset + count); i += 2) 
                    {
                        buffer[i] = zeroBytes[0];
                        buffer[i+1] = zeroBytes[1];
                        addedZero += 2;
                    }
                    actualRead += addedZero;
                }
            }
            finally
            {
                Monitor.Exit(queueLock);
            }
            return actualRead;
        }
    }
}