/*
 * 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/>.
 */

// @TODO(move to strom)

#ifndef StepSequencer_h
#define StepSequencer_h

#include <stdint.h>

#define USE_ASSERT 0

namespace klang {
    template <typename T>
    class StepSequencer {
        typedef uint16_t I_TYPE;

    public:
        StepSequencer(int pLength = 16) : mLength(pLength), mData(new T[pLength]) {}

        ~StepSequencer() {
            delete[] mData;
        }

        I_TYPE length() const {
            return mLength;
        }

        const T& operator[](I_TYPE i) const {
            return mData[check(i)];
        }

        T& operator[](I_TYPE i) {
            return mData[check(i)];
        }

        StepSequencer(const StepSequencer<T>&);
        StepSequencer(StepSequencer<T>&&);
        StepSequencer<T>& operator=(const StepSequencer<T>&);
        StepSequencer<T>& operator=(StepSequencer<T>&&);

        T next() {
            mStep++;
            while (mStep >= mLength) {
                mStep -= mLength;
            }
            return mData[mStep];
        }

        I_TYPE get_current_step() {
            return mStep;
        }

        T* sequence() {
            return mData;
        }

        void set(const T* pData) {
            for (I_TYPE i = 0; i < length(); i++) {
                mData[i] = pData[i];
            }
        }

        void shift_left() {
            const I_TYPE mLastIndex = length() - 1;
            const T      mFirst     = mData[0];
            for (I_TYPE i = 0; i < mLastIndex; i++) {
                mData[i] = mData[i + 1];
            }
            mData[mLastIndex] = mFirst;
        }

        void shift_right() {
            const I_TYPE mLastIndex = length() - 1;
            const T      mLast      = mData[mLastIndex];
            for (I_TYPE i = mLastIndex; i > 0; i--) {
                mData[i] = mData[i - 1];
            }
            mData[0] = mLast;
        }

        void shift(int pShiftSteps) {
            if (pShiftSteps > 0) {
                for (int i = 0; i < pShiftSteps; i++) {
                    shift_right();
                }
            } else if (pShiftSteps < 0) {
                const int mShiftStepsInv = pShiftSteps * -1;
                for (int i = 0; i < mShiftStepsInv; i++) {
                    shift_left();
                }
            }
        }

        void backwards() {
            T* mDataCopy(new T[mLength]);
            for (I_TYPE i = 0; i < length(); i++) {
                mDataCopy[i] = mData[i];
            }
            const I_TYPE mLastIndex = length() - 1;
            for (I_TYPE i = 0; i < length(); i++) {
                mData[i] = mDataCopy[mLastIndex - i];
            }
            delete[] mDataCopy;
        }

        void print(int pWrap = 4) {
            // TODO integrate into debugging output system
            // ... aka do not use `printf` directly
            for (int i = 0; i < length(); i++) {
                printf("%i", sequence()[i]);
                if ((i % pWrap) == (pWrap - 1)) {
                    printf("n");
                } else {
                    printf(",");
                }
            }
            printf("n");
        }

    private:
        I_TYPE mLength;
        T*     mData;

        I_TYPE mStep = -1;

        I_TYPE check(I_TYPE i) const {
#if (USE_ASSERT)
            assert(i >= 0 && i < mLength);
#endif
            return i;
        }
    };
}  // namespace klang

#endif /* StepSequencer_h */