
/* UART asynchronous example, that uses separate RX and TX tasks
 */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "string.h"
#include "driver/gpio.h"

#include "nanopb/pb_decode.h"
#include "nanopb/pb_encode.h"
#include "nanopb/pb_common.h"

#include "LoToHi.pb.h"
#include "HiToLo.pb.h"
// #include "inc/version_autogen.h"
#include "sync_master.h"
#include "evse_api.h"
#include "evse_error.h"
#include "evse_state.h"
#include "evse_config.h"

#define VERSION_STRING "2.2"

static const int RX_BUF_SIZE = 1024;

#define TXD_PIN (GPIO_NUM_27)
#define RXD_PIN (GPIO_NUM_26)

static uint8_t msg[2048];
static uint8_t code;
static uint8_t block;
static uint8_t *decode;

static const char *TAG = "MASTER_TASK";

void cobsDecodeReset()
{
    code = 0xff;
    block = 0;
    decode = msg;
}

void handlePacket(uint8_t *buf, int len);

void init(void)
{
    const uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    // We won't use a buffer for sending data.
    uart_driver_install(UART_NUM_2, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_param_config(UART_NUM_2, &uart_config);
    uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    cobsDecodeReset();
}

uint32_t crc32(uint8_t *buf, int len)
{
    int i, j;
    uint32_t b, crc, msk;

    i = 0;
    crc = 0xFFFFFFFF;
    while (i < len)
    {
        b = buf[i];
        crc = crc ^ b;
        for (j = 7; j >= 0; j--)
        {
            msk = -(crc & 1);
            crc = (crc >> 1) ^ (0xEDB88320 & msk);
        }
        i = i + 1;
    }
    // printf("%X",crc);
    return crc;
}

void cobsDecodeByte(uint8_t byte)
{

    // ESP_LOGI("RX_TASK", "init cobsDecodeByte");
    // ESP_LOGI("RX_TASK", "Read bytes: '%02X'", byte);

    // check max length
    if ((decode - msg == 2048 - 1) && byte != 0x00)
    {
        ESP_LOGI(TAG, "cobsDecode: Buffer overflow");
        cobsDecodeReset();
    }

    if (block)
    {
        // ESP_LOGI("RX_TASK", "block");
        //  we're currently decoding and should not get a 0
        if (byte == 0x00)
        {
            // probably found some garbage -> reset
            ESP_LOGI("RX_TASK", "cobsDecode: Garbage detected");
            cobsDecodeReset();
            return;
        }
        *decode++ = byte;
    }
    else
    {
        // ESP_LOGI("RX_TASK", "not block");
        if (code != 0xff)
        {
            *decode++ = 0;
        }
        block = code = byte;
        if (code == 0x00)
        {
            // we're finished, reset everything and commit
            if (decode == msg)
            {
                // we received nothing, just a 0x00
                ESP_LOGI("RX_TASK", "cobsDecode: Received nothing");
            }
            else
            {
                // set back decode with one, as it gets post-incremented
                handlePacket(msg, decode - 1 - msg);
            }
            cobsDecodeReset();
            return; // need to return here, because of block--
        }
    }
    block--;
}

void cobsDecode(uint8_t *buf, int len)
{
    for (int i = 0; i < len; i++)
    {
        cobsDecodeByte(buf[i]);
    }
}

size_t cobsEncode(const void *data, size_t length, uint8_t *buffer)
{
    uint8_t *encode = buffer;  // Encoded byte pointer
    uint8_t *codep = encode++; // Output code pointer
    uint8_t code = 1;          // Code value

    for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte)
    {
        if (*byte) // Byte not zero, write it
            *encode++ = *byte, ++code;

        if (!*byte || code == 0xff) // Input is zero or block completed, restart
        {
            *codep = code, code = 1, codep = encode;
            if (!*byte || length)
                ++encode;
        }
    }
    *codep = code; // Write final code value

    // add final 0
    *encode++ = 0x00;

    return encode - buffer;
}

int linkWrite(HiToLo *m)
{

    ESP_LOGI(TAG, "linkWrite");
    uint8_t tx_packet_buf[200];
    uint8_t encode_buf[208];
    pb_ostream_t ostream = pb_ostream_from_buffer(tx_packet_buf, sizeof(tx_packet_buf) - 4);
    bool status = pb_encode(&ostream, HiToLo_fields, m);

    if (!status)
    {
        // couldn't encode
        return false;
    }

    size_t tx_payload_len = ostream.bytes_written;

    // add crc32 (CRC-32/JAMCRC)
    uint32_t crc = crc32(tx_packet_buf, tx_payload_len);

    for (int byte_pos = 0; byte_pos < 4; ++byte_pos)
    {
        tx_packet_buf[tx_payload_len] = (uint8_t)crc & 0xFF;
        crc = crc >> 8;
        tx_payload_len++;
    }
    size_t tx_encode_len = cobsEncode(tx_packet_buf, tx_payload_len, encode_buf);
    const int txBytes = uart_write_bytes(UART_NUM_2, encode_buf, tx_encode_len);
    return txBytes;
}

void send_time_stamp()
{
    ESP_LOGI(TAG, "send_time_stamp");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_time_stamp_tag;
    msg_out.payload.time_stamp = esp_timer_get_time() / 1000;
    linkWrite(&msg_out);
    printf("time stamp sent %i.\n", (int)msg_out.payload.time_stamp);
}

void send_connector_lock(bool on)
{
    ESP_LOGI(TAG, "connector_lock");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_connector_lock_tag;
    msg_out.payload.connector_lock = on;
    linkWrite(&msg_out);
}

void send_max_charging_current(uint32_t max_charging_current)
{
    ESP_LOGI(TAG, "send_max_charging_current");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_max_charging_current_tag;
    msg_out.payload.max_charging_current = max_charging_current;
    linkWrite(&msg_out);
}

void send_allow_power_on(bool allow_power_on)
{
    ESP_LOGI(TAG, "allow_power_on");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_allow_power_on_tag;
    msg_out.payload.allow_power_on = allow_power_on;
    linkWrite(&msg_out);
}

void send_reset(bool reset)
{
    ESP_LOGI(TAG, "reset");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_reset_tag;
    msg_out.payload.reset = reset;
    linkWrite(&msg_out);
}

void send_grid_current(uint32_t grid_current)
{
    ESP_LOGI(TAG, "send_grid_current");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_grid_current_tag;
    msg_out.payload.grid_current = grid_current;
    linkWrite(&msg_out);
}

void send_max_grid_current(uint32_t max_grid_current)
{
    ESP_LOGI(TAG, "send_max_grid_current");
    HiToLo msg_out = HiToLo_init_default;
    msg_out.which_payload = HiToLo_max_grid_current_tag;
    msg_out.payload.max_grid_current = max_grid_current;
    linkWrite(&msg_out);
}
/*
const evse_state_t cp_state_to_evse_state(CpState cp_state)
{
    switch (state)
    {
    case CpState_EVSE_STATE_A:
        return EVSE_STATE_A;
    case CpState_EVSE_STATE_B1:
        return EVSE_STATE_B1;
    case CpState_EVSE_STATE_B2:
        return EVSE_STATE_B2;
    case CpState_EVSE_STATE_C1:
        return EVSE_STATE_C1;
    case CpState_EVSE_STATE_C2:
        return EVSE_STATE_C2;
    case CpState_EVSE_STATE_D1:
        return EVSE_STATE_D1;
    case CpState_EVSE_STATE_D2:
        return EVSE_STATE_D2;
    case CpState_EVSE_STATE_E:
        return EVSE_STATE_E;
    case CpState_EVSE_STATE_F:
        return EVSE_STATE_F;
    default:
        return EVSE_STATE_F;
    }
}
*/

void handlePacket(uint8_t *buf, int len)
{
    // printf ("packet received len %u\n", len);

    // Check CRC32 (last 4 bytes)
    //  uint32_t crc = calculateCrc(rx_packet_buf, rx_packet_len);
    if (crc32(buf, len))
    {
        printf("CRC mismatch\n");
        return;
    }

    len -= 4;

    LoToHi msg_in;

    pb_istream_t istream = pb_istream_from_buffer(buf, len);

    if (pb_decode(&istream, LoToHi_fields, &msg_in))
    {
        //printf("LoToHi recvd %i \n", msg_in.which_payload);

        switch (msg_in.which_payload)
        {
        case LoToHi_time_stamp_tag:
            printf("Received time stamp %i\n", (int)msg_in.payload.time_stamp);
            break;
        case LoToHi_relais_state_tag:
            printf("Received relais_state %i\n", (int)msg_in.payload.relais_state);
            break;
        case LoToHi_error_flags_tag:
            // printf("Received error_flags %i\n", (int)msg_in.payload.error_flags);
            break;
        case LoToHi_cp_state_tag:
            printf("Received cp_state %i\n", (int)msg_in.payload.cp_state);
            // state = cp_state_to_evse_state(msg_in.payload.cp_state);
            break;
        case LoToHi_pp_state_tag:
            printf("Received cp_state %i\n", (int)msg_in.payload.pp_state);
            break;
        case LoToHi_max_charging_current_tag:
            printf("Received max_charging_current %i\n", (int)msg_in.payload.max_charging_current);

            int max_charging_current = (int)msg_in.payload.max_charging_current;

            if (max_charging_current != evse_get_max_charging_current())
            {
                send_max_charging_current(evse_get_max_charging_current());
            }
            break;

        case LoToHi_max_grid_current_tag:
            printf("Received max_grid_current %i\n", (int)msg_in.payload.max_grid_current);

            int max_grid_current = (int)msg_in.payload.max_grid_current;
            /*
            if (max_grid_current !=  grid_get_max_current())
            {
                send_max_grid_current(grid_get_max_current());
            }*/
            break;

        case LoToHi_lock_state_tag:
            printf("Received lock_state %i\n", (int)msg_in.payload.lock_state);
            break;
        case LoToHi_power_meter_tag:
            printf("Received power_meter %i\n", (int)msg_in.payload.power_meter.time_stamp);
            break;

        default:
            printf("Not Found Sync Master recvd %i \n", msg_in.which_payload);
            break;
        }
    }
}

static void tx_task(void *arg)
{

    while (1)
    {

        // send_time_stamp();
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
        // send_connector_lock(true);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);

        send_max_charging_current(32);
        vTaskDelay(1000 / portTICK_PERIOD_MS);

        // send_allow_power_on(true);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
        // send_reset(true);
        // vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

static void rx_task(void *arg)
{
    uint8_t *data = (uint8_t *)malloc(RX_BUF_SIZE + 1);
    while (1)
    {

        const int rxBytes = uart_read_bytes(UART_NUM_2, data, RX_BUF_SIZE, 1000 / portTICK_PERIOD_MS);
        if (rxBytes > 0)
        {
            data[rxBytes] = 0;
            // ESP_LOGI(TAG, "Read %d bytes: '%s'", rxBytes, data);
            // ESP_LOG_BUFFER_HEXDUMP(TAG, data, rxBytes, ESP_LOG_INFO);
            cobsDecode(data, rxBytes);
        }
    }
    free(data);
}

void master_sync_start()
{
    ESP_LOGI(TAG, "Master SYNC Serial");

    init();
    xTaskCreate(rx_task, "uart_rx_task", 1024 * 5, NULL, 3, NULL);
    //xTaskCreate(tx_task, "uart_tx_task", 1024 * 5, NULL, 5, NULL);
}

void master_sync_stop(void)
{
    /*
    ESP_LOGI(TAG, "Stopping");

    if (rx_task)
    {
        vTaskDelete(rx_task);
        rx_task = NULL;
    }

    if (tx_task)
    {
        vTaskDelete(tx_task);
        tx_task = NULL;
    }

    if (port != -1)
    {
        uart_driver_delete(port);
        port = -1;
    }*/
}