/** \file .c
 *  \brief This module
 */

/***************************************************************************************************
 * Includes
 **************************************************************************************************/

#include "rpi_midi_controller.h"

#include <alsa/asoundlib.h>

#include "log.h"

#include "rpi_param.h"

/***************************************************************************************************
 * Preprocessor Constants and Macros
 **************************************************************************************************/

#define C3 48
#define Gd3 56

/***************************************************************************************************
 * Type and Contant Definitions
 **************************************************************************************************/

/***************************************************************************************************
 * Persistent Variables
 **************************************************************************************************/

/**
 * Boolean for interrupting the main thread loop
 */
static snd_seq_t *seq_handle;

/**
 * Boolean for interrupting the main thread loop
 */
static int sys_port;

/**
 * Boolean for interrupting the main thread loop
 */
static int in_port;

/* state machine */
int destChannel = 0xf;

/***************************************************************************************************
 * Internal Function Prototypes
 **************************************************************************************************/

/**
 * \brief
 */
void subscribe_system_port();

/**
 * \brief
 *
 * \param   controller
 */
void subscribe_midi_controller(snd_seq_addr_t controller);

/**
 * \brief
 *
 * \param   ev
 */
void handle_system_port_events(snd_seq_event_t *ev);

/**
 * \brief
 *
 * \param   ev
 */
void handle_in_port_events(snd_seq_event_t *ev);

/***************************************************************************************************
 * External Function Definitions
 **************************************************************************************************/

void midi_controller_setup() {
  if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK) != 0) {
    SNDERR("snd_seq_open");
  }

  if (snd_seq_set_client_name(seq_handle, "Midi Listener") != 0) {
    SNDERR("snd_seq_set_client_name");
  }

  sys_port = snd_seq_create_simple_port(seq_handle, "sys:in",
                                        SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
                                        SND_SEQ_PORT_TYPE_APPLICATION);

  if (sys_port < 0) {
    SNDERR("snd_seq_create_simple_port");
  }

  subscribe_system_port();

  in_port = snd_seq_create_simple_port(seq_handle, "listen:in",
                                       SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
                                       SND_SEQ_PORT_TYPE_APPLICATION);

  if (in_port < 0) {
    SNDERR("snd_seq_create_simple_port");
  }

  snd_seq_addr_t controller = {.client = 24, .port = 0};
  subscribe_midi_controller(controller);
}

void midi_controller_execute() {
  snd_seq_event_t *ev = NULL;
  int ret;
  while ((ret = snd_seq_event_input(seq_handle, &ev)) > 0) {
    if (ev->dest.port == sys_port) {
      handle_system_port_events(ev);
    } else if (ev->dest.port == in_port) {
      handle_in_port_events(ev);
    } else {
      log_warn("Unkonwn midi dest port %d", ev->dest.port);
    }
  }
  if (ret < 0 && ret != -EAGAIN) {
    SNDERR("snd_seq_event_input");
  }
}

void midi_controller_close() {}

/***************************************************************************************************
 * Internal Function Definitions
 **************************************************************************************************/

void subscribe_system_port() {
  snd_seq_addr_t sender = {.client = 0, .port = SND_SEQ_PORT_SYSTEM_ANNOUNCE};
  snd_seq_connect_from(seq_handle, sys_port, sender.client, sender.port);
}

void subscribe_midi_controller(snd_seq_addr_t controller) {
  snd_seq_connect_from(seq_handle, in_port, controller.client, controller.port);
}

void handle_system_port_events(snd_seq_event_t *ev) {
  if (ev->type == SND_SEQ_EVENT_PORT_START) {
    snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
    if (newport->client != snd_seq_client_id(seq_handle)) {
      log_info("New port %d:%d", newport->client, newport->port);
      subscribe_midi_controller(*newport);
    }
  }
}

void handle_in_port_events(snd_seq_event_t *ev) {
  switch (ev->type) {
  case SND_SEQ_EVENT_PGMCHANGE:
    if (ev->data.control.value < LED_NCHANS) {
      if (ev->data.control.value != destChannel) {
        destChannel = ev->data.control.value;
        log_info("Control param channel %d", ev->data.control.value);
      }
    } else if (destChannel != GLOBAL_CHANNEL) {
      log_info("Control global param");
      destChannel = GLOBAL_CHANNEL;
    }
    break;

  case SND_SEQ_EVENT_NOTEON:
    if (ev->data.note.note == C3) {
      if (destChannel != GLOBAL_CHANNEL) {
        destChannel = GLOBAL_CHANNEL;
        log_info("Control global param");
      }
    } else if (ev->data.note.note >= Gd3) {
      set_pattern(ev->data.note.note - Gd3);
    } else {
      log_debug("NOTEON : ch %#04x - note %d - vel %d", ev->data.note.channel, ev->data.note.note,
                ev->data.note.velocity);
    }
    break;

  case SND_SEQ_EVENT_NOTEOFF:
    break;

  case SND_SEQ_EVENT_CONTROLLER:
    switch (ev->data.control.param) {
    /* Pots */
    case 1:
      set_sensitivity(destChannel, ev->data.control.value);
      break;
    case 2:
      set_luminosity(destChannel, ev->data.control.value);
      break;
    case 3:
      set_hue_base(destChannel, ev->data.control.value);
      break;
    case 4:
      set_hue_interval(destChannel, ev->data.control.value);
      break;
    case 5:
      set_gravity(ev->data.control.value);
      break;
    case 6:
      set_saturation(destChannel, ev->data.control.value);
      break;
    case 7:
      set_hue_auto_shift_interval(destChannel, ev->data.control.value);
      break;
    case 8:
      set_hue_auto_shift_speed(destChannel, ev->data.control.value);
      break;

    /* Pads in CC */
    case 26:
      set_hue_auto_shift(ev->data.control.value != 0);
      break;

    default:
      log_debug("CONTROLLER : ch %#04x - param %u - value %d", ev->data.control.channel,
                ev->data.control.param, ev->data.control.value);
      break;
    }
    break;

  default:
    log_debug("ev %02d : %#010x - %#010x - %#010x", ev->type, ev->data.raw32.d[0],
              ev->data.raw32.d[1], ev->data.raw32.d[2]);
    break;
  }
  if (ev->type == SND_SEQ_EVENT_PORT_START) {
    snd_seq_addr_t *newport = (snd_seq_addr_t *)&ev->data;
    if (newport->client != snd_seq_client_id(seq_handle)) {
      log_debug("New port %d:%d", newport->client, newport->port);
      subscribe_midi_controller(*newport);
    }
  }
}
