/*
 * This file is part of the MicroPython project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2024-2026 Angus Gratton
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

// This file is never compiled standalone, it's included directly from
// extmod/machine_can.c via MICROPY_PY_MACHINE_CAN_INCLUDEFILE.
#include <stdbool.h>
#include "extmod/machine_can_port.h"
#include "can.h"
#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "py/gc.h"

#if MICROPY_HW_ENABLE_FDCAN
#define CAN_BRP_MIN 1
#define CAN_BRP_MAX 512
#define CAN_FD_BRS_BRP_MIN 1
#define CAN_FD_BRS_BRP_MAX 32
#define CAN_FILTERS_STD_EXT_SEPARATE 1

#else // Classic bxCAN
#define CAN_BRP_MIN 1
#define CAN_BRP_MAX 1024
#define CAN_FILTERS_STD_EXT_SEPARATE 0
#endif

#define TX_EMPTY UINT32_MAX

struct machine_can_port {
    CAN_HandleTypeDef h;
    uint32_t tx[CAN_TX_QUEUE_LEN];  // ID stored in each hardware tx buffer, or TX_EMPTY if empty
    bool irq_state_pending;
    bool error_passive;
};

// Convert the port agnostic CAN mode to the ST mode
static uint32_t can_port_mode(machine_can_mode_t mode) {
    switch (mode) {
        case MP_CAN_MODE_NORMAL:
            return CAN_MODE_NORMAL;
        case MP_CAN_MODE_SLEEP:
            return CAN_MODE_SILENT; // Sleep is not an operating mode for ST's peripheral
        case MP_CAN_MODE_LOOPBACK:
            return CAN_MODE_LOOPBACK;
        case MP_CAN_MODE_SILENT:
            return CAN_MODE_SILENT;
        case MP_CAN_MODE_SILENT_LOOPBACK:
            return CAN_MODE_SILENT_LOOPBACK;
        default:
            assert(0); // Mode should have been checked already
            return CAN_MODE_NORMAL;
    }
}

static int machine_can_port_f_clock(const machine_can_obj_t *self) {
    return (int)can_get_source_freq();
}

static bool machine_can_port_supports_mode(const machine_can_obj_t *self, machine_can_mode_t mode) {
    return mode < MP_CAN_MODE_MAX;
}

static mp_uint_t machine_can_port_max_data_len(mp_uint_t flags) {
    #if MICROPY_HW_ENABLE_FDCAN
    if (flags & CAN_MSG_FLAG_FD_F) {
        return 64;
    }
    #endif
    return 8;
}

static void machine_can_port_init(machine_can_obj_t *self) {
    if (!self->port) {
        self->port = m_new(struct machine_can_port, 1);
    }
    memset(self->port, 0, sizeof(struct machine_can_port));
    for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
        self->port->tx[i] = TX_EMPTY;
    }

    bool res = can_init(&self->port->h,
        self->can_idx + 1, // Convert 0-based index to 1-based 'can_id' for lower layer
        CAN_TX_QUEUE,
        can_port_mode(self->mode),
        self->brp,
        self->sjw,
        self->tseg1,
        self->tseg2,
        false); // auto_restart not currently exposed

    if (!res) {
        mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("CAN init failed"));
    }
}

static void machine_can_port_cancel_all_tx(machine_can_obj_t *self) {
    struct machine_can_port *port = self->port;
    can_disable_tx_interrupts(&port->h);
    for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
        can_cancel_transmit(&port->h, i);
        port->tx[i] = TX_EMPTY;
    }
}

static void machine_can_port_deinit(machine_can_obj_t *self) {
    machine_can_port_cancel_all_tx(self);
    can_deinit(&self->port->h);
}

static mp_int_t machine_can_port_send(machine_can_obj_t *self, mp_uint_t id, const byte *data, size_t data_len, mp_uint_t flags) {
    int idx_empty = -1; // Empty transmit buffer, where no later index has the same ID message in it

    // Scan through the current transmit queue to find an eligible buffer for transmit
    for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
        uint32_t tx_id = self->port->tx[i];
        if (tx_id == TX_EMPTY) {
            // This slot is empty
            if (idx_empty == -1) {
                // Still have to keep scanning as we might see a later message with the same ID,
                idx_empty = i;
            }
        } else if (tx_id == id && !(flags & CAN_MSG_FLAG_UNORDERED)) {
            // Can't queue a second message with the same ID and guarantee order

            // (Undocumented hardware limitation - CANFD reference suggests
            // messages with the same ID are sent in buffer index order but
            // testing shows not always the case at least on STM32H7! Unsure if
            // also a limitation of bxCAN or STM32G4, but these only have 3 TX
            // buffers so inserting in buffer index order is likely to run out
            // of buffers relatively quickly anyway...)

            // Note: currently the driver considers a Standard and an Extended
            // ID with the same numeric value to be the same ID... could fix
            // this, although it's a relatively uncommon case.
            return -1;
        }
    }

    if (idx_empty == -1) {
        // No space in transmit queue
        return -1;
    }

    CanTxMsgTypeDef tx = {
        #if MICROPY_HW_ENABLE_FDCAN
        .MessageMarker = 0,
        .ErrorStateIndicator = FDCAN_ESI_ACTIVE,
        .TxEventFifoControl = FDCAN_NO_TX_EVENTS,
        .Identifier = id, // Range checked by caller
        .IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID,
        .TxFrameType = (flags & CAN_MSG_FLAG_RTR) ? FDCAN_REMOTE_FRAME : FDCAN_DATA_FRAME,
        .FDFormat = (flags & CAN_MSG_FLAG_FD_F) ? FDCAN_FD_CAN : FDCAN_CLASSIC_CAN,
        .BitRateSwitch = (flags & CAN_MSG_FLAG_BRS) ? FDCAN_BRS_ON : FDCAN_BRS_OFF,
        .DataLength = data_len, // Converted inside can_transmit_buf_index
        #else // Classic
        .StdId = (flags & CAN_MSG_FLAG_EXT_ID) ? 0 : id,
        .ExtId = (flags & CAN_MSG_FLAG_EXT_ID) ? id : 0,
        .IDE = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_ID_EXT : CAN_ID_STD,
        .RTR = (flags & CAN_MSG_FLAG_RTR) ? CAN_RTR_REMOTE : CAN_RTR_DATA,
        .DLC = data_len,
        #endif
    };
    #if !MICROPY_HW_ENABLE_FDCAN
    assert(data_len <= sizeof(tx.Data)); // Also checked by caller
    memcpy(tx.Data, data, data_len);
    #endif

    HAL_StatusTypeDef err = can_transmit_buf_index(&self->port->h, idx_empty, &tx, data);
    if (err != HAL_OK) {
        return -1;
    }
    self->port->tx[idx_empty] = id;

    return idx_empty;
}

static bool machine_can_port_cancel_send(machine_can_obj_t *self, mp_uint_t idx) {
    return can_cancel_transmit(&self->port->h, idx);
}

static bool machine_can_port_recv(machine_can_obj_t *self, void *data, size_t *dlen, mp_uint_t *id, mp_uint_t *flags, mp_uint_t *errors) {
    CAN_HandleTypeDef *can = &self->port->h;
    CanRxMsgTypeDef msg;

    for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
        if (can_receive(can, fifo, &msg, data, 0) == 0) {
            // CanRxMsgTypeDef is different for Classic vs FD
            #if MICROPY_HW_ENABLE_FDCAN
            *flags = ((msg.IdType == FDCAN_EXTENDED_ID) ? CAN_MSG_FLAG_EXT_ID : 0) |
                ((msg.RxFrameType == FDCAN_REMOTE_FRAME) ? CAN_MSG_FLAG_RTR : 0);
            *id = msg.Identifier;
            *dlen = msg.DataLength; // Lower layer has converted to bytes already
            #else
            *flags = (msg.IDE ? CAN_MSG_FLAG_EXT_ID : 0) |
                (msg.RTR ? CAN_MSG_FLAG_RTR : 0);
            *id = msg.IDE ? msg.ExtId : msg.StdId;
            *dlen = msg.DLC;
            #endif

            *errors = self->rx_error_flags;
            self->rx_error_flags = 0;

            // Re-enable any interrupts that were disabled in RX IRQ handlers
            can_enable_rx_interrupts(can, fifo, self->mp_irq_trigger & MP_CAN_IRQ_RX);

            return true;
        }
    }
    return false;
}

static void machine_can_update_irqs(machine_can_obj_t *self) {
    uint16_t triggers = self->mp_irq_trigger;

    for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
        if (triggers & MP_CAN_IRQ_RX) {
            can_enable_rx_interrupts(&self->port->h, fifo, true);
        } else {
            can_disable_rx_interrupts(&self->port->h, fifo);
        }
    }

    // Note: TX complete interrupt is always enabled to manage the internal queue state
}

static void machine_can_port_clear_filters(machine_can_obj_t *self) {
    #if MICROPY_HW_ENABLE_FDCAN
    for (int f = 0; f < CAN_HW_MAX_STD_FILTER; f++) {
        can_clearfilter(&self->port->h, f, false);
    }
    for (int f = 0; f < CAN_HW_MAX_EXT_FILTER; f++) {
        can_clearfilter(&self->port->h, f, true);
    }
    #else
    int bank_offs = (self->can_idx == 1) ? CAN_HW_MAX_FILTER : 0; // CAN2 filters index after CAN1
    for (int f = 0; f < CAN_HW_MAX_FILTER; f++) {
        can_clearfilter(&self->port->h, f + bank_offs, CAN_HW_MAX_FILTER);
    }
    #endif
}

#if MICROPY_HW_ENABLE_FDCAN
static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) {
    int max_idx = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_HW_MAX_EXT_FILTER : CAN_HW_MAX_STD_FILTER;
    if (filter_idx >= max_idx) {
        mp_raise_ValueError(MP_ERROR_TEXT("too many filters for this ID type"));
    }
    if (flags & ~CAN_MSG_FLAG_EXT_ID) {
        mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID
    }

    FDCAN_FilterTypeDef filter = {
        .IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID,
        // FDCAN counts standard and extended id filters separately, but this is
        // already accounted for in filter_idx due to CAN_FILTERS_STD_EXT_SEPARATE.
        .FilterIndex = filter_idx,
        .FilterType = FDCAN_FILTER_MASK,
        // Round-robin between FIFO1 and FIFO0
        .FilterConfig = (filter_idx & 1) ? FDCAN_FILTER_TO_RXFIFO1 : FDCAN_FILTER_TO_RXFIFO0,
        .FilterID1 = can_id,
        .FilterID2 = mask,
    };

    int r = HAL_FDCAN_ConfigFilter(&self->port->h, &filter);
    assert(r == HAL_OK);
    (void)r;
}
#else
static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) {
    if (filter_idx >= CAN_HW_MAX_FILTER) {
        mp_raise_ValueError(MP_ERROR_TEXT("too many filters"));
    }
    if (flags & ~CAN_MSG_FLAG_EXT_ID) {
        mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID
    }

    if (self->can_idx == 1) {
        filter_idx += CAN_HW_MAX_FILTER;  // CAN2 filters index after CAN1
    }

    CAN_FilterConfTypeDef filter = {
        .FilterActivation = ENABLE,
        .FilterScale = CAN_FILTERSCALE_32BIT,
        .FilterMode = CAN_FILTERMODE_IDMASK,
        .FilterNumber = filter_idx,
        // Apply the filters round-robin to each FIFO, as each filter in bxCAN is
        // associated with only one FIFO.
        .FilterFIFOAssignment = filter_idx % 2,
        .BankNumber = CAN_HW_MAX_FILTER, // Assign same number of filters to CAN2 as CAN1
    };

    // This somewhat corresponds to STM32 RM Figure 342 "Filter bank scale
    // configuration", although the Reference Manual makes 32-bit mask filters look
    // a lot more complex than they are, then the ST HAL makes it even more
    // complex by only supporting filter configuration via 16-bit halfwords
    // which are re-assembled to full words inside the HAL...
    if (flags & CAN_MSG_FLAG_EXT_ID) {
        filter.FilterIdLow = (can_id << 3) | CAN_ID_EXT;
        filter.FilterIdHigh = can_id >> 13;
        filter.FilterMaskIdLow = (mask << 3) | CAN_ID_EXT;
        filter.FilterMaskIdHigh = mask >> 13;
    } else {
        filter.FilterIdLow = 0;
        filter.FilterIdHigh = can_id << 5;
        filter.FilterMaskIdLow = CAN_ID_EXT; // Set to require CAN_ID_EXT unset in message
        filter.FilterMaskIdHigh = mask << 5;
    }

    int r = HAL_CAN_ConfigFilter(&self->port->h, &filter);
    assert(r == HAL_OK); // Params should be verified before passing to HAL
    (void)r;
}
#endif // MICROPY_HW_ENABLE_FDCAN

static machine_can_state_t machine_can_port_get_state(machine_can_obj_t *self) {
    // machine_can_port.h defines MP_CAN_STATE_xxx enums, verify they all match
    // numerically with stm32 can.h CAN_STATE_xxx enums
    MP_STATIC_ASSERT((int)MP_CAN_STATE_STOPPED == (int)CAN_STATE_STOPPED);
    MP_STATIC_ASSERT((int)MP_CAN_STATE_ACTIVE == (int)CAN_STATE_ERROR_ACTIVE);
    MP_STATIC_ASSERT((int)MP_CAN_STATE_WARNING == (int)CAN_STATE_ERROR_WARNING);
    MP_STATIC_ASSERT((int)MP_CAN_STATE_PASSIVE == (int)CAN_STATE_ERROR_PASSIVE);
    MP_STATIC_ASSERT((int)MP_CAN_STATE_BUS_OFF == (int)CAN_STATE_BUS_OFF);
    return (machine_can_state_t)can_get_state(&self->port->h);
}

static void machine_can_port_update_counters(machine_can_obj_t *self) {
    can_counters_t hw_counters;
    struct machine_can_port *port = self->port;
    machine_can_counters_t *counters = &self->counters;

    can_get_counters(&port->h, &hw_counters);

    counters->tec = hw_counters.tec;
    counters->rec = hw_counters.rec;
    counters->tx_pending = hw_counters.tx_pending;
    counters->rx_pending = hw_counters.rx_fifo0_pending + hw_counters.rx_fifo1_pending;

    // Other fields in 'counters' are updated from ISR directly
}

static mp_obj_t machine_can_port_get_additional_timings(machine_can_obj_t *self, mp_obj_t optional_arg) {
    return mp_const_none;
}

static void machine_can_port_restart(machine_can_obj_t *self) {
    // extmod layer has already checked CAN is initialised
    struct machine_can_port *port = self->port;
    machine_can_port_cancel_all_tx(self);
    can_restart(&port->h);
    port->irq_state_pending = false;
}

static bool clear_complete_transfer(machine_can_obj_t *self, int *index, bool *is_success) {
    *index = can_get_transmit_finished(&self->port->h, is_success);
    if (*index == -1) {
        return false;
    }
    self->port->tx[*index] = TX_EMPTY;

    return true;
}

static mp_uint_t machine_can_port_irq_flags(machine_can_obj_t *self) {
    mp_uint_t flags = 0;
    CAN_HandleTypeDef *can = &self->port->h;

    if (self->mp_irq_trigger & MP_CAN_IRQ_STATE && self->port->irq_state_pending) {
        flags |= MP_CAN_IRQ_STATE;
        self->port->irq_state_pending = false;
    }

    // Check for RX
    if (self->mp_irq_trigger & MP_CAN_IRQ_RX) {
        for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
            if (can_is_rx_pending(can, fifo)) {
                flags |= MP_CAN_IRQ_RX;
            }
        }
    }

    // Check for TX done
    if (self->mp_irq_trigger & MP_CAN_IRQ_TX) {
        bool is_success = false;
        int index;
        if (clear_complete_transfer(self, &index, &is_success)) {
            flags |= (mp_uint_t)(index << MP_CAN_IRQ_IDX_SHIFT) | MP_CAN_IRQ_TX;
            if (!is_success) {
                flags |= MP_CAN_IRQ_TX_FAILED;
            }
        }
    }

    return flags;
}

void machine_can_irq_handler(uint can_id,  can_int_t interrupt) {
    assert(can_id > 0);
    machine_can_obj_t *self = MP_STATE_PORT(machine_can_objs)[can_id - 1];
    if (self == NULL) {
        return; // Should only hit this code path if pyb.CAN has enabled interrupt
    }
    struct machine_can_port *port = self->port;
    machine_can_counters_t *counters = &self->counters;
    bool call_irq = false;
    bool irq_state = false;

    switch (interrupt) {
        // RX
        case CAN_INT_FIFO_FULL:
            self->rx_error_flags |= CAN_RECV_ERR_FULL;
            break;
        case CAN_INT_FIFO_OVERFLOW:
            self->rx_error_flags |= CAN_RECV_ERR_OVERRUN;
            counters->rx_overruns++;
            break;
        case CAN_INT_MESSAGE_RECEIVED:
            call_irq = call_irq || (self->mp_irq_trigger & MP_CAN_IRQ_RX);
            break;

        // Error states
        case CAN_INT_ERR_WARNING:
            if (!port->error_passive) {
                // Only count entering warning state, not leaving it
                counters->num_warning++;
                irq_state = true;
            }
            port->error_passive = false;
            break;
        case CAN_INT_ERR_PASSIVE:
            counters->num_passive++;
            port->error_passive = true;
            irq_state = true;
            break;
        case CAN_INT_ERR_BUS_OFF:
            counters->num_bus_off++;
            irq_state = true;
            port->error_passive = false;
            break;

        // TX
        case CAN_INT_TX_COMPLETE:
            if (!(self->mp_irq_trigger & MP_CAN_IRQ_TX)) {
                // No TX IRQ, so mark this buffer as free and move on
                int index;
                bool is_success = false;
                clear_complete_transfer(self, &index, &is_success);
            } else {
                // Otherwise, the slot is marked empty after the irq calls flags()
                call_irq = true;
            }
            break;

        default:
            assert(0); // Should be unreachable
    }

    if (irq_state && (self->mp_irq_trigger & MP_CAN_IRQ_STATE)) {
        self->port->irq_state_pending = true;
        call_irq = true;
    }

    if (call_irq) {
        assert(self->mp_irq_obj != NULL); // Can't set mp_irq_trigger otherwise
        mp_irq_handler(self->mp_irq_obj);
    }
}
