#ifndef __AMUSE_VOICE_HPP__
#define __AMUSE_VOICE_HPP__

#include <cstdint>
#include <cstdlib>
#include <memory>
#include <list>
#include "SoundMacroState.hpp"
#include "Entity.hpp"
#include "AudioGroupSampleDirectory.hpp"
#include "AudioGroup.hpp"
#include "Envelope.hpp"
#include "Studio.hpp"

namespace amuse
{
class IBackendVoice;
struct Keymap;
struct LayerMapping;

/** State of voice over lifetime */
enum class VoiceState
{
    Playing, /**< SoundMacro actively executing, not in KeyOff */
    KeyOff,  /**< KeyOff event issued, macro beginning fade-out */
    Dead     /**< Default state, causes Engine to remove voice at end of pump cycle */
};

/** Individual source of audio */
class Voice : public Entity
{
    friend class Engine;
    friend class Sequencer;
    friend struct SoundMacroState;
    friend class Envelope;
    friend class Emitter;
    friend struct SoundMacro::CmdScaleVolume;
    friend struct SoundMacro::CmdKeyOff;
    friend struct SoundMacro::CmdScaleVolumeDLS;
    friend struct SoundMacro::CmdReturn;
    friend struct SoundMacro::CmdGoSub;
    friend struct SoundMacro::CmdTrapEvent;
    friend struct SoundMacro::CmdUntrapEvent;
    friend struct SoundMacro::CmdGetMessage;
    friend struct SoundMacro::CmdModeSelect;

    struct VolumeCache
    {
        bool m_curDLS = false;       /**< Current DLS status of cached lookup */
        float m_curVolLUTKey = -1.f; /**< Current input level cached out of LUT */
        float m_curVolLUTVal = 0.f;  /**< Current output level cached out of LUT */
        float getVolume(float vol, bool dls);
        float getCachedVolume() const { return m_curVolLUTVal; }
    };

    void _setObjectId(ObjectId id) { m_objectId = id; }

    int m_vid;                        /**< VoiceID of this voice instance */
    bool m_emitter;                   /**< Voice is part of an Emitter */
    ObjToken<Studio> m_studio;        /**< Studio this voice outputs to */
    IObjToken<Sequencer> m_sequencer; /**< Strong reference to parent sequencer to retain ctrl vals */

    std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
    SoundMacroState m_state;                       /**< State container for SoundMacro playback */
    SoundMacroState::EventTrap m_keyoffTrap;    /**< Trap for keyoff (SoundMacro overrides default envelope behavior) */
    SoundMacroState::EventTrap m_sampleEndTrap; /**< Trap for sampleend (SoundMacro overrides voice removal) */
    SoundMacroState::EventTrap m_messageTrap;   /**< Trap for messages sent from other SoundMacros */
    int32_t m_latestMessage = 0;                     /**< Latest message received on voice */
    std::list<ObjToken<Voice>> m_childVoices;        /**< Child voices for PLAYMACRO usage */
    uint8_t m_keygroup = 0;                          /**< Keygroup voice is a member of */

    ObjToken<SampleEntryData> m_curSample;   /**< Current sample entry playing */
    const unsigned char* m_curSampleData = nullptr; /**< Current sample data playing */
    SampleFormat m_curFormat;                       /**< Current sample format playing */
    uint32_t m_curSamplePos = 0;                    /**< Current sample position */
    uint32_t m_lastSamplePos = 0;                   /**< Last sample position (or last loop sample) */
    int16_t m_prev1 = 0;                            /**< DSPADPCM prev sample */
    int16_t m_prev2 = 0;                            /**< DSPADPCM prev-prev sample */
    double m_dopplerRatio = 1.0;   /**< Current ratio to mix with chromatic pitch for doppler effects */
    double m_sampleRate = NativeSampleRate; /**< Current sample rate computed from relative sample key or SETPITCH */
    double m_voiceTime = 0.0;      /**< Current seconds of voice playback (per-sample resolution) */
    uint64_t m_voiceSamples = 0;   /**< Count of samples processed over voice's lifetime */
    float m_lastLevel = 0.f;       /**< Last computed level ([0,1] mapped to [-10,0] clamped decibels) */
    float m_nextLevel = 0.f;       /**< Next computed level used for lerp-mode amplitude */
    VolumeCache m_nextLevelCache;
    VolumeCache m_lerpedCache;

    VoiceState m_voxState = VoiceState::Dead; /**< Current high-level state of voice */
    bool m_sustained = false;                 /**< Sustain pedal pressed for this voice */
    bool m_sustainKeyOff = false;             /**< Keyoff event occured while sustained */
    uint8_t m_curAftertouch = 0;              /**< Aftertouch value (key pressure when 'bottoming out') */

    float m_targetUserVol = 1.f;    /**< Target user volume of voice (slewed to prevent audible aliasing) */
    float m_curUserVol = 1.f;       /**< Current user volume of voice */
    float m_curVol = 1.f;           /**< Current volume of voice */
    float m_curReverbVol = 0.f;     /**< Current reverb volume of voice */
    float m_curAuxBVol = 0.f;       /**< Current AuxB volume of voice */
    float m_curPan = 0.f;           /**< Current pan of voice */
    float m_curSpan = -1.f;         /**< Current surround pan of voice */
    float m_curPitchWheel = 0.f;    /**< Current normalized wheel value for control */
    int32_t m_pitchWheelUp = 200;   /**< Up range for pitchwheel control in cents */
    int32_t m_pitchWheelDown = 200; /**< Down range for pitchwheel control in cents */
    int32_t m_pitchWheelVal = 0;    /**< Current resolved pitchwheel delta for control */
    int32_t m_curPitch;             /**< Current base pitch in cents */
    bool m_pitchDirty = true;       /**< m_curPitch has been updated and needs sending to voice */
    bool m_needsSlew = false;       /**< next _setTotalPitch will be slewed */
    bool m_dlsVol = false;          /**< Use DLS LUT for resolving voice volume */

    Envelope m_volAdsr;           /**< Volume envelope */
    double m_envelopeTime = -1.f; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
    double m_envelopeDur;         /**< requested duration of last ENVELOPE command */
    float m_envelopeStart;        /**< initial value for last ENVELOPE command */
    float m_envelopeEnd;          /**< final value for last ENVELOPE command */
    const Curve* m_envelopeCurve; /**< curve to use for ENVELOPE command */

    bool m_pitchEnv = false; /**< Pitch envelope activated */
    Envelope m_pitchAdsr;    /**< Pitch envelope for SETPITCHADSR */
    int32_t m_pitchEnvRange; /**< Pitch delta for SETPITCHADSR (in cents) */

    float m_portamentoTime = -1.f; /**< time since last portamento invocation, -1 for no active portamento-glide */
    int32_t m_portamentoTarget;    /**< destination pitch for latest portamento invocation */

    int32_t m_pitchSweep1 = 0;     /**< Current value of PITCHSWEEP1 controller (in cents) */
    int32_t m_pitchSweep2 = 0;     /**< Current value of PITCHSWEEP2 controller (in cents) */
    int16_t m_pitchSweep1Add = 0;   /**< Value to add to PITCHSWEEP1 controller each cycle */
    int16_t m_pitchSweep2Add = 0;   /**< Value to add to PITCHSWEEP2 controller each cycle */
    uint8_t m_pitchSweep1Times = 0; /**< Remaining times to advance PITCHSWEEP1 controller */
    uint8_t m_pitchSweep2Times = 0; /**< Remaining times to advance PITCHSWEEP2 controller */
    uint8_t m_pitchSweep1It = 0;    /**< Current iteration of PITCHSWEEP1 controller */
    uint8_t m_pitchSweep2It = 0;    /**< Current iteration of PITCHSWEEP2 controller */

    float m_panningTime = -1.f; /**< time since last PANNING command, -1 for no active pan-sweep */
    float m_panningDur;         /**< requested duration of last PANNING command */
    uint8_t m_panPos;           /**< initial pan value of last PANNING command */
    int8_t m_panWidth;          /**< delta pan value to target of last PANNING command */

    float m_spanningTime = -1.f; /**< time since last SPANNING command, -1 for no active span-sweep */
    float m_spanningDur;         /**< requested duration of last SPANNING command */
    uint8_t m_spanPos;           /**< initial pan value of last SPANNING command */
    int8_t m_spanWidth;          /**< delta pan value to target of last SPANNING command */

    float m_vibratoTime = -1.f;     /**< time since last VIBRATO command, -1 for no active vibrato */
    int32_t m_vibratoLevel = 0;     /**< scale of vibrato effect (in cents) */
    int32_t m_vibratoModLevel = 0;  /**< scale of vibrato mod-wheel influence (in cents) */
    float m_vibratoPeriod = 0.f;    /**< vibrato wave period-time, 0.f will disable vibrato */
    bool m_vibratoModWheel = false; /**< vibrato scaled with mod-wheel if set */

    float m_tremoloScale = 0.f;    /**< minimum volume factor produced via LFO */
    float m_tremoloModScale = 0.f; /**< minimum volume factor produced via LFO, scaled via mod wheel */

    float m_lfoPeriods[2] = {};               /**< time-periods for LFO1 and LFO2 */
    std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */
    int8_t* m_extCtrlVals = nullptr;          /**< MIDI Controller values (external storage) */

    uint16_t m_rpn = 0; /**< Current RPN (only pitch-range 0x0000 supported) */

    void _destroy();
    bool _checkSamplePos(bool& looped);
    void _doKeyOff();
    void _macroKeyOff();
    void _macroSampleEnd();
    void _procSamplePre(int16_t& samp);
    VolumeCache m_masterCache;
    template <typename T>
    T _procSampleMaster(double time, T samp);
    VolumeCache m_auxACache;
    template <typename T>
    T _procSampleAuxA(double time, T samp);
    VolumeCache m_auxBCache;
    template <typename T>
    T _procSampleAuxB(double time, T samp);
    void _setTotalPitch(int32_t cents, bool slew);
    bool _isRecursivelyDead();
    void _bringOutYourDead();
    static uint32_t _GetBlockSampleCount(SampleFormat fmt);
    ObjToken<Voice> _findVoice(int vid, ObjToken<Voice> thisPtr);
    std::unique_ptr<int8_t[]>& _ensureCtrlVals();

    std::list<ObjToken<Voice>>::iterator _allocateVoice(double sampleRate, bool dynamicPitch);
    std::list<ObjToken<Voice>>::iterator _destroyVoice(std::list<ObjToken<Voice>>::iterator it);

    bool _loadSoundMacro(SoundMacroId id, const SoundMacro* macroData, int macroStep, double ticksPerSec,
                         uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
    bool _loadKeymap(const Keymap* keymap, double ticksPerSec, uint8_t midiKey,
                     uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
    bool _loadLayer(const std::vector<LayerMapping>& layer, double ticksPerSec,
                    uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
    ObjToken<Voice> _startChildMacro(ObjectId macroId, int macroStep, double ticksPerSec, uint8_t midiKey,
                                     uint8_t midiVel, uint8_t midiMod, bool pushPc = false);

    void _panLaw(float coefsOut[8], float frontPan, float backPan, float totalSpan) const;
    void _setPan(float pan);
    void _setSurroundPan(float span);
    void _setChannelCoefs(const float coefs[8]);
    void _setPitchWheel(float pitchWheel);
    void _notifyCtrlChange(uint8_t ctrl, int8_t val);

public:
    ~Voice();
    Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, ObjToken<Studio> studio);
    Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter,
          ObjToken<Studio> studio);

    /** Called before each supplyAudio invocation to prepare voice
     *  backend for possible parameter updates */
    void preSupplyAudio(double dt);

    /** Request specified count of audio frames (samples) from voice,
     *  internally advancing the voice stream */
    size_t supplyAudio(size_t frames, int16_t* data);

    /** Called three times after resampling supplyAudio output, voice should
     *  perform volume processing / send routing for each aux bus and master */
    void routeAudio(size_t frames, double dt, int busId, int16_t* in, int16_t* out);
    void routeAudio(size_t frames, double dt, int busId, int32_t* in, int32_t* out);
    void routeAudio(size_t frames, double dt, int busId, float* in, float* out);

    /** Obtain pointer to Voice's Studio */
    ObjToken<Studio> getStudio() { return m_studio; }

    /** Get current state of voice */
    VoiceState state() const { return m_voxState; }

    /** Get VoiceId of this voice (unique to all currently-playing voices) */
    int vid() const { return m_vid; }

    /** Get max VoiceId of this voice and any contained children */
    int maxVid() const;

    /** Allocate parallel macro and tie to voice for possible emitter influence */
    ObjToken<Voice> startChildMacro(int8_t addNote, ObjectId macroId, int macroStep);

    /** Load specified SoundMacro Object from within group into voice */
    bool loadMacroObject(SoundMacroId macroId, int macroStep, double ticksPerSec, uint8_t midiKey, uint8_t midiVel,
                         uint8_t midiMod, bool pushPc = false);

    /** Load specified SoundMacro Object into voice */
    bool loadMacroObject(const SoundMacro* macro, int macroStep, double ticksPerSec, uint8_t midiKey, uint8_t midiVel,
                         uint8_t midiMod, bool pushPc = false);

    /** Load specified song page object (Keymap/Layer) from within group into voice */
    bool loadPageObject(ObjectId objectId, double ticksPerSec, uint8_t midiKey, uint8_t midiVel, uint8_t midiMod);

    /** Signals voice to begin fade-out (or defer if sustained), eventually reaching silence */
    void keyOff();

    /** Sends numeric message to voice and all siblings */
    void message(int32_t val);

    /** Start playing specified sample from within group, optionally by sample offset */
    void startSample(SampleId sampId, int32_t offset);

    /** Stop playing current sample */
    void stopSample();

    /** Set current voice volume immediately */
    void setVolume(float vol);

    /** Set current voice panning immediately */
    void setPan(float pan);

    /** Set current voice surround-panning immediately */
    void setSurroundPan(float span);

    /** Set current voice channel coefficients immediately */
    void setChannelCoefs(const float coefs[8]);

    /** Start volume envelope to specified level */
    void startEnvelope(double dur, float vol, const Curve* envCurve);

    /** Start volume envelope from zero to current level */
    void startFadeIn(double dur, float vol, const Curve* envCurve);

    /** Start pan envelope to specified position */
    void startPanning(double dur, uint8_t panPos, int8_t panWidth);

    /** Start span envelope to specified position */
    void startSpanning(double dur, uint8_t spanPos, int8_t spanWidth);

    /** Set voice relative-pitch in cents */
    void setPitchKey(int32_t cents);

    /** Set sustain status within voice; clearing will trigger a deferred keyoff */
    void setPedal(bool pedal);

    /** Set doppler factor for voice */
    void setDoppler(float doppler);

    /** Set vibrato parameters for voice */
    void setVibrato(int32_t level, bool modScale, float period);

    /** Configure modwheel influence range over vibrato */
    void setMod2VibratoRange(int32_t modLevel);

    /** Setup tremolo parameters for voice */
    void setTremolo(float tremoloScale, float tremoloModScale);

    /** Setup LFO1 for voice */
    void setLFO1Period(float period) { m_lfoPeriods[0] = period; }

    /** Setup LFO2 for voice */
    void setLFO2Period(float period) { m_lfoPeriods[1] = period; }

    /** Setup pitch sweep controller 1 */
    void setPitchSweep1(uint8_t times, int16_t add);

    /** Setup pitch sweep controller 2 */
    void setPitchSweep2(uint8_t times, int16_t add);

    /** Set reverb mix for voice */
    void setReverbVol(float rvol);

    /** Set AuxB volume for voice */
    void setAuxBVol(float bvol);

    /** Set envelope for voice */
    void setAdsr(ObjectId adsrId, bool dls);

    /** Set pitch in absolute hertz */
    void setPitchFrequency(uint32_t hz, uint16_t fine);

    /** Set pitch envelope */
    void setPitchAdsr(ObjectId adsrId, int32_t cents);

    /** Set pitchwheel value for use with controller */
    void setPitchWheel(float pitchWheel);

    /** Set effective pitch range via the pitchwheel controller */
    void setPitchWheelRange(int8_t up, int8_t down);

    /** Set aftertouch */
    void setAftertouch(uint8_t aftertouch);

    /** Assign voice to keygroup for coordinated mass-silencing */
    void setKeygroup(uint8_t kg) { m_keygroup = kg; }

    /** Get note played on voice */
    uint8_t getLastNote() const { return m_state.m_initKey; }

    /** Do portamento glide; returns `false` if portamento disabled */
    bool doPortamento(uint8_t newNote);

    /** Get MIDI Controller value on voice */
    int8_t getCtrlValue(uint8_t ctrl) const
    {
        if (!m_extCtrlVals)
        {
            if (m_ctrlValsSelf)
                return m_ctrlValsSelf[ctrl];
            return 0;
        }
        return m_extCtrlVals[ctrl];
    }

    /** Set MIDI Controller value on voice */
    void setCtrlValue(uint8_t ctrl, int8_t val)
    {
        if (!m_extCtrlVals)
        {
            std::unique_ptr<int8_t[]>& vals = _ensureCtrlVals();
            vals[ctrl] = val;
        }
        else
            m_extCtrlVals[ctrl] = val;
        _notifyCtrlChange(ctrl, val);
    }

    /** 'install' external MIDI controller storage */
    void installCtrlValues(int8_t* cvs)
    {
        m_ctrlValsSelf.reset();
        m_extCtrlVals = cvs;
        for (ObjToken<Voice>& vox : m_childVoices)
            vox->installCtrlValues(cvs);
    }

    /** Get MIDI pitch wheel value on voice */
    float getPitchWheel() const { return m_curPitchWheel; }

    /** Get MIDI aftertouch value on voice */
    int8_t getAftertouch() const { return m_curAftertouch; }

    /** Get count of all voices in hierarchy, including this one */
    size_t getTotalVoices() const;

    /** Get latest decoded sample index */
    uint32_t getSamplePos() const { return m_curSamplePos; }

    /** Recursively mark voice as dead for Engine to deallocate on next cycle */
    void kill();
};
}

#endif // __AMUSE_VOICE_HPP__
