/** @file CANvenient_PEAK.c
 *
 *  CANvenient is an abstraction layer for multiple CAN APIs on
 *  Windows and Linux.
 *
 *  Copyright (c) 2026, Michael Fitzmayer. All rights reserved.
 *  SPDX-License-Identifier: MIT
 *
 **/

#include <stddef.h>
#include <stdio.h>
#include <string.h>

#include "CANvenient.h"
#include "CANvenient_internal.h"

#ifdef _WIN32
#include <windows.h>
#include <PCANBasic.h>

static char* lookup_bus_name(DWORD device_type);
static char* lookup_error_string(TPCANStatus pcan_status);
static TPCANBaudrate lookup_pcan_baudrate(enum can_baudrate baud);
#endif

int peak_find_interfaces(void)
{
#ifdef _WIN32

    u32 ch_count = 0;
    TPCANChannelInformation* pcan_ch_info;
    TPCANStatus pcan_status;
    char bus_name[16] = {0};

    /* PEAK-System. */
    pcan_status = CAN_GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS_COUNT, &ch_count, sizeof(u32));
    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
        return -1;
    }

    if (ch_count > CAN_MAX_INTERFACES)
    {
        ch_count = CAN_MAX_INTERFACES;
    }

    pcan_ch_info = (TPCANChannelInformation*)malloc(sizeof(TPCANChannelInformation) * ch_count);
    if (NULL == pcan_ch_info)
    {
        set_error_reason("Memory allocation failed.");
        return -1;
    }

    pcan_status = CAN_GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS, pcan_ch_info, sizeof(TPCANChannelInformation) * ch_count);
    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
        free(pcan_ch_info);
        pcan_ch_info = NULL;
        return -1;
    }

    for (u32 i = 0; i < ch_count; i++)
    {
        size_t name_len;
        u32 free_index;
        u32 k;
        int already_registered = 0;

        snprintf(bus_name, sizeof(bus_name), "%s", lookup_bus_name(pcan_ch_info[i].channel_handle));

        for (k = 0; k < CAN_MAX_INTERFACES; k++)
        {
            if (can_interface[k].name && strcmp(can_interface[k].name, bus_name) == 0)
            {
                already_registered = 1;
                break;
            }
        }
        if (already_registered)
        {
            continue;
        }

        if (0 != find_free_interface_slot(&free_index))
        {
            /* No free slot available: stop. */
            break;
        }

        can_interface[free_index].internal = malloc(sizeof(TPCANChannelInformation));
        if (! can_interface[free_index].internal)
        {
            set_error_reason("Memory allocation failed.");
            free(pcan_ch_info);
            return -1;
        }

        name_len = strnlen(bus_name, sizeof(bus_name));
        can_interface[free_index].name = (char*)malloc(name_len + 1);
        if (NULL == can_interface[free_index].name)
        {
            set_error_reason("Memory allocation failed.");
            free(can_interface[free_index].internal);
            can_interface[free_index].internal = NULL;
            free(pcan_ch_info);
            return -1;
        }

        /* Set interface properties */
        snprintf(can_interface[free_index].name, name_len + 1, "%s", bus_name);
        memcpy(can_interface[free_index].internal, &pcan_ch_info[i], sizeof(TPCANChannelInformation));

        can_interface[free_index].vendor = CAN_VENDOR_PEAK;
        can_interface[free_index].opened = 0;
        can_interface[free_index].baudrate = CAN_BAUD_1M;
    }
    free(pcan_ch_info);

#endif

    return 0;
}

int peak_open(int index)
{
#ifdef _WIN32

    TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle;
    TPCANStatus pcan_status;

    pcan_status = CAN_Initialize(pcan_ch, lookup_pcan_baudrate(can_interface[index].baudrate), 0, 0, 0);
    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
        return -1;
    }
    else
    {
        can_interface[index].opened = 1;
        return 0;
    }

#else
    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
    return -1;
#endif
}

void peak_close(int index)
{
#ifdef _WIN32

    TPCANStatus pcan_status;

    pcan_status = CAN_Uninitialize(((TPCANChannelInformation*)can_interface[index].internal)->channel_handle);
    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
    }

#else
    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
#endif
}

int peak_update(int index)
{
#ifdef _WIN32

    TPCANStatus pcan_status;

    if (NULL == can_interface[index].internal)
    {
        return -1;
    }

    pcan_status = CAN_GetStatus(((TPCANChannelInformation*)can_interface[index].internal)->channel_handle);
    if (PCAN_ERROR_ILLHW == pcan_status)
    {
        can_release(index);
        return -1;
    }
    else
    {
        return 0;
    }

#else

    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
    return -1;
#endif
}

int peak_set_baudrate(int index, enum can_baudrate baud)
{
#ifdef _WIN32

    can_close(index);
    can_interface[index].baudrate = baud;
    return can_open(index, baud);

#else
    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
    (void)baud;
    return -1;
#endif
}

int peak_send(int index, const struct can_frame* frame)
{
#ifdef _WIN32

    TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle;
    TPCANStatus pcan_status;
    TPCANMsg pcan_frame = {0};

    pcan_frame.ID = frame->can_id;
    pcan_frame.LEN = frame->can_dlc;
    pcan_frame.MSGTYPE = PCAN_MESSAGE_STANDARD;

    for (int i = 0; i < 8; i += 1)
    {
        pcan_frame.DATA[i] = frame->data[i];
    }

    pcan_status = CAN_Write(pcan_ch, &pcan_frame);

    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
        return -1;
    }

    return 0;

#else
    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
    (void)frame;
    return -1;
#endif
}

int peak_recv(int index, struct can_frame* frame, u64* timestamp)
{
#ifdef _WIN32

    TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle;
    TPCANStatus pcan_status;
    TPCANMsg pcan_frame = {0};
    TPCANTimestamp pcan_timestamp = {0};

    pcan_status = CAN_Read(pcan_ch, &pcan_frame, &pcan_timestamp);
    if (PCAN_ERROR_OK != pcan_status)
    {
        set_error_reason(lookup_error_string(pcan_status));
        return -1;
    }

    frame->can_id = pcan_frame.ID;
    frame->can_dlc = pcan_frame.LEN;

    *timestamp = pcan_timestamp.micros + (1000ULL * pcan_timestamp.millis) + (0x100000000ULL * 1000ULL * pcan_timestamp.millis_overflow);

    for (int i = 0; i < 8; i += 1)
    {
        frame->data[i] = pcan_frame.DATA[i];
    }

    return 0;

#else

    set_error_reason("PEAK driver is only supported on Windows.");
    (void)index;
    (void)frame;
    (void)timestamp;
    return -1;
#endif
}

#ifdef _WIN32
static TPCANBaudrate lookup_pcan_baudrate(enum can_baudrate baud)
{
    switch (baud)
    {
        default:
        case CAN_BAUD_1M:
            return PCAN_BAUD_1M;
        case CAN_BAUD_800K:
            return PCAN_BAUD_800K;
        case CAN_BAUD_500K:
            return PCAN_BAUD_500K;
        case CAN_BAUD_250K:
            return PCAN_BAUD_250K;
        case CAN_BAUD_125K:
            return PCAN_BAUD_125K;
        case CAN_BAUD_100K:
            return PCAN_BAUD_100K;
        case CAN_BAUD_95K:
            return PCAN_BAUD_95K;
        case CAN_BAUD_83K:
            return PCAN_BAUD_83K;
        case CAN_BAUD_50K:
            return PCAN_BAUD_50K;
        case CAN_BAUD_47K:
            return PCAN_BAUD_47K;
        case CAN_BAUD_33K:
            return PCAN_BAUD_33K;
        case CAN_BAUD_20K:
            return PCAN_BAUD_20K;
        case CAN_BAUD_10K:
            return PCAN_BAUD_10K;
        case CAN_BAUD_5K:
            return PCAN_BAUD_5K;
    }
}

static char* lookup_bus_name(DWORD device_type)
{
    switch (device_type)
    {
        default:
        case PCAN_NONEBUS:
            return "PCAN-NONEBUS";
        case PCAN_PCIBUS1:
            return "PCAN-PCI 1";
        case PCAN_PCIBUS2:
            return "PCAN-PCI 2";
        case PCAN_PCIBUS3:
            return "PCAN-PCI 3";
        case PCAN_PCIBUS4:
            return "PCAN-PCI 4";
        case PCAN_PCIBUS5:
            return "PCAN-PCI 5";
        case PCAN_PCIBUS6:
            return "PCAN-PCI 6";
        case PCAN_PCIBUS7:
            return "PCAN-PCI 7";
        case PCAN_PCIBUS8:
            return "PCAN-PCI 8";
        case PCAN_PCIBUS9:
            return "PCAN-PCI 9";
        case PCAN_PCIBUS10:
            return "PCAN-PCI 10";
        case PCAN_PCIBUS11:
            return "PCAN-PCI 11";
        case PCAN_PCIBUS12:
            return "PCAN-PCI 12";
        case PCAN_PCIBUS13:
            return "PCAN-PCI 13";
        case PCAN_PCIBUS14:
            return "PCAN-PCI 14";
        case PCAN_PCIBUS15:
            return "PCAN-PCI 15";
        case PCAN_PCIBUS16:
            return "PCAN-PCI 16";
        case PCAN_USBBUS1:
            return "PCAN-USB 1";
        case PCAN_USBBUS2:
            return "PCAN-USB 2";
        case PCAN_USBBUS3:
            return "PCAN-USB 3";
        case PCAN_USBBUS4:
            return "PCAN-USB 4";
        case PCAN_USBBUS5:
            return "PCAN-USB 5";
        case PCAN_USBBUS6:
            return "PCAN-USB 6";
        case PCAN_USBBUS7:
            return "PCAN-USB 7";
        case PCAN_USBBUS8:
            return "PCAN-USB 8";
        case PCAN_USBBUS9:
            return "PCAN-USB 9";
        case PCAN_USBBUS10:
            return "PCAN-USB 10";
        case PCAN_USBBUS11:
            return "PCAN-USB 11";
        case PCAN_USBBUS12:
            return "PCAN-USB 12";
        case PCAN_USBBUS13:
            return "PCAN-USB 13";
        case PCAN_USBBUS14:
            return "PCAN-USB 14";
        case PCAN_USBBUS15:
            return "PCAN-USB 15";
        case PCAN_USBBUS16:
            return "PCAN-USB 16";
        case PCAN_LANBUS1:
            return "PCAN-LAN 1";
        case PCAN_LANBUS2:
            return "PCAN-LAN 2";
        case PCAN_LANBUS3:
            return "PCAN-LAN 3";
        case PCAN_LANBUS4:
            return "PCAN-LAN 4";
        case PCAN_LANBUS5:
            return "PCAN-LAN 5";
        case PCAN_LANBUS6:
            return "PCAN-LAN 6";
        case PCAN_LANBUS7:
            return "PCAN-LAN 7";
        case PCAN_LANBUS8:
            return "PCAN-LAN 8";
        case PCAN_LANBUS9:
            return "PCAN-LAN 9";
        case PCAN_LANBUS10:
            return "PCAN-LAN 10";
        case PCAN_LANBUS11:
            return "PCAN-LAN 11";
        case PCAN_LANBUS12:
            return "PCAN-LAN 12";
        case PCAN_LANBUS13:
            return "PCAN-LAN 13";
        case PCAN_LANBUS14:
            return "PCAN-LAN 14";
        case PCAN_LANBUS15:
            return "PCAN-LAN 15";
        case PCAN_LANBUS16:
            return "PCAN-LAN 16";
    }
}

static char* lookup_error_string(TPCANStatus pcan_status)
{
    static char error_buf[1024];
    size_t offset = 0;
    int found = 0;
    size_t i;

    /* Some PCAN status codes share bits (e.g. ILLHW, ILLNET, ILLCLIENT all
       overlap with HWINUSE / NETINUSE).  Using a (value, mask) pair lets us
       match each code exactly without false positives. */
    static const struct
    {
        TPCANStatus value;
        TPCANStatus mask;
        const char* msg;

    } error_table[] =
        {
            {PCAN_ERROR_XMTFULL, PCAN_ERROR_XMTFULL, "Transmit buffer in CAN controller is full."},
            {PCAN_ERROR_OVERRUN, PCAN_ERROR_OVERRUN, "CAN controller was read too late."},
            {PCAN_ERROR_BUSLIGHT, PCAN_ERROR_BUSLIGHT, "Bus error: an error counter reached the 'light' limit."},
            {PCAN_ERROR_BUSHEAVY, PCAN_ERROR_BUSHEAVY, "Bus error: an error counter reached the 'heavy' limit."},
            {PCAN_ERROR_BUSPASSIVE, PCAN_ERROR_BUSPASSIVE, "Bus error: the CAN controller is error passive."},
            {PCAN_ERROR_BUSOFF, PCAN_ERROR_BUSOFF, "Bus error: the CAN controller is in bus-off state."},
            {PCAN_ERROR_QRCVEMPTY, PCAN_ERROR_QRCVEMPTY, "Receive queue is empty."},
            {PCAN_ERROR_QOVERRUN, PCAN_ERROR_QOVERRUN, "Receive queue was read too late."},
            {PCAN_ERROR_QXMTFULL, PCAN_ERROR_QXMTFULL, "Transmit queue is full."},
            {PCAN_ERROR_REGTEST, PCAN_ERROR_REGTEST, "Test of the CAN controller hardware registers failed (no hardware found)."},
            {PCAN_ERROR_NODRIVER, PCAN_ERROR_NODRIVER, "Driver not loaded."},
            /* HWINUSE (0x0400), NETINUSE (0x0800), ILLHW (0x1400), ILLNET (0x1800)
               and ILLCLIENT (0x1C00) all share bits within the 0x1C00 mask.
               Each entry is therefore matched against that common mask so that
               only the intended code is reported. */
            {PCAN_ERROR_HWINUSE, PCAN_ERROR_ILLCLIENT, "PCAN-Hardware already in use by a PCAN-Net."},
            {PCAN_ERROR_NETINUSE, PCAN_ERROR_ILLCLIENT, "A PCAN-Client is already connected to the PCAN-Net."},
            {PCAN_ERROR_ILLHW, PCAN_ERROR_ILLCLIENT, "PCAN-Hardware handle is invalid."},
            {PCAN_ERROR_ILLNET, PCAN_ERROR_ILLCLIENT, "PCAN-Net handle is invalid."},
            {PCAN_ERROR_ILLCLIENT, PCAN_ERROR_ILLCLIENT, "PCAN-Client handle is invalid."},
            {PCAN_ERROR_RESOURCE, PCAN_ERROR_RESOURCE, "Resource (FIFO, Client, timeout) cannot be created."},
            {PCAN_ERROR_ILLPARAMTYPE, PCAN_ERROR_ILLPARAMTYPE, "Invalid parameter."},
            {PCAN_ERROR_ILLPARAMVAL, PCAN_ERROR_ILLPARAMVAL, "Invalid parameter value."},
            {PCAN_ERROR_UNKNOWN, PCAN_ERROR_UNKNOWN, "Unknown error."},
            {PCAN_ERROR_ILLDATA, PCAN_ERROR_ILLDATA, "Invalid data, function, or action."},
            {PCAN_ERROR_ILLMODE, PCAN_ERROR_ILLMODE, "Driver object state is wrong for the attempted operation."},
            {PCAN_ERROR_CAUTION, PCAN_ERROR_CAUTION, "Operation succeeded but with irregularities."},
            {PCAN_ERROR_INITIALIZE, PCAN_ERROR_INITIALIZE, "Channel is not initialized."},
            {PCAN_ERROR_ILLOPERATION, PCAN_ERROR_ILLOPERATION, "Invalid operation."},
        };

    if (PCAN_ERROR_OK == pcan_status)
    {
        return "No error. Success.";
    }

    error_buf[0] = '\0';

    for (i = 0; i < sizeof(error_table) / sizeof(error_table[0]); i++)
    {
        if ((pcan_status & error_table[i].mask) == error_table[i].value)
        {
            if (found)
            {
                offset += (size_t)snprintf(error_buf + offset, sizeof(error_buf) - offset, "; ");
            }
            offset += (size_t)snprintf(error_buf + offset, sizeof(error_buf) - offset, "%s", error_table[i].msg);
            found = 1;
        }
    }

    if (! found)
    {
        return "Unknown error.";
    }

    return error_buf;
}
#endif /* _WIN32 */
