#pragma once

#include <memory>
#include <list>
#include <vector>
#include <deque>
#include <unordered_map>
#include <atomic>

#include <QIODevice>
#include <QAudioOutput>

class QThread;
namespace Xybrid::Data {
    class Project;
    class Node;
}
namespace Xybrid::Audio {
    template <typename T> struct PointerCompare {
        bool operator()(T* a, T* b) const { return *a == *b; }
        size_t operator()(T* a) const { return std::hash<T>()(*a); }
    };
    struct NoteInfo {
        bool valid = false;
        uint8_t port = 0;
        uint16_t noteId = 0;
        NoteInfo() = default;
        NoteInfo(uint8_t p, uint16_t nId) { valid = true; port = p; noteId = nId; }
    };
    class AudioEngine : public QIODevice {
        Q_OBJECT
        explicit AudioEngine(QObject *parent = nullptr);
    public:
        enum PlaybackMode {
            Stopped, // stopped
            Playing, // playing track
            Paused, // paused during playback
            Previewing, // instrument live preview
            Rendering, // rendering to file
        };
    private:
        QThread* thread;
        std::unique_ptr<QAudioOutput> output;
        int sampleRate = 48000;

        std::vector<float> buffer[2];
        size_t bufPos = 0;

        static const constexpr size_t tickBufSize = (1024*1024*5); // 5mb should be enough
        std::unique_ptr<size_t[]> tickBuf;
        std::atomic<size_t*> tickBufPtr;
        size_t* tickBufEnd;

        PlaybackMode mode = Stopped;
        size_t tickId = 0;
        std::shared_ptr<Data::Project> project;

        std::deque<std::shared_ptr<Data::Node>> queue;
        bool queueValid;
        void buildQueue();

        std::array<uint16_t, 256> portLastNoteId;
        std::vector<NoteInfo> chTrack;
        std::vector<NoteInfo> noteEndQueue;
        std::unordered_map<std::string*, NoteInfo, PointerCompare<std::string>, PointerCompare<std::string>> nameTrack;
        std::vector<uint8_t> buf; /// preallocated buffer for building commands

        // playback timing and position
        float tempo = 140.0;
        int seqPos;
        int curRow;
        int curTick;
        double tickAcc; // accumulator for tick remainder

        void postInit();
        void initAudio(bool startNow = false);
        void deinitAudio();
        void nextTick();
    public:
        static void init();
        inline constexpr PlaybackMode playbackMode() const { return mode; }
        inline constexpr const std::shared_ptr<Data::Project>& playingProject() const { return project; }
        void play(std::shared_ptr<Data::Project>);
        void stop();

        inline void invalidateQueue(Data::Project* p) { if (p == project.get()) queueValid = false; }

        void* tickAlloc(size_t size);
        inline size_t curTickId() const { return tickId; }
        inline size_t curTickSize() const { return buffer[0].size(); }
        inline int curSampleRate() const { return sampleRate; }

        // QIODevice functions
        qint64 readData(char* data, qint64 maxlen) override;
        qint64 writeData(const char*, qint64) override { return 0; }
        qint64 bytesAvailable() const override { return 0; } // not actually used by QAudioOutput

        volatile int note = 12*5;

    signals:
        void playbackModeChanged();

    public slots:
    };

    extern AudioEngine* audioEngine;
}
