/** \file rpi_artnet.c
 *  \brief This module contains continuous tasks for artnet node communications.
 *
 * This is the implementation file.
 */

/***************************************************************************************************
 * Includes
 **************************************************************************************************/
#include "rpi_artnet.h"

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include "log.h"
#include "rpi_artnet_utils.h"

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

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

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

/**
 * Boolean for interrupting the main thread loop
 */
bool isUdpListenerRunning = false;
/**
 * Task thread identifier
 */
pthread_t udpListener;
/**
 * Buffer for storing artnet dmx data received
 */
uint8_t artDmxBufferArray[16][512];

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

/**
 * \brief This function is used to thread main execution function
 *
 * \param   arg not used.
 *
 * \return  NULL.
 */
static void *artnet_udp_handler(void *arg);

/**
 * \brief This function is used to send an artnet poll reply packet
 *
 * \param[in]   fdUdpSocket The UDP socket file descriptor.
 *
 * \param[in]   senderAddr  The adress of poll emiter.
 */
static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr);

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

void artnet_start() {
  isUdpListenerRunning = true;

  if (pthread_create(&udpListener, NULL, artnet_udp_handler, NULL) < 0) {
    log_error("pthread_create: %s", strerror(errno));
  }
}

void artnet_stop() {
  isUdpListenerRunning = false;

  if (pthread_join(udpListener, NULL) != 0) {
    log_error("pthread_join: %s", strerror(errno));
  }
}

int artnet_get_dmx_data(unsigned int univerve, uint8_t *dmxData[]) {
  if (univerve > 8) {
    log_error("Universe %d out of bounds %d\n", univerve, 16);
    *dmxData = NULL;
    return -1;
  }
  *dmxData = artDmxBufferArray[univerve];
  log_trace("%d;%d;%d;%d", (*dmxData)[0], (*dmxData)[1], (*dmxData)[2], (*dmxData)[3]);
  return 0;
}

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

static void *artnet_udp_handler(void *arg) {
  int fdUdpSocket = -1;
  struct pollfd pollFdArray[1];
  int timeoutMs;
  int flags;
  char buffer[ARTNET_MAX_BUFFER];
  struct sockaddr_in serverAddr, srcAddr;
  socklen_t srcLen = sizeof(struct sockaddr_in);

  /* Create UDP socket */
  fdUdpSocket = socket(PF_INET, SOCK_DGRAM, 0);
  if (fdUdpSocket < 0) {
    log_error("Opening socket failed: %s", strerror(errno));
  }

  /* Set non-blocking socket */
  flags = fcntl(fdUdpSocket, F_GETFL, 0);
  fcntl(fdUdpSocket, F_SETFL, flags | O_NONBLOCK);

  /* pollfd structure and timeout */
  memset(pollFdArray, 0, sizeof(pollFdArray));
  pollFdArray[0].fd = fdUdpSocket;
  pollFdArray[0].events = POLLIN;
  timeoutMs = 10;

  /* Configure settings in address struct */
  serverAddr.sin_family = AF_INET;
  serverAddr.sin_port = htons(ARTNET_PORT);
  serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
  memset(serverAddr.sin_zero, '\0', sizeof(serverAddr.sin_zero));

  /* Bind socket with address struct */
  bind(fdUdpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));

  while (isUdpListenerRunning) {

    int pollReturn;
    if ((pollReturn = poll(pollFdArray, 1, timeoutMs)) == 1) {
      ssize_t bufferLen =
          recvfrom(fdUdpSocket, buffer, ARTNET_MAX_BUFFER, 0, (struct sockaddr *)&srcAddr, &srcLen);

      if (bufferLen <= ARTNET_MAX_BUFFER && bufferLen > sizeof(artnetHeader_t)) {
        artnetHeader_t *artnetHeader = (artnetHeader_t *)buffer;

        if (memcmp(artnetHeader->id, ARTNET_ID, sizeof(ARTNET_ID)) == 0) {
          switch (artnetHeader->opCode) {
          case OpDmx:
            if (bufferLen >= 20) {
              artDmx_t *artDmx = (artDmx_t *)buffer;
              uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo;
              uint8_t *artDmxBuffer = artDmxBufferArray[artDmx->subUni & 0x00ff];
              if (dmxLength <= 512) {
                // store for later use
                memcpy(artDmxBuffer, artDmx->data, dmxLength);
              }
            }
            break;

          case OpPoll:
            artnet_send_poll_reply(fdUdpSocket, srcAddr);
            break;

          default:
            break;
          }
        }
      }

    } else if (pollReturn < 0) {
      log_error("error polling %d: %s", fdUdpSocket, strerror(errno));
    }
  }

  close(fdUdpSocket);

  return NULL;
}

static void artnet_send_poll_reply(int fdUdpSocket, struct sockaddr_in senderAddr) {
  /* Configure settings in address struct */
  senderAddr.sin_family = AF_INET;
  senderAddr.sin_port = htons(ARTNET_PORT);
  memset(senderAddr.sin_zero, '\0', sizeof(senderAddr.sin_zero));

  if (sendto(fdUdpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
             (struct sockaddr *)&senderAddr, sizeof(senderAddr)) < 0) {
    log_error("%s: sending poll reply to \"%s:%d\": %s", inet_ntoa(senderAddr.sin_addr),
              senderAddr.sin_port, strerror(errno));
  }
}