/*
 ==============================================================================
 This file is part of the IEM plug-in suite.
 Authors: Daniel Rudrich
 Copyright (c) 2018 - Institute of Electronic Music and Acoustics (IEM)
 https://iem.at

 The IEM plug-in suite 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.

 The IEM plug-in suite 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 software.  If not, see <https://www.gnu.org/licenses/>.
 ==============================================================================
 */

#pragma once
#include "../JuceLibraryCode/JuceHeader.h"

using namespace juce::dsp;
class Delay : private ProcessorBase
{
public:
    Delay() {}
    ~Delay() override = default;

    void setDelayTime (float delayTimeInSeconds)
    {
        if (delayTimeInSeconds <= 0.0f)
        {
            delay = 0.0f;
            bypassed = true;
        }
        else
        {
            delay = delayTimeInSeconds;
            bypassed = false;
        }

        prepare (spec);
    }

    const int getDelayInSamples() { return bypassed ? 0 : delayInSamples; }

    void prepare (const juce::dsp::ProcessSpec& specs) override
    {
        spec = specs;

        delayInSamples = delay * specs.sampleRate;

        buffer.setSize (specs.numChannels, specs.maximumBlockSize + delayInSamples);
        buffer.clear();
        writePosition = 0;
    }

    void process (const juce::dsp::ProcessContextReplacing<float>& context) override
    {
        juce::ScopedNoDenormals noDenormals;

        if (! bypassed)
        {
            auto abIn = context.getInputBlock();
            auto abOut = context.getOutputBlock();
            auto L = abIn.getNumSamples();
            auto nCh = juce::jmin ((int) spec.numChannels, (int) abIn.getNumChannels());

            int startIndex, blockSize1, blockSize2;

            // write in delay line
            getReadWritePositions (false, (int) L, startIndex, blockSize1, blockSize2);

            for (int ch = 0; ch < nCh; ch++)
                buffer.copyFrom (ch, startIndex, abIn.getChannelPointer (ch), blockSize1);

            if (blockSize2 > 0)
                for (int ch = 0; ch < nCh; ch++)
                    buffer.copyFrom (ch, 0, abIn.getChannelPointer (ch) + blockSize1, blockSize2);

            // read from delay line
            getReadWritePositions (true, (int) L, startIndex, blockSize1, blockSize2);

            for (int ch = 0; ch < nCh; ch++)
                juce::FloatVectorOperations::copy (abOut.getChannelPointer (ch),
                                                   buffer.getReadPointer (ch) + startIndex,
                                                   blockSize1);

            if (blockSize2 > 0)
                for (int ch = 0; ch < nCh; ch++)
                    juce::FloatVectorOperations::copy (abOut.getChannelPointer (ch) + blockSize1,
                                                       buffer.getReadPointer (ch),
                                                       blockSize2);

            writePosition += L;
            writePosition = writePosition % buffer.getNumSamples();
        }
    }

    void reset() override {}

    void getReadWritePositions (bool read,
                                int numSamples,
                                int& startIndex,
                                int& blockSize1,
                                int& blockSize2)
    {
        const int L = buffer.getNumSamples();
        int pos = writePosition;
        if (read)
        {
            pos = writePosition - delayInSamples;
        }
        if (pos < 0)
            pos = pos + L;
        pos = pos % L;

        jassert (pos >= 0 && pos < L);

        if (numSamples <= 0)
        {
            startIndex = 0;
            blockSize1 = 0;
            blockSize2 = 0;
        }
        else
        {
            startIndex = pos;
            blockSize1 = juce::jmin (L - pos, numSamples);
            numSamples -= blockSize1;
            blockSize2 = numSamples <= 0 ? 0 : numSamples;
        }
    }

private:
    //==============================================================================
    juce::dsp::ProcessSpec spec = { -1, 0, 0 };
    float delay;
    int delayInSamples = 0;
    bool bypassed = false;
    int writePosition = 0;
    juce::AudioBuffer<float> buffer;
};
