/*
    ------------------------------------------------------------------

    This file is part of the Open Ephys GUI
    Copyright (C) 2013 Open Ephys

    ------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "AudioResamplingNode.h"
#include <stdio.h>

AudioResamplingNode::AudioResamplingNode()
    : GenericProcessor("Resampling Node"),
      sourceBufferSampleRate(40000.0), destBufferSampleRate(44100.0),
      ratio(1.0), lastRatio(1.0), destBuffer(0), tempBuffer(0),
      destBufferIsTempBuffer(true), isTransmitting(false), destBufferPos(0)
{

    settings.numInputs = 2;
    settings.numOutputs = 2;

    setPlayConfigDetails(getNumInputs(), // number of inputs
                         getNumOutputs(), // number of outputs
                         44100.0, // sampleRate
                         128);    // blockSize

    filter = new Dsp::SmoothedFilterDesign
    <Dsp::RBJ::Design::LowPass, 1> (1024);

    if (destBufferIsTempBuffer)
        destBufferWidth = 1024;
    else
        destBufferWidth = 1000;

    destBufferTimebaseSecs = 1.0;

    destBuffer = new AudioSampleBuffer(16, destBufferWidth);
    tempBuffer = new AudioSampleBuffer(16, destBufferWidth);

    continuousDataBuffer = new int16[10000];

}

AudioResamplingNode::~AudioResamplingNode()
{
    filter = 0;
    deleteAndZero(destBuffer);
    deleteAndZero(tempBuffer);

}



void AudioResamplingNode::setParameter(int parameterIndex, float newValue)
{
    editor->updateParameterButtons(parameterIndex);

    switch (parameterIndex)
    {

        case 0:
            destBufferTimebaseSecs = newValue;
            break;
        case 1:
            destBufferWidth = roundToInt(newValue);

    }

    // reset to zero and clear if timebase or width has changed.
    destBufferPos = 0;
    destBuffer->clear();

}


void AudioResamplingNode::prepareToPlay(double sampleRate_, int estimatedSamplesPerBlock)
{

    std::cout << "AudioResamplingNode preparing to play." << std::endl;

    if (destBufferIsTempBuffer)
    {
        destBufferSampleRate = sampleRate_;
        tempBuffer->setSize(getNumInputs(), 4096);
    }
    else
    {
        destBufferSampleRate = float(destBufferWidth) / destBufferTimebaseSecs;
        destBuffer->setSize(getNumInputs(), destBufferWidth);
    }

    destBuffer->clear();
    tempBuffer->clear();

    destBufferPos = 0;

    std::cout << "Temp buffer size: " << tempBuffer->getNumChannels() << " x "
              << tempBuffer->getNumSamples() << std::endl;

    updateFilter();

}

void AudioResamplingNode::updateFilter()
{

    double cutoffFreq = (ratio > 1.0) ? 2 * destBufferSampleRate  // downsample
                        : destBufferSampleRate / 2; // upsample

    double sampleFreq = (ratio > 1.0) ? sourceBufferSampleRate // downsample
                        : destBufferSampleRate;  // upsample

    Dsp::Params params;
    params[0] = sampleFreq; // sample rate
    params[1] = cutoffFreq; // cutoff frequency
    params[2] = 1.25; //Q //

    filter->setParams(params);

}

void AudioResamplingNode::releaseResources()
{

}

void AudioResamplingNode::process(AudioSampleBuffer& buffer,
                                  MidiBuffer& midiMessages,
                                  int& nSamples)
{

    int nSamps = nSamples;
    int valuesNeeded;

    if (destBufferIsTempBuffer)
    {
        ratio = float(nSamps) / float(buffer.getNumSamples());
        valuesNeeded = buffer.getNumSamples();
    }
    else
    {
        ratio = sourceBufferSampleRate / destBufferSampleRate;
        valuesNeeded = (int) buffer.getNumSamples() / ratio;
        //std::cout << std::endl;
        //std::cout << "Ratio: " << ratio << std::endl;
        //std::cout << "Values needed: " << valuesNeeded << std::endl;
    }



    if (lastRatio != ratio)
    {
        updateFilter();
        lastRatio = ratio;
    }


    if (ratio > 1.0001)
    {
        // pre-apply filter before downsampling
        filter->process(nSamps, buffer.getArrayOfChannels());
    }


    // initialize variables
    tempBuffer->clear();
    int sourceBufferPos = 0;
    int sourceBufferSize = buffer.getNumSamples();
    float subSampleOffset = 0.0;
    int nextPos = (sourceBufferPos + 1) % sourceBufferSize;

    int tempBufferPos;
    //int totalSamples = 0;

    // code modified from "juce_ResamplingAudioSource.cpp":

    for (tempBufferPos = 0; tempBufferPos < valuesNeeded; tempBufferPos++)
    {
        float gain = 1.0;
        float alpha = (float) subSampleOffset;
        float invAlpha = 1.0f - alpha;

        for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
        {

            tempBuffer->addFrom(channel, 		// destChannel
                                tempBufferPos,  // destSampleOffset
                                buffer,			// source
                                channel,		// sourceChannel
                                sourceBufferPos,// sourceSampleOffset
                                1,				// number of samples
                                invAlpha*gain);      // gain to apply to source

            tempBuffer->addFrom(channel, 		// destChannel
                                tempBufferPos,   // destSampleOffset
                                buffer,			// source
                                channel,			// sourceChannel
                                nextPos,		 	// sourceSampleOffset
                                1,				// number of samples
                                alpha*gain);     	 // gain to apply to source

        }

        subSampleOffset += ratio;



        while (subSampleOffset >= 1.0)
        {
            if (++sourceBufferPos >= sourceBufferSize)
                sourceBufferPos = 0;

            nextPos = (sourceBufferPos + 1) % sourceBufferSize;
            subSampleOffset -= 1.0;
        }
    }

    // std::cout << sourceBufferPos << " " << tempBufferPos << std::endl;


    if (ratio < 0.9999)
    {

        filter->process(tempBufferPos, tempBuffer->getArrayOfChannels());
        // apply the filter after upsampling
        ///////filter->process (totalSamples, buffer.getArrayOfChannels());
    }
    else if (ratio <= 1.0001)
    {

        // no resampling is being applied, no need to filter, BUT...
        // keep the filter stoked with samples to avoid discontinuities

    }

    if (destBufferIsTempBuffer)
    {

        // copy the temp buffer into the original buffer
        buffer = AudioSampleBuffer(tempBuffer->getArrayOfChannels(), 2, tempBufferPos);//buffer.getNumSamples());

    }
    else
    {

        //std::cout << "Copying into dest buffer..." << std::endl;

        // copy the temp buffer into the destination buffer

        int pos = 0;

        while (*tempBuffer->getSampleData(0,pos) != 0)
            pos++;

        int spaceAvailable = destBufferWidth - destBufferPos;
        int blockSize1 = (spaceAvailable > pos) ? pos : spaceAvailable;
        int blockSize2 = (spaceAvailable > pos) ? 0 : (pos - spaceAvailable);

        for (int channel = 0; channel < destBuffer->getNumChannels(); channel++)
        {

            // copy first block
            destBuffer->copyFrom(channel, 		//destChannel
                                 destBufferPos, //destStartSample
                                 *tempBuffer, 	//source
                                 channel, 		//sourceChannel
                                 0, 			//sourceStartSample
                                 blockSize1  //numSamples
                                );

            // copy second block
            destBuffer->copyFrom(channel, 		//destChannel
                                 0, 			//destStartSample
                                 *tempBuffer, 	//source
                                 channel, 		//sourceChannel
                                 blockSize1, 	//sourceStartSample
                                 blockSize2  //numSamples
                                );

        }

        //destBufferPos = (spaceAvailable > tempBufferPos) ? destBufferPos

        destBufferPos += pos;
        destBufferPos %= destBufferWidth;

        //std::cout << "Temp buffer position: " << tempBufferPos << std::endl;
        //std::cout << "Resampling node value:" << *destBuffer->getSampleData(0,0) << std::endl;

    }

}


void AudioResamplingNode::writeContinuousBuffer(float* data, int nSamples, int channel)
{

    // find file and write samples to disk
    timestamp = timer.getHighResolutionTicks();

    AudioDataConverters::convertFloatToInt16BE(data, continuousDataBuffer, nSamples);

    //int16 samps = nSamples;

    fwrite(&timestamp,							// ptr
           8,   							// size of each element
           1, 		  						// count
           file);   // ptr to FILE object

    fwrite(&nSamples,								// ptr
           sizeof(nSamples),   				// size of each element
           1, 		  						// count
           file);   // ptr to FILE object

    fwrite(continuousDataBuffer,			// ptr
           2,			     					// size of each element
           nSamples, 		  					// count
           file);   // ptr to FILE object
    // FIXME: check that return value of each fwrite() equals "count";
    // otherwise, there was an error.
}