// Copyright 2007-2022 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#ifndef JALV_INTERNAL_H
#define JALV_INTERNAL_H

#include "jalv_config.h"
#include "lv2_evbuf.h"
#include "symap.h"

#include "zix/ring.h"
#include "zix/sem.h"
#include "zix/thread.h"

#include "lilv/lilv.h"
#include "serd/serd.h"
#include "sratom/sratom.h"
#ifdef HAVE_SUIL
#  include "suil/suil.h"
#endif

#include "lv2/atom/atom.h"
#include "lv2/atom/forge.h"
#include "lv2/core/lv2.h"
#include "lv2/data-access/data-access.h"
#include "lv2/log/log.h"
#include "lv2/options/options.h"
#include "lv2/state/state.h"
#include "lv2/ui/ui.h"
#include "lv2/urid/urid.h"
#include "lv2/worker/worker.h"

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __clang__
#  define REALTIME __attribute__((annotate("realtime")))
#else
#  define REALTIME
#endif

#ifdef __GNUC__
#  define JALV_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
#else
#  define JALV_LOG_FUNC(fmt, arg1)
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct Jalv;

typedef struct JalvBackend JalvBackend;

typedef struct Jalv Jalv;

enum PortFlow { FLOW_UNKNOWN, FLOW_INPUT, FLOW_OUTPUT };

enum PortType { TYPE_UNKNOWN, TYPE_CONTROL, TYPE_AUDIO, TYPE_EVENT, TYPE_CV };

struct Port {
  const LilvPort* lilv_port; ///< LV2 port
  enum PortType   type;      ///< Data type
  enum PortFlow   flow;      ///< Data flow direction
  void*           sys_port;  ///< For audio/MIDI ports, otherwise NULL
  LV2_Evbuf*      evbuf;     ///< For MIDI ports, otherwise NULL
  void*           widget;    ///< Control widget, if applicable
  size_t          buf_size;  ///< Custom buffer size, or 0
  uint32_t        index;     ///< Port index
  float           control;   ///< For control ports, otherwise 0.0f
};

// Controls

/// Type of plugin control
typedef enum {
  PORT,    ///< Control port
  PROPERTY ///< Property (set via atom message)
} ControlType;

typedef struct {
  float value;
  char* label;
} ScalePoint;

/// Order scale points by value
int
scale_point_cmp(const ScalePoint* a, const ScalePoint* b);

/// Plugin control
typedef struct {
  Jalv*       jalv;
  ControlType type;
  LilvNode*   node;
  LilvNode*   symbol;         ///< Symbol
  LilvNode*   label;          ///< Human readable label
  LV2_URID    property;       ///< Iff type == PROPERTY
  uint32_t    index;          ///< Iff type == PORT
  LilvNode*   group;          ///< Port/control group, or NULL
  void*       widget;         ///< Control Widget
  size_t      n_points;       ///< Number of scale points
  ScalePoint* points;         ///< Scale points
  LV2_URID    value_type;     ///< Type of control value
  LilvNode*   min;            ///< Minimum value
  LilvNode*   max;            ///< Maximum value
  LilvNode*   def;            ///< Default value
  bool        is_toggle;      ///< Boolean (0 and 1 only)
  bool        is_integer;     ///< Integer values only
  bool        is_enumeration; ///< Point values only
  bool        is_logarithmic; ///< Logarithmic scale
  bool        is_writable;    ///< Writable (input)
  bool        is_readable;    ///< Readable (output)
} ControlID;

ControlID*
new_port_control(Jalv* jalv, uint32_t index);

ControlID*
new_property_control(Jalv* jalv, const LilvNode* property);

typedef struct {
  size_t      n_controls;
  ControlID** controls;
} Controls;

void
add_control(Controls* controls, ControlID* control);

ControlID*
get_property_control(const Controls* controls, LV2_URID property);

/// Control change event, sent through ring buffers for UI updates
typedef struct {
  uint32_t index;
  uint32_t protocol;
  uint32_t size;

  // Followed immediately by size bytes of data
} ControlChange;

typedef struct {
  char*    name;            ///< Client name
  int      name_exact;      ///< Exit if name is taken
  char*    load;            ///< Path for state to load
  char*    preset;          ///< URI of preset to load
  char**   controls;        ///< Control values
  uint32_t buffer_size;     ///< Plugin <= >UI communication buffer size
  double   update_rate;     ///< UI update rate in Hz
  double   scale_factor;    ///< UI scale factor
  int      dump;            ///< Dump communication iff true
  int      trace;           ///< Print trace log iff true
  int      generic_ui;      ///< Use generic UI iff true
  int      show_hidden;     ///< Show controls for notOnGUI ports
  int      no_menu;         ///< Hide menu iff true
  int      show_ui;         ///< Show non-embedded UI
  int      print_controls;  ///< Print control changes to stdout
  int      non_interactive; ///< Do not listen for commands on stdin
  char*    ui_uri;          ///< URI of UI to load
} JalvOptions;

typedef struct {
  LV2_URID atom_Float;
  LV2_URID atom_Int;
  LV2_URID atom_Object;
  LV2_URID atom_Path;
  LV2_URID atom_String;
  LV2_URID atom_eventTransfer;
  LV2_URID bufsz_maxBlockLength;
  LV2_URID bufsz_minBlockLength;
  LV2_URID bufsz_sequenceSize;
  LV2_URID log_Error;
  LV2_URID log_Trace;
  LV2_URID log_Warning;
  LV2_URID midi_MidiEvent;
  LV2_URID param_sampleRate;
  LV2_URID patch_Get;
  LV2_URID patch_Put;
  LV2_URID patch_Set;
  LV2_URID patch_body;
  LV2_URID patch_property;
  LV2_URID patch_value;
  LV2_URID time_Position;
  LV2_URID time_bar;
  LV2_URID time_barBeat;
  LV2_URID time_beatUnit;
  LV2_URID time_beatsPerBar;
  LV2_URID time_beatsPerMinute;
  LV2_URID time_frame;
  LV2_URID time_speed;
  LV2_URID ui_scaleFactor;
  LV2_URID ui_updateRate;
} JalvURIDs;

typedef struct {
  LilvNode* atom_AtomPort;
  LilvNode* atom_Chunk;
  LilvNode* atom_Float;
  LilvNode* atom_Path;
  LilvNode* atom_Sequence;
  LilvNode* lv2_AudioPort;
  LilvNode* lv2_CVPort;
  LilvNode* lv2_ControlPort;
  LilvNode* lv2_InputPort;
  LilvNode* lv2_OutputPort;
  LilvNode* lv2_connectionOptional;
  LilvNode* lv2_control;
  LilvNode* lv2_default;
  LilvNode* lv2_enumeration;
  LilvNode* lv2_extensionData;
  LilvNode* lv2_integer;
  LilvNode* lv2_maximum;
  LilvNode* lv2_minimum;
  LilvNode* lv2_name;
  LilvNode* lv2_reportsLatency;
  LilvNode* lv2_sampleRate;
  LilvNode* lv2_symbol;
  LilvNode* lv2_toggled;
  LilvNode* midi_MidiEvent;
  LilvNode* pg_group;
  LilvNode* pprops_logarithmic;
  LilvNode* pprops_notOnGUI;
  LilvNode* pprops_rangeSteps;
  LilvNode* pset_Preset;
  LilvNode* pset_bank;
  LilvNode* rdfs_comment;
  LilvNode* rdfs_label;
  LilvNode* rdfs_range;
  LilvNode* rsz_minimumSize;
  LilvNode* ui_showInterface;
  LilvNode* work_interface;
  LilvNode* work_schedule;
  LilvNode* end; ///< NULL terminator for easy freeing of entire structure
} JalvNodes;

typedef enum { JALV_RUNNING, JALV_PAUSE_REQUESTED, JALV_PAUSED } JalvPlayState;

typedef struct {
  Jalv*                       jalv;      ///< Pointer back to Jalv
  ZixRing*                    requests;  ///< Requests to the worker
  ZixRing*                    responses; ///< Responses from the worker
  void*                       response;  ///< Worker response buffer
  ZixSem                      sem;       ///< Worker semaphore
  ZixThread                   thread;    ///< Worker thread
  const LV2_Worker_Interface* iface;     ///< Plugin worker interface
  bool                        threaded;  ///< Run work in another thread
} JalvWorker;

typedef struct {
  LV2_Feature                map_feature;
  LV2_Feature                unmap_feature;
  LV2_State_Make_Path        make_path;
  LV2_Feature                make_path_feature;
  LV2_Worker_Schedule        sched;
  LV2_Feature                sched_feature;
  LV2_Worker_Schedule        ssched;
  LV2_Feature                state_sched_feature;
  LV2_Log_Log                llog;
  LV2_Feature                log_feature;
  LV2_Options_Option         options[7];
  LV2_Feature                options_feature;
  LV2_Feature                safe_restore_feature;
  LV2UI_Request_Value        request_value;
  LV2_Feature                request_value_feature;
  LV2_Extension_Data_Feature ext_data;
} JalvFeatures;

struct Jalv {
  JalvOptions       opts;          ///< Command-line options
  JalvURIDs         urids;         ///< URIDs
  JalvNodes         nodes;         ///< Nodes
  LV2_Atom_Forge    forge;         ///< Atom forge
  const char*       prog_name;     ///< Program name (argv[0])
  LilvWorld*        world;         ///< Lilv World
  LV2_URID_Map      map;           ///< URI => Int map
  LV2_URID_Unmap    unmap;         ///< Int => URI map
  SerdEnv*          env;           ///< Environment for RDF printing
  Sratom*           sratom;        ///< Atom serialiser
  Sratom*           ui_sratom;     ///< Atom serialiser for UI thread
  Symap*            symap;         ///< URI map
  ZixSem            symap_lock;    ///< Lock for URI map
  JalvBackend*      backend;       ///< Audio system backend
  ZixRing*          ui_events;     ///< Port events from UI
  ZixRing*          plugin_events; ///< Port events from plugin
  void*             ui_event_buf;  ///< Buffer for reading UI port events
  JalvWorker        worker;        ///< Worker thread implementation
  JalvWorker        state_worker;  ///< Synchronous worker for state restore
  ZixSem            work_lock;     ///< Lock for plugin work() method
  ZixSem            done;          ///< Exit semaphore
  ZixSem            paused;        ///< Paused signal from process thread
  JalvPlayState     play_state;    ///< Current play state
  char*             temp_dir;      ///< Temporary plugin state directory
  char*             save_dir;      ///< Plugin save directory
  const LilvPlugin* plugin;        ///< Plugin class (RDF data)
  LilvState*        preset;        ///< Current preset
  LilvUIs*          uis;           ///< All plugin UIs (RDF data)
  const LilvUI*     ui;            ///< Plugin UI (RDF data)
  const LilvNode*   ui_type;       ///< Plugin UI type (unwrapped)
  LilvInstance*     instance;      ///< Plugin instance (shared library)
#ifdef HAVE_SUIL
  SuilHost*     ui_host;     ///< Plugin UI host support
  SuilInstance* ui_instance; ///< Plugin UI instance (shared library)
#endif
  void*               window;          ///< Window (if applicable)
  struct Port*        ports;           ///< Port array of size num_ports
  Controls            controls;        ///< Available plugin controls
  uint32_t            block_length;    ///< Audio buffer size (block length)
  size_t              midi_buf_size;   ///< Size of MIDI port buffers
  uint32_t            control_in;      ///< Index of control input port
  uint32_t            num_ports;       ///< Size of the two following arrays:
  uint32_t            plugin_latency;  ///< Latency reported by plugin (if any)
  float               ui_update_hz;    ///< Frequency of UI updates
  float               ui_scale_factor; ///< UI scale factor
  float               sample_rate;     ///< Sample rate
  uint32_t            event_delta_t;   ///< Frames since last update sent to UI
  uint32_t            position;        ///< Transport position in frames
  float               bpm;             ///< Transport tempo in beats per minute
  bool                rolling;         ///< Transport speed (0=stop, 1=play)
  bool                buf_size_set;    ///< True iff buffer size callback fired
  bool                exit;            ///< True iff execution is finished
  bool                has_ui;          ///< True iff a control UI is present
  bool                request_update;  ///< True iff a plugin update is needed
  bool                safe_restore;    ///< Plugin restore() is thread-safe
  JalvFeatures        features;
  const LV2_Feature** feature_list;
};

int
jalv_open(Jalv* jalv, int* argc, char*** argv);

int
jalv_init(int* argc, char*** argv, JalvOptions* opts);

int
jalv_close(Jalv* jalv);

JalvBackend*
jalv_backend_init(Jalv* jalv);

void
jalv_backend_activate(Jalv* jalv);

void
jalv_backend_deactivate(Jalv* jalv);

void
jalv_backend_close(Jalv* jalv);

/// Expose a port to the system (if applicable) and connect it to its buffer
void
jalv_backend_activate_port(Jalv* jalv, uint32_t port_index);

void
jalv_create_ports(Jalv* jalv);

void
jalv_allocate_port_buffers(Jalv* jalv);

struct Port*
jalv_port_by_symbol(Jalv* jalv, const char* sym);

void
jalv_create_controls(Jalv* jalv, bool writable);

ControlID*
jalv_control_by_symbol(Jalv* jalv, const char* sym);

void
jalv_set_control(const ControlID* control,
                 uint32_t         size,
                 LV2_URID         type,
                 const void*      body);

const char*
jalv_native_ui_type(void);

bool
jalv_discover_ui(Jalv* jalv);

float
jalv_ui_refresh_rate(Jalv* jalv);

float
jalv_ui_scale_factor(Jalv* jalv);

int
jalv_open_ui(Jalv* jalv);

LilvNode*
jalv_select_plugin(Jalv* jalv);

void
jalv_init_ui(Jalv* jalv);

int
jalv_close_ui(Jalv* jalv);

void
jalv_ui_instantiate(Jalv* jalv, const char* native_ui_type, void* parent);

bool
jalv_ui_is_resizable(Jalv* jalv);

void
jalv_ui_write(void*       jalv_handle,
              uint32_t    port_index,
              uint32_t    buffer_size,
              uint32_t    protocol,
              const void* buffer);

void
jalv_apply_ui_events(Jalv* jalv, uint32_t nframes);

uint32_t
jalv_ui_port_index(void* controller, const char* symbol);

void
jalv_ui_port_event(Jalv*       jalv,
                   uint32_t    port_index,
                   uint32_t    buffer_size,
                   uint32_t    protocol,
                   const void* buffer);

bool
jalv_send_to_ui(Jalv*       jalv,
                uint32_t    port_index,
                uint32_t    type,
                uint32_t    size,
                const void* body);
bool
jalv_run(Jalv* jalv, uint32_t nframes);

int
jalv_update(Jalv* jalv);

typedef int (*PresetSink)(Jalv*           jalv,
                          const LilvNode* node,
                          const LilvNode* title,
                          void*           data);

int
jalv_load_presets(Jalv* jalv, PresetSink sink, void* data);

int
jalv_unload_presets(Jalv* jalv);

int
jalv_apply_preset(Jalv* jalv, const LilvNode* preset);

int
jalv_delete_current_preset(Jalv* jalv);

int
jalv_save_preset(Jalv*       jalv,
                 const char* dir,
                 const char* uri,
                 const char* label,
                 const char* filename);

void
jalv_save(Jalv* jalv, const char* dir);

void
jalv_save_port_values(Jalv* jalv, SerdWriter* writer, const SerdNode* subject);
char*
jalv_make_path(LV2_State_Make_Path_Handle handle, const char* path);

void
jalv_apply_state(Jalv* jalv, LilvState* state);

char*
atom_to_turtle(LV2_URID_Unmap* unmap,
               const SerdNode* subject,
               const SerdNode* predicate,
               const LV2_Atom* atom);

void
jalv_print_control(Jalv* jalv, const struct Port* port, float value);

char*
jalv_strdup(const char* str);

char*
jalv_strjoin(const char* a, const char* b);

JALV_LOG_FUNC(3, 4)
int
jalv_printf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...);

JALV_LOG_FUNC(3, 0)
int
jalv_vprintf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, va_list ap);

bool
jalv_ansi_start(FILE* stream, int color);

void
jalv_ansi_reset(FILE* stream);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // JALV_INTERNAL_H