#pragma once

#include <memory>
#include <functional>
#include <unordered_map>
#ifdef WITH_BOOST
#include <boost/container/pmr/memory_resource.hpp>
#include <boost/container/pmr/polymorphic_allocator.hpp>
using boost::container::pmr::polymorphic_allocator;
template <class Key,
             class T,
             class Hash = std::hash<Key>,
             class Pred = std::equal_to<Key>>
   using unordered_map = std::unordered_map<Key, T, Hash, Pred, polymorphic_allocator<std::pair<const Key,T>>>;
template <class Key, class T,
          class Hash = std::hash<Key>,
          class Pred = std::equal_to<Key>>
using unordered_multimap = std::unordered_multimap<Key, T, Hash, Pred, polymorphic_allocator<std::pair<const Key,T>>>;
#else
    using std::pmr::unordered_map;
    using std::pmr::unordered_multimap;
#endif
#include <array>
#include "nodelib/basics.h"
#include "data/node.h"
#include "util/mem.h"

namespace Xybrid::Data {
    class CommandPort;
    class AudioPort;
}

namespace Xybrid::NodeLib {
    class ParamReader;
    /*!
     * \class InstrumentCore
     *
     * Helper class to form the core of an instrument node.
     *
     * Not mandatory by any means, but handles all the "standard" commands for you.
     */
    class InstrumentCore {
    public:
        class Note {
            friend class InstrumentCore;
            InstrumentCore* core = nullptr;
            void* intern = nullptr;
        public:
            uint16_t id;
            double note = 64.0; // floating point to allow smooth pitch bends
            double noteAdd = 0.0;
            double time = 0.0;

            double volume = 1.0;
            double pan = 0.0;

            ADSR adsr;

            double adsrTime = 0;
            uint8_t adsrPhase = 0;

            union {
                std::array<double, 5> scratch {0.0};
                std::array<void*, 5> ptr;
            };

            template<typename T> inline T& scratchAs() { return *(reinterpret_cast<T*>(reinterpret_cast<void*>(&scratch))); }

            Note() = default;
            Note(InstrumentCore*, uint16_t id);

            double effectiveNote() const;
            double ampMult() const;
        };
        friend class Note;

        class Tween {
            friend class InstrumentCore;
        public:
            uint16_t noteId;

            int16_t ticksLeft = -1;

            uint8_t flags = 0;

            double timeStart = 0.0;
            double timeEnd = 0.0;
            double valStart = 0.0;
            double valEnd = 0.0;

            double* op;

            Tween(Note&, double*, double val, double time, int16_t ticks = -1);

            void startTick(Note&, double tickTime);
            void process(Note&, double smpTime);
        };
    private:
        //

        double time;
        double smpTime;

    public:

        double volume = 1.0;
        double pan = 0.0;

        unordered_map<uint16_t, Note> activeNotes = {16, Util::ralloc};
        unordered_multimap<uint16_t, Tween> activeTweens = {16, Util::ralloc};

        std::function<bool(Note*, const ParamReader&)> paramFilter;
        std::unordered_map<uint8_t, std::function<bool(const ParamReader&)>> globalParam;
        std::unordered_map<uint8_t, std::function<bool(Note&, const ParamReader&)>> localParam;

        std::function<void(Note&, Data::AudioPort*)> processNote;
        std::function<void(Note&)> onNoteOn;
        std::function<void(Note&)> onNoteLegato;
        std::function<void(Note&, bool)> onNoteOff;
        std::function<void(Note&)> onDeleteNote;

        InstrumentCore() = default;
        ~InstrumentCore();

        inline double globalTime() const { return time; }
        inline double sampleTime() const { return smpTime; }

        void release();
        void reset();
        void process(Data::Node*);
        void process(Data::CommandPort*, Data::AudioPort* = nullptr);
        void advanceNote(Note&);
        void deleteNote(Note&);

        /// Removes tween matching the specified note and target.
        void removeTween(Note&, double*);
        /// Removes all tweens matching the specified note.
        void removeTweens(Note&);
        Tween* findTween(Note&, double*);
        Tween& startTween(Note&, double*, double val, double time, int16_t ticks = -1);

    };
}
