/*
 * LEIGH:plc
 * Copyright 2021 Lukas Salomon
 * Copyright 2022 Alex Leigh
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/uart.h>
#include <string.h>
#include <strings.h>
#include <esp_log.h>

#include "dmx.h"

// 13 12 39 14

#ifdef BOARD_WESP32
#define DMX1_SERIAL_INPUT_PIN 13  // pin for dmx rx
#define DMX1_SERIAL_OUTPUT_PIN 12 // pin for dmx tx
#define DMX1_UART_NUM UART_NUM_1  // dmx uart
#define DMX2_SERIAL_INPUT_PIN 39  // pin for dmx rx
#define DMX2_SERIAL_OUTPUT_PIN 14 // pin for dmx tx
#define DMX2_UART_NUM UART_NUM_2  // dmx uart
#elif BOARD_ESPRESSIF_DEV
// These are wrong
#define DMX_SERIAL_INPUT_PIN 13  // pin for dmx rx
#define DMX_SERIAL_OUTPUT_PIN 12 // pin for dmx tx
#define DMX_UART_NUM UART_NUM_1  // dmx uart
#endif

#define HEALTHY_TIME 500          // timeout in ms
#define BUF_SIZE 1024             //  buffer size for rx events
#define DMX_CORE 1                // select the core the rx/tx thread should run on
#define DMX_IGNORE_THREADSAFETY 1 // set to 1 to disable all threadsafe mechanisms

// FIXME: Not globals!

// DMX1: A Universe
// DMX2: B Universe

static uint8_t dmx_data[2][513];
static long last_dmx_packet1;
static long last_dmx_packet2;
static QueueHandle_t dmxGqueue1;
static QueueHandle_t dmxGqueue2;

static void dmxFuart_send_task();

static const char *TAG = "dmx";

void dmxFinit()
{
    ESP_LOGI(TAG, "dmxFinit(): Initializing DMX...");
    // configure UART for DMX
    uart_config_t uart_config =
        {
            .baud_rate = 250000,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_2,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
            .source_clk = UART_SCLK_DEFAULT};

    ESP_ERROR_CHECK(uart_param_config(DMX1_UART_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_param_config(DMX2_UART_NUM, &uart_config));

    // Set pins for UART
    ESP_ERROR_CHECK(uart_set_pin(DMX1_UART_NUM, DMX1_SERIAL_OUTPUT_PIN, DMX1_SERIAL_INPUT_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_set_pin(DMX2_UART_NUM, DMX2_SERIAL_OUTPUT_PIN, DMX2_SERIAL_INPUT_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));

    // Install UART driver, and get the queue.
    ESP_ERROR_CHECK(uart_driver_install(DMX1_UART_NUM, 1024 * 2, 1024 * 2, 20, &dmxGqueue1, 0));
    ESP_ERROR_CHECK(uart_driver_install(DMX2_UART_NUM, 1024 * 2, 1024 * 2, 20, &dmxGqueue2, 0));

    // create send task
    xTaskCreatePinnedToCore(dmxFuart_send_task, "dmxFuart_send_task", 4999, NULL, 1, NULL, DMX_CORE);
}

/**
 * @brief Read a DMX channel.
 *
 * Return the value for the given DMX channel, which must be  between 1 and 512.
 *
 * @param channel
 * @return uint8_t
 */
uint8_t dmxFread(size_t universe, uint16_t channel)
{
    // restrict acces to dmx array to valid values
    if (channel < 1 || channel > 512)
    {
        return 0;
    }

    // take data threadsafe from array and return
    uint8_t tmp_dmx = dmx_data[universe][channel];
    return tmp_dmx;
}

/**
 * @brief Read a range of DMX channels.
 *
 * Read <code>size</code> channels of DMX starting with <start> into the provided
 * buffer.
 *
 * @param data
 * @param start
 * @param size
 */
void dmxFread_all(size_t universe, uint8_t *data, uint16_t start, size_t size)
{
    // restrict acces to dmx array to valid values
    if (start < 1 || start > 512 || start + size > 513)
    {
        return;
    }
    memcpy(data, (uint8_t *)dmx_data[universe] + start, size);
}

/**
 * @brief Update the given DMX channel.
 *
 * Update the given DMX channel with the provided value. The channel must be between
 * 1 and 512.
 *
 * @param channel
 * @param value
 */
void dmxFwrite(size_t universe, uint16_t channel, uint8_t value)
{
    // restrict acces to dmx array to valid values
    if (channel < 1 || channel > 512)
    {
        return;
    }

    dmx_data[universe][channel] = value;
}

void dmxFwrite_all(size_t universe, const uint8_t *data, uint16_t start, size_t size)
{
    // restrict acces to dmx array to valid values
    if (start < 1 || start > 512 || start + size > 513)
    {
        return;
    }
    memcpy((uint8_t *)dmx_data[universe] + start, data + start, size);
}

/**
 * @brief Determine if the DMX data is healthy.
 *
 * Returns 1 if the last DMX frame received was received within the timeout period.
 *
 * @return uint8_t
 */

uint8_t dmxFis_healthy()
{
    // get timestamp of last received packet
    long dmx_timeout = last_dmx_packet1;
    // check if elapsed time < defined timeout
    if (xTaskGetTickCount() - dmx_timeout < HEALTHY_TIME)
    {
        return 1;
    }
    return 0;
}

static void dmxFuart_send_task()
{
    uint8_t start_code = 0x00;
    for (;;)
    {
        // wait till uart is ready
        uart_wait_tx_done(DMX1_UART_NUM, 1000);
        // set line to inverse, creates break signal
        uart_set_line_inverse(DMX1_UART_NUM, UART_SIGNAL_TXD_INV);
        // wait break time
        esp_rom_delay_us(184);
        // disable break signal
        uart_set_line_inverse(DMX1_UART_NUM, 0);
        // wait mark after break
        esp_rom_delay_us(24);
        // write start code
        uart_write_bytes(DMX1_UART_NUM, (const char *)&start_code, 1);
        // transmit the dmx data
        uart_write_bytes(DMX1_UART_NUM, (const char *)dmx_data[0] + 1, 512);
    }
}
