/*
 * 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_SAMPLER        ]
 *       +---------------------+
 *       |                     |
 *       |              SIGNAL |--OUT00
 *       |                     |
 *       +---------------------+
 */

// @todo(implement input signal for speed)

#ifndef NodeSampler_hpp
#define NodeSampler_hpp

#include "KlangMath.hpp"
#include "KlangNode.hpp"

namespace klang {
    template <class BUFFER_TYPE>
    class NodeSamplerT : public Node {
    public:
        static const CHANNEL_ID NUM_CH_IN  = 0;
        static const CHANNEL_ID NUM_CH_OUT = 1;

        NodeSamplerT() : NodeSamplerT(nullptr, 0) {}

        NodeSamplerT(const BUFFER_TYPE* pBuffer, uint32_t pLength) {
            fLoop          = true;
            fSpeed         = 1.0;
            fBuffer        = pBuffer;
            fLength        = pLength;
            fBufferPointer = fLength;
            fIsPlaying     = false;
            fIn            = 0.0;
            fOut           = 1.0;
            fAmplification = 1.0;
        }

        bool connect(Connection* pConnection, CHANNEL_ID pInChannel) { return false; }

        bool disconnect(CHANNEL_ID pInChannel) { return false; }

        void update(CHANNEL_ID pChannel, float* pAudioBlock) {
            if (pChannel == CH_OUT_SIGNAL) {
                for (uint16_t i = 0; i < KLANG_SAMPLES_PER_AUDIO_BLOCK; i++) {
                    pAudioBlock[i] = next_sample();
                }
            }
        }

        void start() {
            fBufferPointer = 0;
            fIsPlaying     = true;
        }

        void set_position(uint32_t pCounter) {
            fBufferPointer = pCounter;
        }

        uint32_t get_position() {
            return fBufferPointer;
        }

        void set_buffer(const BUFFER_TYPE* pBuffer) {
            fBuffer = pBuffer;
        }

        const BUFFER_TYPE* get_buffer() {
            return fBuffer;
        }

        void set_speed(float pSpeed) {
            fSpeed = pSpeed;
        }

        float get_speed() {
            return fSpeed;
        }

        void set_amplification(float pAmplification) {
            fAmplification = pAmplification;
        }

        float get_amplification() {
            return fAmplification;
        }

        void set_buffer_size(uint32_t pLength) {
            fLength        = pLength;
            fBufferPointer = fLength;
        }

        uint32_t get_buffer_size() {
            return fLength;
        }

        void set_in(float pIn) {
            fIn = KlangMath::clamp(pIn, 0.0, fOut);
        }

        float get_in() {
            return fIn;
        }

        void set_out(float pOut) {
            fOut = KlangMath::clamp(pOut, fIn, 1.0);
        }

        float get_out() {
            return fOut;
        }

        void loop(bool pLoop) {
            fLoop = pLoop;
        }

        bool is_playing() {
            return fIsPlaying;
        }

        void set_command(KLANG_CMD_TYPE pCommand, KLANG_CMD_TYPE* pPayLoad) {
            switch (pCommand) {
                case KLANG_SET_SPEED_F32:
                    set_speed(KlangMath::FLOAT_32(pPayLoad));
                    break;
            }
        }

        inline float next_sample() {
            if (fBuffer != nullptr) {
                /* quit early if buffer has no elements or loop ended */
                if (fLength == 0) {
                    return 0.0;
                }
                // if (!fLoop && (fBufferPointer >= fLength || fBufferPointer < 0)) {
                //     return 0.0;
                // }
                if (!fIsPlaying) {
                    return 0.0;
                }

                /* move forward in buffer */
                fBufferPointer += fSpeed;

                if (fIn == 0.0 && fOut == 1.0) {
                    /* check if done */
                    if (!fLoop && (fBufferPointer >= fLength || fBufferPointer < 0)) {
                        fIsPlaying = false;
                        return 0.0;
                    }

                    /* wrap around -- trying maintaing fraction */
                    while (fBufferPointer >= fLength) {
                        fBufferPointer -= fLength;
                    }
                    while (fBufferPointer < 0) {
                        fBufferPointer += fLength;
                    }
                } else {
                    const uint32_t mIn     = fLength * fIn;
                    const uint32_t mOut    = fLength * fOut;
                    const uint32_t mLength = mOut - mIn;
                    if (mLength == 0) {
                        return 0.0;
                    }

                    /* check if done */
                    if (!fLoop && (fBufferPointer >= mOut || fBufferPointer < mIn)) {
                        fIsPlaying = false;
                        return 0.0;
                    }

                    /* wrap around -- trying maintaing fraction */
                    while (fBufferPointer >= mOut) {
                        fBufferPointer -= mLength;
                    }
                    while (fBufferPointer < mIn) {
                        fBufferPointer += mLength;
                    }
                }

                /* retreive sample */
                // @TODO(consider interpolation between samples … see wavetable)
                uint32_t          mIndex  = (uint32_t)fBufferPointer;
                const BUFFER_TYPE mSample = fBuffer[mIndex];
                return convert_sample(mSample) * fAmplification;
            } else {
                return 0.0;
            }
        }

    private:
        bool               fLoop;
        const BUFFER_TYPE* fBuffer;
        uint32_t           fLength;
        float              fBufferPointer;
        float              fSpeed;
        float              fAmplification;
        bool               fIsPlaying;
        float              fIn;
        float              fOut;

        inline float convert_sample(const BUFFER_TYPE pRawSample) {
            return pRawSample;
        }
    };

    template <>
    float klang::NodeSamplerT<uint8_t>::convert_sample(const uint8_t pRawSample) {
        const static float mScale = 1.0 / ((1 << 8) - 1);
        const float        mRange = pRawSample * mScale;
        return mRange * 2.0 - 1.0;
    }

    template <>
    float klang::NodeSamplerT<int8_t>::convert_sample(const int8_t pRawSample) {
        const static float mScale  = 1.0 / ((1 << 8) - 1);
        const float        mOffset = pRawSample + (1 << 7);
        const float        mRange  = mOffset * mScale;
        return mRange * 2.0 - 1.0;
    }

    template <>
    float klang::NodeSamplerT<uint16_t>::convert_sample(const uint16_t pRawSample) {
        const static float mScale = 1.0 / ((1 << 16) - 1);
        const float        mRange = pRawSample * mScale;
        return mRange * 2.0 - 1.0;
        // @note(below: less precise but faster)
        // const float s      = pRawSample;
        // static const float mScale = 1.0 / (1 << 15);
        // const float a      = s * mScale - 1.0;
        // return a;
    }

    template <>
    float klang::NodeSamplerT<int16_t>::convert_sample(const int16_t pRawSample) {
        const static float mScale  = 1.0 / ((1 << 16) - 1);
        const float        mOffset = pRawSample + (1 << 15);
        const float        mRange  = mOffset * mScale;
        return mRange * 2.0 - 1.0;
    }

    using NodeSamplerUI8  = NodeSamplerT<uint8_t>;
    using NodeSamplerI8   = NodeSamplerT<int8_t>;
    using NodeSamplerUI16 = NodeSamplerT<uint16_t>;
    using NodeSamplerI16  = NodeSamplerT<int16_t>;
    using NodeSamplerF32  = NodeSamplerT<float>;
    using NodeSampler     = NodeSamplerT<float>;
}  // namespace klang

#endif /* NodeSampler_hpp */