/*
 * Klang – a node+text-based synthesizer library
 *
 * This file is part of the *wellen* library (https://github.com/dennisppaul/wellen).
 * Copyright (c) 2022 Dennis P Paul.
 *
 * This library 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, version 3.
 *
 * This library 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/>.
 */

/**
 *       [ NODE_CHORUS         ]
 *       +---------------------+
 *       |                     |
 * IN00--| SIGNAL  SIGNAL_LEFT |--OUT00
 *       |        SIGNAL_RIGHT |--OUT01
 *       |                     |
 *       +---------------------+
 */

#ifndef NodeChorus_hpp
#define NodeChorus_hpp

#include "KlangNode.hpp"
#include "LUTSine.hpp"

namespace klang {
    class NodeChorus : public Node {
    public:
        static const CHANNEL_ID NUM_CH_IN           = 1;
        static const CHANNEL_ID CH_OUT_SIGNAL_LEFT  = 0;
        static const CHANNEL_ID CH_OUT_SIGNAL_RIGHT = 1;
        static const CHANNEL_ID NUM_CH_OUT          = 2;

        NodeChorus() {
            Chorus_init();
        }

        bool connect(Connection *pConnection, CHANNEL_ID pInChannel) {
            if (pInChannel == CH_IN_SIGNAL) {
                mConnection_CH_IN_SIGNAL = pConnection;
                return true;
            }
            return false;
        }

        bool disconnect(CHANNEL_ID pInChannel) {
            if (pInChannel == CH_IN_SIGNAL) {
                mConnection_CH_IN_SIGNAL = nullptr;
                return true;
            }
            return false;
        }

        void update(CHANNEL_ID pChannel, float *pAudioBlock) {
            if (is_not_updated()) {
                if (mConnection_CH_IN_SIGNAL != nullptr) {
                    AUDIO_BLOCK_ID mBlock_IN_SIGNAL = AudioBlockPool::instance().request();
                    mConnection_CH_IN_SIGNAL->update(mBlock_IN_SIGNAL);
                    /* process and store output blocks */
                    float *mBlockData_IN_SIGNAL    = AudioBlockPool::instance().data(mBlock_IN_SIGNAL);
                    mBlock_OUT_SIGNAL_L                  = AudioBlockPool::instance().request();
                    float *mBlockData_OUT_SIGNAL_L = AudioBlockPool::instance().data(mBlock_OUT_SIGNAL_L);
                    mBlock_OUT_SIGNAL_R                  = AudioBlockPool::NO_ID;
                    float *mBlockData_OUT_SIGNAL_R = nullptr;
                    if (mStereoOutput) {
                        mBlock_OUT_SIGNAL_R     = AudioBlockPool::instance().request();
                        mBlockData_OUT_SIGNAL_R = AudioBlockPool::instance().data(mBlock_OUT_SIGNAL_R);
                    }
                    for (uint16_t i = 0; i < KLANG_SAMPLES_PER_AUDIO_BLOCK; i++) {
                        mBlockData_OUT_SIGNAL_L[i] = mono_chorus_compute(&delL, &lfoL, mBlockData_IN_SIGNAL[i]);
                        if (mStereoOutput) {
                            mBlockData_OUT_SIGNAL_R[i] = mono_chorus_compute(&delR, &lfoR, mBlockData_IN_SIGNAL[i]);
                        }
                    }
                    AudioBlockPool::instance().release(mBlock_IN_SIGNAL);
                }
                flag_updated();
            }
            if (pChannel == CH_OUT_SIGNAL_LEFT && mBlock_OUT_SIGNAL_L != AudioBlockPool::NO_ID) {
                float *mBlockData_OUT_SIGNAL_L = AudioBlockPool::instance().data(mBlock_OUT_SIGNAL_L);
                for (uint16_t i = 0; i < KLANG_SAMPLES_PER_AUDIO_BLOCK; i++) {
                    pAudioBlock[i] = mBlockData_OUT_SIGNAL_L[i];
                }
                // @TODO(should probably use `std::copy` here
                // memcpy( mBlockData_OUT_SIGNAL_L,
                //         pAudioBlock,
                //         sizeof(float) * KLANG_SAMPLES_PER_AUDIO_BLOCK);
            } else if (pChannel == CH_OUT_SIGNAL_RIGHT && mStereoOutput && mBlock_OUT_SIGNAL_R != AudioBlockPool::NO_ID) {
                float *mBlockData_OUT_SIGNAL_R = AudioBlockPool::instance().data(mBlock_OUT_SIGNAL_R);
                for (uint16_t i = 0; i < KLANG_SAMPLES_PER_AUDIO_BLOCK; i++) {
                    pAudioBlock[i] = mBlockData_OUT_SIGNAL_R[i];
                }
            }
        }

        void set_stereo(bool pStereoOutput) {
            mStereoOutput = pStereoOutput;
        }

        void set_feedback(float pValue) {
            ChorusFeedback_set(pValue);
        }

        void set_rate(float pValue) {
            ChorusRate_set(pValue);
        }

        void set_delay(float pValue) {
            ChorusDelay_set(pValue);
        }

        void set_sweep(float pValue) {
            ChorusSweep_set(pValue);
        }

        void toggle_mode() {
            ChorusMode_toggle();
        }

        /**
         * @brief set constant or variable delayed feedback
         *
         * @param pValue
         */
        void set_mode(bool pValue) {
            ChorusMode_switch(pValue ? 127 : 0);
        }

        void toggle_feedback_sign() {
            ChorusFDBsign_change();
        }

        void set_feedback_sign(bool pValue) {
            ChorusFDBsign_switch(pValue ? 127 : 0);
        }

        void set_command(KLANG_CMD_TYPE pCommand, KLANG_CMD_TYPE *pPayLoad) {
            switch (pCommand) {
                case KLANG_SET_STEREO_I8:
                    set_stereo(pPayLoad[0]);
                    break;
                case KLANG_SET_FEEDBACK_F32:
                    set_feedback(KlangMath::FLOAT_32(pPayLoad));
                    break;
                case KLANG_SET_DELAY_F32:
                    set_delay(KlangMath::FLOAT_32(pPayLoad));
                    break;
                case KLANG_SET_RATE_F32:
                    set_rate(KlangMath::FLOAT_32(pPayLoad));
                    break;
                case KLANG_SET_SWEEP_F32:
                    set_sweep(KlangMath::FLOAT_32(pPayLoad));
                    break;
            }
        }

    private:
        AUDIO_BLOCK_ID mBlock_OUT_SIGNAL_L = AudioBlockPool::NO_ID;
        AUDIO_BLOCK_ID mBlock_OUT_SIGNAL_R = AudioBlockPool::NO_ID;

        Connection *mConnection_CH_IN_SIGNAL = nullptr;

        bool mStereoOutput = true;

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

#define DEPTH       1400    // Size of delay buffer, in samples : 29.17 ms
#define LEFT_DELAY  3 * 240 /*  initial left delay */
#define RIGHT_DELAY 3 * 240 /*  initial right delay */
#define LEFT_SWEEP  2 * 50
#define RIGHT_SWEEP 2 * 50
#define MAX_RATE    7.f        // in Hz
#define MIN_RATE    0.02f      // in Hz
#define LEFT_RATE   0.11f      // in Hz
#define RIGHT_RATE  0.12f      // in Hz
#define FEEDBACK    2 * -0.2f  // look at the diagram
#define FORWARD     0.5f
#define MIX         0.3f

        float Fs = ((float)(KLANG_AUDIO_RATE_UINT16));  // samplerate
        float Ts = (1.f / Fs);                          // sample period

        /*------------------------------------*/
        typedef struct
        {
            float amp;
            float freq;
            float phase;
            float out;

        } Lfo_t;

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

        typedef struct
        {
            float   mix;          /* delay blend parameter */
            float   fb;           /* feedback volume */
            float   fw;           /* delay tap mix volume */
            int32_t in_idx;       /* delay write index */
            float   dline[DEPTH]; /* delay buffer */
            float   baseDelay;    /* tap position */
            int8_t  mode;         /* constant or variable delayed feedback ? */

        } monochorus_t;

        Lfo_t        lfoL;
        Lfo_t        lfoR;  // 2 independant LFOs
        monochorus_t delR;
        monochorus_t delL;  // 2 fractional delay lines
        float        rateCoeff;

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

#define MARGIN 6  // minimal delay (in samples)

        /*************************************************************************************************
         *                       chorus/flanger diagram (one channel) :
         *
         *                    ---------[mix >----------------------------
         *                    |                                         |
         *                    |                                         |
         *                    |x1                                       v
         *     xin ------>[+]----->[z^-M]--[interp.]----[fw >--------->[+]-----> yout
         *                 ^         delay line      |
         *                 |                         |
         *                 --< fb]<-------------------
         *
         ************************************************************************************************/

        void Chorus_init() {
            ChorusDelay_init(&delL, LEFT_DELAY, FEEDBACK, FORWARD, MIX);
            ChorusDelay_init(&delR, RIGHT_DELAY, FEEDBACK, FORWARD, MIX);
            lfoL.amp   = LEFT_SWEEP;
            lfoR.amp   = RIGHT_SWEEP;
            lfoL.freq  = LEFT_RATE;
            lfoR.freq  = RIGHT_RATE;
            rateCoeff  = 1;
            lfoL.phase = PI / 2;  // initial phases for quadrature
            lfoR.phase = 0;

            for (int var = 0; var < DEPTH; ++var) {
                delR.dline[var] = 0;
                delL.dline[var] = 0;
            }
        }

        /*-------------------------------------------------------------------------------------------*/
        float Lfo_SampleCompute(Lfo_t *op)  // ! returns a positive value between 0 and op.amp !
        {
            float z;

            op->phase += TWO_PI * Ts * op->freq;  // increment phase
            while (op->phase < 0)                 // keep phase in [0, 2pi]
                op->phase += TWO_PI;
            while (op->phase >= TWO_PI)
                op->phase -= TWO_PI;

            z       = LUTSine::WAVETABLE[lrintf(LUTSine::ALPHA * (op->phase))];
            op->out = op->amp * (z + 1);  // returns a positive value between 0 and op.amp

            return op->out;
        }

        /*---------------------------------------------------------------------------------------------*/
        void ChorusDelay_init(monochorus_t *del, float delay, float dfb, float dfw, float dmix) {
            Delay_set_fb(del, dfb);
            Delay_set_fw(del, dfw);
            Delay_set_mix(del, dmix);
            Delay_set_delay(del, delay);
            del->in_idx = DEPTH - 1;  // Place the input pointer at the end of the buffer
            del->mode   = 1;
        }

        /*---------------------------------------------------------------------------------------------*/
        void Chorus_reset() {
            Chorus_init();
        }
        /*-------------------------------------------------------------------------------------------*/
        void inc_chorusRate(void) {
            lfoL.freq *= 1.05f;
            lfoR.freq *= 1.05f;
        }
        /*-------------------------------------------------------------------------------------------*/
        void dec_chorusRate(void) {
            lfoL.freq *= .95f;
            lfoR.freq *= .95f;
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusRate_set(float val) {
            float rate;
            rate      = MAX_RATE * val + MIN_RATE;
            lfoL.freq = rate;
            // lfoR.freq = 0.98f * rate;
            lfoR.freq = rateCoeff * rate;
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusSecondRate_set(float val) {
            rateCoeff = 0.9f * val + 0.1f;  // from 10 % to 100 % of lfoL rate
            lfoR.freq = rateCoeff * lfoL.freq;
        }
        /*-------------------------------------------------------------------------------------------*/
        void inc_chorusDelay(void) {
            float d;

            d = delL.baseDelay * 1.1f;
            if (d < DEPTH) {
                delL.baseDelay = d;
            }

            d = delR.baseDelay * 1.1f;
            if (d < DEPTH) {
                delR.baseDelay = d;
            }
        }
        /*-------------------------------------------------------------------------------------------*/
        void dec_chorusDelay(void) {
            delL.baseDelay *= .9f;
            delR.baseDelay *= .9f;
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusDelay_set(float val) {
            float d;
            d              = 0.5f * DEPTH * val;
            delL.baseDelay = d;
            delR.baseDelay = d;
        }
        /*-------------------------------------------------------------------------------------------*/
        void inc_chorusFeedback(void) {
            /* increase feedback delay */

            delL.fb *= 1.02f;  //
            delR.fb *= 1.02f;  //
        }
        /*-------------------------------------------------------------------------------------------*/
        void dec_chorusFeedback(void) {
            /* decrease feedback delay */

            delL.fb *= 0.95f;  //
            delR.fb *= 0.95f;  //
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusFeedback_set(float val) {
            float_t fb;
            fb      = 0.95f * val;
            delL.fb = fb;
            delR.fb = fb;
        }
        /*-------------------------------------------------------------------------------------------*/
        void inc_chorusSweep(void) {
            lfoL.amp *= 1.05f;  //
            lfoR.amp *= 1.05f;  //
        }
        /*-------------------------------------------------------------------------------------------*/
        void dec_chorusSweep(void) {
            lfoL.amp *= .95f;  //
            lfoR.amp *= .95f;  //
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusSweep_set(float val) {
            float sw;
            sw       = 0.1f * (DEPTH - MARGIN) * val;
            lfoL.amp = sw;
            lfoR.amp = sw;
        }
        /*-------------------------------------------------------------------------------------------*/
        void ChorusMode_toggle(void) {
            delL.mode *= -1;  //
            delR.mode *= -1;  //
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusMode_switch(uint8_t val) {
            switch (val) {
                case 127:
                    delR.mode = delL.mode = -1;
                    break;
                case 0:
                    delR.mode = delL.mode = 1;
                    break;
            }
        }
        /*-------------------------------------------------------------------------------------------*/
        void ChorusFDBsign_change(void) {
            delL.fb *= -1.f;  //
            delR.fb *= -1.f;  //
        }
        /*---------------------------------------------------------------------------------------------*/
        void ChorusFDBsign_switch(uint8_t val) {
            switch (val) {
                case 127:
                    delR.fb = fabsf(delR.fb);
                    delL.fb = fabsf(delL.fb);
                    break;  // positive feedback;
                case 0:
                    delR.fb = -fabsf(delR.fb);
                    delL.fb = -fabsf(delL.fb);
                    break;  // negative feedback;
            }
        }
        /*-------------------------------------------------------------------------------------------*/
        void Delay_set_delay(monochorus_t *del, float delay) {
            del->baseDelay = delay;
        }
        /*-------------------------------------------------------------------------------------------*/
        void Delay_set_fb(monochorus_t *del, float val) {
            del->fb = val;
        }
        /*-------------------------------------------------------------------------------------------*/
        void Delay_set_fw(monochorus_t *del, float val) {
            del->fw = val;
        }
        /*-------------------------------------------------------------------------------------------*/
        void Delay_set_mix(monochorus_t *del, float val) {
            del->mix = val;
        }
        /*-------------------------------------------------------------------------------------------*/
        float Delay_get_fb(monochorus_t *del) {
            return del->fb;
        }
        /*-------------------------------------------------------------------------------------------*/
        float Delay_get_fw(monochorus_t *del) {
            return del->fw;
        }
        /*-------------------------------------------------------------------------------------------*/
        float Delay_get_mix(monochorus_t *del) {
            return del->mix;
        }
        /*-------------------------------------------------------------------------------------------*/
        void delay_write(monochorus_t *del, float xin) {
            del->dline[del->in_idx] = xin;

            (del->in_idx)++;
            if (del->in_idx >= DEPTH)
                del->in_idx = 0;
        }
        /*-------------------------------------------------------------------------------------------*/
        float delay_read(monochorus_t *del, float delay)  // "delay" is a floating point number of samples
        {
            float   d;      // true requested delay (floating point number of samples)
            float   f;      // fractional part of delay
            int32_t i;      // integer part of delay
            float   y_n;    // y(n)
            float   y_n_1;  // y(n-1)
            float   y_n_2;  // y(n-2)
            float   y_n_3;  // y(n-3)
            int32_t idx;

            d = delay;
            if (d < MARGIN)
                d = MARGIN;  // MARGIN is the minimum allowed delay
            if (d > DEPTH - MARGIN)
                d = DEPTH - MARGIN;

            i = (int32_t)floorf(d);
            f = d - i;

            idx = del->in_idx - i;
            if (idx < 0)
                idx += DEPTH;
            y_n = del->dline[idx];  // y(n)

            idx--;
            if (idx < 0)
                idx += DEPTH;
            y_n_1 = del->dline[idx];  // y(n-1)

            idx--;
            if (idx < 0)
                idx += DEPTH;
            y_n_2 = del->dline[idx];  // y(n-2)

            idx--;
            if (idx < 0)
                idx += DEPTH;
            y_n_3 = del->dline[idx];  // y(n-3)

            // return (y_n_1 - y_n) * f + y_n ; // linear interpolation

            // return (.5f)*(f-1)*(f-2)*y_n - f*(f-2)*y_n_1 + (.5f)*f*(f-1)*y_n_2 ; // 2nd order Lagrange interpolation

            // return .5f*(f-1)*((f-2)*y_n + f*y_n_2) - f*(f-2)*y_n_1 ;    // 2nd order Lagrange interpolation (faster)

            /* 3rd order Lagrange interpolation :  */
            return (f - 2) * (f - 3) * (-0.16666666666f * (f - 1) * y_n + 0.5f * f * y_n_1) + f * (f - 1) * (-0.5f * (f - 3) * y_n_2 + 0.166666666666f * (f - 2) * y_n_3);
        }
        /*---------------------------------------------------------------------------------------------*/

        float mono_chorus_compute(monochorus_t *del, Lfo_t *lfo, float xin) {
            float yout;
            float x1;
            float x2;

            x2 = delay_read(del, del->baseDelay + Lfo_SampleCompute(lfo) + MARGIN);

            if (del->mode == 1)
                x1 = xin + del->fb * x2;  // variable delay feedback signal or ...
            else
                x1 = xin + del->fb * delay_read(del, del->baseDelay + MARGIN);  // fixed delay feedback signal

            x1 = (x1 > 1.0f) ? 1.0f : x1;  // clip too loud samples
            x1 = (x1 < -1.0f) ? -1.0f : x1;

            yout = del->mix * x1 + del->fw * x2;
            // yout = del->mix * xin + del->fw * x2; // not good sounding...
            delay_write(del, x1);

            return yout;
        }

        /*--------------------This is the main stereo chorus function : ----------------------------*/

        void stereoChorus_compute(float *left_out, float *right_out, float in) {
            *left_out  = mono_chorus_compute(&delL, &lfoL, in);
            *right_out = mono_chorus_compute(&delR, &lfoR, in);
        }
    };
}  // namespace klang

#endif /* NodeChorus_hpp */