/*

  canbus.c -

  Part of grblHAL

  Copyright (c) 2022 Jon Escombe
  Copyright (c) 2024-2025 Terje Io

  grblHAL 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.

  grblHAL 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 grblHAL. If not, see <http://www.gnu.org/licenses/>.

*/

#include "hal.h"
#include "task.h"
#include "protocol.h"
#include "canbus.h"

#ifndef CANBUS_BUFFER_LEN
#define CANBUS_BUFFER_LEN 8
#endif
#ifndef CANBUS_BAUDRATE
#define CANBUS_BAUDRATE 0  // 125000
#endif

typedef struct {
    volatile canbus_message_t message;
    can_rx_ptr callback;
} canbus_rx_message;

typedef struct {
    volatile canbus_message_t message;
    bool ext_id;
} canbus_tx_message;

typedef struct {
    volatile uint8_t head;
    volatile uint8_t tail;
    volatile canbus_tx_message tx[CANBUS_BUFFER_LEN];
} canbus_tx_buffer_t;

typedef struct {
    volatile uint8_t head;
    volatile uint8_t tail;
    volatile canbus_rx_message rx[CANBUS_BUFFER_LEN];
} canbus_rx_buffer_t;

static bool isEnabled = false;
static const uint32_t baud[] = { 125000, 250000, 500000, 1000000 };
static canbus_tx_buffer_t tx_buffer = {0};
static canbus_rx_buffer_t rx_buffer = {0};

// Weak implementations of low level functions to be provided by the driver

__attribute__((weak)) bool can_start (uint32_t baud, can_rx_enqueue_fn callback)
{
    return false;
}

__attribute__((weak)) bool can_stop (void)
{
    return false;
}

__attribute__((weak)) bool can_set_baud (uint32_t baud)
{
    return false;
}

__attribute__((weak)) bool can_put (canbus_message_t msg, bool ext_id)
{
    return false;
}

__attribute__((weak)) bool can_add_filter (uint32_t id, uint32_t mask, bool ext_id, can_rx_ptr callback)
{
    return false;
}

// ---

ISR_CODE static bool ISR_FUNC(canbus_queue_rx)(canbus_message_t message, can_rx_ptr callback)
{
    bool ok;
    uint8_t next_head = (rx_buffer.head + 1) % CANBUS_BUFFER_LEN;

    if((ok = next_head != rx_buffer.tail)) {
        rx_buffer.rx[rx_buffer.head].callback = callback;
        rx_buffer.rx[rx_buffer.head].message = message;

        rx_buffer.head = next_head;
    }

    return ok;
}

// called every 1 ms
static void canbus_poll (void *data)
{
    /* if have TX data, sends one message per iteration.. */
    if(tx_buffer.head != tx_buffer.tail && can_put(tx_buffer.tx[tx_buffer.tail].message, tx_buffer.tx[tx_buffer.tail].ext_id))
        tx_buffer.tail = (tx_buffer.tail + 1) % CANBUS_BUFFER_LEN;

    /* if have RX data, process one message per iteration.. */
    if(rx_buffer.head != rx_buffer.tail) {
        if(rx_buffer.rx[rx_buffer.tail].callback)
            rx_buffer.rx[rx_buffer.tail].callback(rx_buffer.rx[rx_buffer.tail].message);
        rx_buffer.tail = (rx_buffer.tail + 1) % CANBUS_BUFFER_LEN;
    }
}

static bool canbus_start (uint32_t baud)
{
    if((isEnabled = can_start(baud, canbus_queue_rx)))
        task_add_systick(canbus_poll, NULL);

    return isEnabled;
}

static status_code_t canbus_set_baud (setting_id_t id, uint_fast16_t value)
{
    settings.canbus_baud = value;

    return can_set_baud(baud[settings.canbus_baud]) ? Status_OK : Status_SettingValueOutOfRange;
}

static uint32_t canbus_get_baud (setting_id_t setting)
{
    return settings.canbus_baud < (sizeof(baud) / sizeof(uint32_t)) ? settings.canbus_baud : CANBUS_BAUDRATE;
}

static const setting_group_detail_t canbus_groups [] = {
    { Group_Root, Group_CANbus, "CAN bus"}
};

static const setting_detail_t canbus_setting_detail[] = {
    { Setting_CANbus_BaudRate, Group_CANbus, "CAN bus baud rate", NULL, Format_RadioButtons, "125000,250000,500000,1000000", NULL, NULL, Setting_NonCoreFn, canbus_set_baud, canbus_get_baud, NULL },
};

static void canbus_settings_restore (void)
{
    settings.canbus_baud = CANBUS_BAUDRATE;

    settings_write_global();
}

static void canbus_settings_load (void)
{
    canbus_start(baud[canbus_get_baud(Setting_CANbus_BaudRate)]);
}

// Public API

bool canbus_enabled (void)
{
    return isEnabled;
}

bool canbus_queue_tx (canbus_message_t message, bool ext_id)
{
    bool ok;
    uint8_t next_head = (tx_buffer.head + 1) % CANBUS_BUFFER_LEN;

    if((ok = next_head != tx_buffer.tail)) {
        tx_buffer.tx[tx_buffer.head].ext_id = ext_id;
        tx_buffer.tx[tx_buffer.head].message = message;
        tx_buffer.head = next_head;
    }

    return ok;
}

bool canbus_add_filter (uint32_t id, uint32_t mask, bool ext_id, can_rx_ptr callback)
{
    return can_add_filter(id, mask, ext_id, callback);
}

void canbus_init (void)
{
    static setting_details_t setting_details = {
        .is_core = true,
        .groups = canbus_groups,
        .n_groups = sizeof(canbus_groups) / sizeof(setting_group_detail_t),
        .settings = canbus_setting_detail,
        .n_settings = sizeof(canbus_setting_detail) / sizeof(setting_detail_t),
        .save = settings_write_global,
        .load = canbus_settings_load,
        .restore = canbus_settings_restore
    };

    static bool init_ok = false;

    if(!init_ok) {
        init_ok = true;
        settings_register(&setting_details);
    }
}