//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#ifndef cwMidiState_h
#define cwMidiState_h

namespace cw
{
  namespace midi_state
  {
    // This object receives MIDI note and pedal (sustain,sostenuto,soft) messages
    // and generates voice and pedal control information.
    // Primarily it maintains the state of the voice associated with each
    // MIDI channel and pitch and notifies the client of the changes to
    // that state as various MIDI messages arrive.
    // The primary features are:
    //
    // 1. Maintains note gate and pedal gate information to determine
    // when a note is sounding. For example a note gate may be off
    // but the voice may still be sounding due to the state of the
    // sustain or sostenuto pedal.
    //
    // 2. Handles reporting 'half' pedal state on the sustain pedal.
    //
    // 3. Reports 're-attacks' when a note gate is on and receives
    // another note-on messages.
    //
    // 4. Report 'no-change' state when a MIDI message is received
    // but does not change the state of the voice or pedal.
    // These messages often indicate a problem with the MIDI stream
    // format.

    // TODO:
    // 1. automatically decrease note-on velocity when soft pedal is down
    //
    // 2. handle legato and portmento controls
    //
    // 3. send a 'decay rate' based on the state of the pedals
    //    when when the note gate goes off (i.e. if the note is being
    //    held decrease the decay rate.
    //
    // 4. automatically send note-off / snd-off to all voices that
    //    are sounding on 'reset()'.
    //
    // 5. Allow sending arbitrary time markes (e.g. bars and sections)
    //    though this mechanism so that they can be ordered with the
    //    MIDI events
    
    typedef handle<struct midi_state_str> handle_t;

    enum {
      kNoteOnFl        = 0x0001,  // note-on
      kNoteOffFl       = 0x0002,  // note-off
      kSoundOnFl       = 0x0004,  // note-on
      kSoundOffFl      = 0x0008,  // note-off,sustain up,sostenuto up
      kReattackFl      = 0x0010,  // note-on (when note gate is already on)
      kSoftPedalFl     = 0x0020,  // soft pedal up/down (also accompanies note-on msg's when the pedal is down)
      kUpPedalFl       = 0x0040,  // soft,sustain,sost pedal up
      kHalfPedalFl     = 0x0080,  // sustain pedal entered half pedal range (also sent to all sounding notes)
      kDownPedalFl     = 0x0100,  // soft,sustain,sost pedal down
      kNoChangeFl      = 0x0200,  // a midi-msg arrived but did not cause a change in note or pedal state
      kPedalEvtFl      = 0x0400,  // This is a pedal event
      kNoteEvtFl       = 0x0800,  // This is a note event
      kMarkerEvtFl     = 0x1000   // This is a marker event (bar or section)
    };

    typedef struct midi_msg_str
    {
      unsigned uid;     // midi event unique id
      uint8_t  ch;      // 0-15
      uint8_t  status;  // status w/o channel
      uint8_t  d0;      //
      uint8_t  d1;      // 
    } midi_msg_t;

    typedef struct marker_msg_str
    {
      unsigned uid;
      unsigned typeId; // marker type id
      unsigned value;  // marker value
      uint8_t  ch;
      uint8_t  pad[3];
    } marker_msg_t;

    enum {
      kMidiMsgTId,
      kMarkerMsgTId
    };
    
    typedef struct msg_str
    {
      unsigned typeId;
      union {
        midi_msg_t   midi;
        marker_msg_t marker;
      } u;
    } msg_t;

    // Cached event record 
    typedef struct event_str
    {
      unsigned            flags;  // see flags above
      double              secs;   // time of event
      const msg_t*        msg;    // accompanying midi info
      struct event_str*   link;   // null terminated list of following events
      struct event_str*  tlink;   // time order link
    } event_t;

    typedef struct config_str
    {
      bool       cacheEnableFl;          // enable/disable event caching  
      unsigned   cacheBlockMsgN;         // count of cache messages to pre-allocate in each block
      unsigned   pedalHalfMinMidiValue;  // sustain half pedal lower value
      unsigned   pedalHalfMaxMidiValue;  // sustain pedal half pedal upper value       
      unsigned   pedalUpMidiValue;       // soft and sostenuto pedal down min value
    } config_t;

    const char* flag_to_label( unsigned flag );


    // Returned string must be released via mem::release()
    unsigned    flags_to_string_max_string_length();
    rc_t        flags_to_string( unsigned flags, char* str, unsigned strCharCnt );

    rc_t        format_event( const event_t* e, char* buf, unsigned bufCharN );

    const config_t& default_config();

    // Note that if the cache is not enabled then 'msg' is only valid during the callback and
    // therefore should not be stored.  If the cache is enabled then 'msg' is valid until
    // the next call to 'reset()' or until the midi_state_t instance is destroyed.
    typedef void (*callback_t)( void* arg, unsigned flags, double secs,  const msg_t* msg );
    
    rc_t create( handle_t&       hRef,
                 callback_t      cbFunc, // set to nullptr to disable callbacks
                 void*           cbArg,   // callback arg
                 const config_t* cfg = nullptr ); // if cfg==nullptr then default_config() is used

    rc_t create( handle_t&       hRef,
                 callback_t      cbFunc,         // set to nullptr to disable callbacks
                 void*           cbArg,          // callback arg
                 const object_t* cfg );          //

    rc_t destroy( handle_t& hRef );

    rc_t setMidiMsg( handle_t h, double sec, unsigned uid, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 );
    rc_t setMarker(  handle_t h, double sec, unsigned uid, uint8_t ch, unsigned typeId, unsigned value );

    void reset( handle_t h );

    const config_t* config( handle_t h );

    
    // Get the first link in time, then use event.tlink to traverse the
    // events in time.
    const event_t*  get_first_link( handle_t h );

    // Return cached events.
    // Note that the cached events are returned as a link list based on each channel note and pedal.
    // This list is ordered in time and gives the recorded state of the voice/pedal.
    const event_t* note_event_list(  handle_t h, uint8_t ch, uint8_t pitch );
    const event_t* pedal_event_list( handle_t h, uint8_t ch, unsigned pedal_index );

    // Returns kInvalidIdx if 'midi_ctl_id' is not a valid MIDi pedal control id.
    unsigned pedal_midi_ctl_id_to_index( unsigned midi_ctl_id );
    unsigned pedal_index_to_midi_ctl_id( unsigned pedal_idx );
    unsigned pedal_count( handle_t h );
    
    void get_note_extents(  handle_t h, uint8_t& minPitchRef, uint8_t& maxPitchRef, double& minSecRef, double& maxSecRef );
    void get_pedal_extents( handle_t h, double& minSecRef, double& maxSecRef );

    rc_t report_events( handle_t h, const char* out_fname );

    rc_t load_from_midi_file( handle_t h, const char* midi_fname );
    
    rc_t test( const object_t* cfg );
    
  }
}

#endif
