/** \file rpi_cava.h
 *  \brief This module contains continuous tasks for cava (spectrum analyzer) comunication
 */

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

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>

#include "log.h"

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

/**
 * Constant for Maximum char in a line (128 number of (5 char + 1 delim) + \n )
 */
#define MAXLINECHAR CAVA_BAR_NUMBER * (5 + 1) + 1

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

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

/**
 * Cava process id
 */
pid_t cavaPid;
/**
 * Boolean for interrupting the main thread loop
 */
bool isFifoReaderRunning = false;
/**
 * Task thread identifier
 */
pthread_t fifoReader;

int fdCavaInput;
char lineBuffer[MAXLINECHAR];
uint16_t buffer[128 + 2];

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

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

static int start_cava_process();

static void stop_cava_process();

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

void cava_start() {
  isFifoReaderRunning = true;
  pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
}

void cava_stop() {
  isFifoReaderRunning = false;
  if (pthread_join(fifoReader, NULL) != 0) {
    log_error("pthread_join: %s", strerror(errno));
  }
}

int cava_get_buffer(uint16_t **buffer_dst) {
  *buffer_dst = buffer;
  log_trace("%d;%d;%d;%d", buffer[0], buffer[1], buffer[2], buffer[3]);
  return 0;
}

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

static void *fifo_to_buffer(void *arg) {
  struct pollfd fds[1];
  int timeoutMs;
  int ret;
  size_t valueIndex = 0, charOffset = 0;
  char strValue[6] = "0\0";
  bool hasToBeDiscarded = true;

  if ((fdCavaInput = start_cava_process()) < 0) {
    log_error("y'a un truc qui a pas marché");
  }

  memset(fds, 0, sizeof(fds));
  fds[0].fd = fdCavaInput;
  fds[0].events = POLLIN;
  timeoutMs = 10;

  while (isFifoReaderRunning) {

    if ((ret = poll(fds, 1, timeoutMs)) == 1) {
      int nread;
      nread = read(fdCavaInput, lineBuffer, 128 + 1);

      if (nread >= 0) {
        for (size_t i = 0; i < nread; ++i) {
          char current = lineBuffer[i];

          if (hasToBeDiscarded) {
            if (current == '\n') {
              charOffset = 0;
              strValue[charOffset] = '\0';
              valueIndex = 0;
              hasToBeDiscarded = false;
            }

          } else {
            if ('0' <= current && current <= '9') {
              strValue[charOffset++] = current;
            } else if (current == '\n' || current == ';') {
              strValue[charOffset] = '\0';
              charOffset = 0;
              buffer[valueIndex++] = atoi(strValue);

              if (current == '\n' || valueIndex > 129) {
                valueIndex = 0;

                if (valueIndex > 129) {
                  log_warn("Buffer overflow, \\n missed, discarding next");
                  hasToBeDiscarded = true;
                }
              }

            } else {
              log_warn("Unexpected char %d [%c]\n", current, current);
            }
          }
        }
      } else {
        if (errno != EAGAIN) {
          log_error("read: %s", strerror(errno));
        }
      }
    } else if (ret < 0) {
      log_error("polling %d: %s\n", fdCavaInput, strerror(errno));
    }
  }

  stop_cava_process();
  close(fdCavaInput);

  return NULL;
}

static int start_cava_process() {
  int fdCavaPipe[2];

  if (pipe(fdCavaPipe) < 0) {
    log_error("Cava pipe failure: %s", strerror(errno));
    return -EXIT_FAILURE;
  }

  if ((cavaPid = fork()) < 0) {
    log_error("Cava fork failure: %s", strerror(errno));
    exit(-EXIT_FAILURE);
  }

  if (cavaPid == 0) {
    /* Child process*/
    // int fdLogOut;
    char *args[] = {"cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};

    /* Close reading end of the pipe */
    close(fdCavaPipe[0]);
    /* Dup writing end of the pipe in place of stdout */
    dup2(fdCavaPipe[1], STDOUT_FILENO);
    /* Close writing end of the pipe */
    close(fdCavaPipe[1]);

    /* Open / create a log file for cava */
    // fdLogOut = open("/dev/null", O_WRONLY, NULL);
    /* Dup file in place of stderr */
    // dup2(fdLogOut, STDERR_FILENO);

    execvp(args[0], args);
    log_error("Cava execvp failure or return: %s", strerror(errno));
    exit(-EXIT_FAILURE);
  } else {
    int flags;
    /* Close writing end of the pipe */
    close(fdCavaPipe[1]);
    /* Set reading end of the pipe non-blocking */
    flags = fcntl(fdCavaPipe[0], F_GETFL, 0);
    fcntl(fdCavaPipe[0], F_SETFL, flags | O_NONBLOCK);
    /* Return reading end of the pipe */
    return fdCavaPipe[0];
  }

  /* Unreachable  */
  return -1;
}

static void stop_cava_process() {
  kill(cavaPid, SIGTERM);
  waitpid(0, NULL, WNOHANG);
}
