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

#include "process_setup.h"

#include "jalv_config.h"
#include "lv2_evbuf.h"
#include "macros.h"
#include "mapper.h"
#include "nodes.h"
#include "process.h"
#include "query.h"
#include "settings.h"
#include "string_utils.h"
#include "types.h"
#include "urids.h"
#include "worker.h"

#include <lilv/lilv.h>
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
#include <zix/allocator.h>
#include <zix/ring.h>
#include <zix/sem.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#define MIN_MSG_SIZE 1024U

int
jalv_process_init(JalvProcess* const     proc,
                  const JalvURIDs* const urids,
                  JalvMapper* const      mapper,
                  const bool             trace)
{
  proc->get_msg.atom.size  = sizeof(LV2_Atom_Object_Body);
  proc->get_msg.atom.type  = urids->atom_Object;
  proc->get_msg.body.id    = 0U;
  proc->get_msg.body.otype = urids->patch_Get;

  proc->instance           = NULL;
  proc->ui_to_plugin       = NULL;
  proc->plugin_to_ui       = NULL;
  proc->worker             = NULL;
  proc->state_worker       = NULL;
  proc->ports              = NULL;
  proc->process_msg_size   = 0U;
  proc->process_msg        = NULL;
  proc->run_state          = JALV_PAUSED;
  proc->control_in         = UINT32_MAX;
  proc->num_ports          = 0U;
  proc->pending_frames     = 0U;
  proc->update_frames      = 0U;
  proc->transport.position = 0U;
  proc->transport.bpm      = 120.0f;
  proc->transport.rolling  = false;
  proc->trace              = trace;

  zix_sem_init(&proc->paused, 0);
  lv2_atom_forge_init(&proc->forge, jalv_mapper_urid_map(mapper));

  return 0;
}

void
jalv_process_cleanup(JalvProcess* const proc)
{
  zix_sem_destroy(&proc->paused);
  jalv_worker_free(proc->worker);
  jalv_worker_free(proc->state_worker);
  zix_ring_free(proc->ui_to_plugin);
  zix_ring_free(proc->plugin_to_ui);
  zix_aligned_free(NULL, proc->process_msg);

  for (uint32_t i = 0U; i < proc->num_ports; ++i) {
    jalv_process_port_cleanup(&proc->ports[i]);
  }
}

void
jalv_process_activate(JalvProcess* const        proc,
                      const JalvURIDs* const    urids,
                      LilvInstance* const       instance,
                      const JalvSettings* const settings)
{
  proc->instance = instance;

  size_t max_msg_size = MIN_MSG_SIZE;
  for (uint32_t i = 0U; i < proc->num_ports; ++i) {
    JalvProcessPort* const port = &proc->ports[i];
    if (port->type == TYPE_EVENT) {
      const size_t size =
        port->buf_size ? port->buf_size : settings->midi_buf_size;

      if (!port->evbuf || lv2_evbuf_get_capacity(port->evbuf) != size) {
        lv2_evbuf_free(port->evbuf);
        port->evbuf =
          lv2_evbuf_new(size, urids->atom_Chunk, urids->atom_Sequence);
      }

      lv2_evbuf_reset(port->evbuf, port->flow == FLOW_INPUT);
      lilv_instance_connect_port(
        proc->instance, i, lv2_evbuf_get_buffer(port->evbuf));

      if (port->flow == FLOW_INPUT) {
        max_msg_size = MAX(max_msg_size, size);
      }
    }
  }

  // Allocate UI<=>process communication rings and receive buffer
  if (!proc->ui_to_plugin) {
    proc->ui_to_plugin = zix_ring_new(NULL, settings->ring_size);
    zix_ring_mlock(proc->ui_to_plugin);
  }
  if (!proc->plugin_to_ui) {
    proc->plugin_to_ui = zix_ring_new(NULL, settings->ring_size);
    zix_ring_mlock(proc->plugin_to_ui);
  }

  // Allocate UI=>process message receive buffer
  if (!proc->process_msg || proc->process_msg_size != max_msg_size) {
    zix_free(NULL, proc->process_msg);
    proc->process_msg_size = max_msg_size;
    proc->process_msg = zix_aligned_alloc(NULL, 8U, proc->process_msg_size);
  }

  proc->process_msg_size = max_msg_size;
  proc->update_frames =
    (uint32_t)(settings->sample_rate / settings->ui_update_hz);
}

void
jalv_process_deactivate(JalvProcess* const proc)
{
  zix_aligned_free(NULL, proc->process_msg);
  proc->process_msg = NULL;

  for (uint32_t i = 0U; i < proc->num_ports; ++i) {
    lv2_evbuf_free(proc->ports[i].evbuf);
    lilv_instance_connect_port(proc->instance, i, NULL);
    proc->ports[i].evbuf = NULL;
  }
}

static void
set_port_types(JalvProcessPort* const  port,
               const JalvNodes* const  nodes,
               const LilvPlugin* const plugin,
               const LilvPort* const   lilv_port)
{
  if (lilv_port_is_a(plugin, lilv_port, nodes->lv2_InputPort)) {
    port->flow = FLOW_INPUT;
  } else if (lilv_port_is_a(plugin, lilv_port, nodes->lv2_OutputPort)) {
    port->flow = FLOW_OUTPUT;
  }

  if (lilv_port_is_a(plugin, lilv_port, nodes->lv2_ControlPort)) {
    port->type = TYPE_CONTROL;
  } else if (lilv_port_is_a(plugin, lilv_port, nodes->lv2_AudioPort)) {
    port->type = TYPE_AUDIO;
#if USE_JACK_METADATA
  } else if (lilv_port_is_a(plugin, lilv_port, nodes->lv2_CVPort)) {
    port->type = TYPE_CV;
#endif
  } else if (lilv_port_is_a(plugin, lilv_port, nodes->atom_AtomPort)) {
    port->type = TYPE_EVENT;
  }
}

int
jalv_process_port_init(JalvProcessPort* const  port,
                       const JalvNodes* const  nodes,
                       const LilvPlugin* const plugin,
                       const LilvPort* const   lilv_port)
{
  const LilvNode* const symbol = lilv_port_get_symbol(plugin, lilv_port);

  port->sys_port = NULL;
  port->evbuf    = NULL;
  port->buf_size = 0U;

  // Set symbol and label
  LilvNode* const name = lilv_port_get_name(plugin, lilv_port);
  port->symbol = symbol ? jalv_strdup(lilv_node_as_string(symbol)) : NULL;
  port->label  = name ? jalv_strdup(lilv_node_as_string(name)) : NULL;
  lilv_node_free(name);

  // Set flow and type
  set_port_types(port, nodes, plugin, lilv_port);
  if ((!port->flow || !port->type) &&
      !lilv_port_has_property(
        plugin, lilv_port, nodes->lv2_connectionOptional)) {
    return 1;
  }

  // Set buffer size
  LilvNode* const min_size =
    lilv_port_get(plugin, lilv_port, nodes->rsz_minimumSize);
  if (min_size && lilv_node_is_int(min_size)) {
    port->buf_size = (uint32_t)MAX(lilv_node_as_int(min_size), 0);
  }
  lilv_node_free(min_size);

  // Set flags

  port->is_primary =
    (port->type == TYPE_EVENT &&
     jalv_port_has_designation(nodes, plugin, lilv_port, nodes->lv2_control));

  port->reports_latency =
    (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL &&
     (lilv_port_has_property(plugin, lilv_port, nodes->lv2_reportsLatency) ||
      jalv_port_has_designation(nodes, plugin, lilv_port, nodes->lv2_latency)));

  port->supports_midi =
    lilv_port_supports_event(plugin, lilv_port, nodes->midi_MidiEvent);

  port->supports_pos =
    lilv_port_supports_event(plugin, lilv_port, nodes->time_Position);

  port->is_bpm = (port->flow == FLOW_INPUT && port->type == TYPE_CONTROL &&
                  jalv_port_has_designation(
                    nodes, plugin, lilv_port, nodes->time_beatsPerMinute));

  return 0;
}

void
jalv_process_port_cleanup(JalvProcessPort* const port)
{
  if (port) {
    if (port->evbuf) {
      lv2_evbuf_free(port->evbuf);
    }
    free(port->label);
    free(port->symbol);
  }
}
