/*
  ZynAddSubFX - a software synthesizer

  NotePool.h - Pool of Synthesizer Engines And Note Instances
  Copyright (C) 2016 Mark McCurry

  This program 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; either version 2
  of the License, or (at your option) any later version.
*/
#pragma once
#include <stdint.h>
#include <functional>
#include "../globals.h"

//Expected upper bound of synths given that max polyphony is hit
#define EXPECTED_USAGE 3

namespace zyn {

typedef uint8_t note_t; //Global MIDI note definition

struct LegatoParams;
class PortamentoRealtime;
class NotePool
{
    public:
        //Currently this wastes a ton of bits due to the legatoMirror flag
        struct NoteDescriptor {
            //acceptable overlap after 2 minutes
            //run time at 48kHz 8 samples per buffer
            //19 bit minimum
            uint32_t age;
            note_t note;
            uint8_t sendto;
            //max of 16 kit elms and 3 kit items per
            uint8_t size;
            uint8_t status;
            bool    legatoMirror;
            PortamentoRealtime *portamentoRealtime;
            bool operator==(NoteDescriptor);

            //status checks
            bool playing(void) const;
            bool off(void) const;
            bool sustained(void) const;
            bool released(void) const;
            bool entombed(void) const;
            bool dying(void) const;
            bool latched(void) const;

            //status transitions
            void setStatus(uint8_t s);
            void doSustain(void);

            bool canSustain(void) const;
            void makeUnsustainable(void);
        };

        //To be pedantic this wastes 2 or 6 bytes per descriptor
        //depending on 32bit/64bit alignment rules
        struct SynthDescriptor {
            SynthNote *note;
            uint8_t type;
            uint8_t kit;
        };


        //Pool of notes
        NoteDescriptor   ndesc[POLYPHONY];
        SynthDescriptor  sdesc[POLYPHONY*EXPECTED_USAGE];
        bool             needs_cleaning;


        //Iterators
        struct activeNotesIter {
            SynthDescriptor *begin() {return _b;};
            SynthDescriptor *end() {return _e;};
            SynthDescriptor *_b;
            SynthDescriptor *_e;
        };

        struct activeDescIter {
            activeDescIter(NotePool &_np):np(_np)
            {
                int off=0;
                for(int i=0; i<POLYPHONY; ++i, ++off)
                    if(np.ndesc[i].status == 0)
                        break;
                _end = np.ndesc+off;
            }
            NoteDescriptor *begin() {return np.ndesc;};
            NoteDescriptor *end() { return _end; };
            NoteDescriptor *_end;
            NotePool &np;
        };

        struct constActiveDescIter {
            constActiveDescIter(const NotePool &_np):np(_np)
            {
                int off=0;
                for(int i=0; i<POLYPHONY; ++i, ++off)
                    if(np.ndesc[i].status == 0)
                        break;
                _end = np.ndesc+off;
            }
            const NoteDescriptor *begin() const {return np.ndesc;};
            const NoteDescriptor *end() const { return _end; };
            const NoteDescriptor *_end;
            const NotePool &np;
        };

        activeNotesIter activeNotes(NoteDescriptor &n);

        activeDescIter activeDesc(void);
        constActiveDescIter activeDesc(void) const;

        //Counts of descriptors used for tests
        int usedNoteDesc(void) const;
        int usedSynthDesc(void) const;

        NotePool(void);

        //Operations
        void insertNote(note_t note, uint8_t sendto, SynthDescriptor desc,
                        PortamentoRealtime *portamento_realtime=NULL, bool legato=false);
        void insertLegatoNote(NoteDescriptor desc, SynthDescriptor sdesc);

        void upgradeToLegato(void);
        void applyLegato(note_t note, const LegatoParams &par, PortamentoRealtime *portamento_realtime=NULL);

        void makeUnsustainable(note_t note);
        
        void releaseLatched();

        bool full(void) const;
        bool synthFull(int sdesc_count) const;

        //Note that isn't KEY_PLAYING or KEY_RELEASED_AND_SUSTAINING
        bool existsRunningNote(void) const;
        int getRunningNotes(void) const;
        void enforceKeyLimit(int limit);
        int getRunningVoices(void) const;
        void enforceVoiceLimit(int limit, int preferred_note);
        void limitVoice(int preferred_note);

        void releasePlayingNotes(void);
        void releaseNote(note_t note);
        void release(NoteDescriptor &d);
        void latch(NoteDescriptor &d);
        

        void killAllNotes(void);
        void killNote(note_t note);
        void kill(NoteDescriptor &d);
        void kill(SynthDescriptor &s);
        void entomb(NoteDescriptor &d);

        void cleanup(void);

        void dump(void);
};

}
