// Copyright (c) 2026 Kvaser AB
//
// SPDX-License-Identifier: MIT-0
//
// The MIT-0 (No Attribution) gives the user of this code
// the rights to use this code, without limitation, and without
// any requirements of attribution. See the full license here:
//   https://spdx.org/licenses/MIT-0.html

#include <cl/cl_api.h>
#include <stdio.h>

#ifdef _MSC_VER
#include <windows.h>
#else
#include <unistd.h>
#endif

#include <stdbool.h>
#include <time.h>
#include <string.h>

#define SLEEP_DELAY_LONG 3000
#define SLEEP_DELAY_SHORT 30
#define SLEEP_DELAY SLEEP_DELAY_SHORT

void my_sleep(uint32_t time_ms) {
#ifdef _MSC_VER
        Sleep(time_ms);
#else
        usleep(1000*time_ms);
#endif
}

#define guard(expr) do {                        \
    int __val = (int)(expr);                    \
    if (__val) {                                \
        printf("Failed call \"" #expr "\"");    \
        printf(" with status %d\n", __val);     \
        return __val;                           \
    }                                           \
} while (0)

int format_timestamp_u64(uint64_t timestamp, char* buffer, size_t buffer_size) {
    // Ensure the value fits into time_t
    if (timestamp > (uint64_t)((time_t)-1) && (time_t)-1 > 0) {
        return -1; // overflow
    }
    time_t t = (time_t)timestamp;
    struct tm tm_result;

#ifdef _WIN32
    if (gmtime_s(&tm_result, &t) != 0) {
        return -1;
    }
#else
    if (gmtime_r(&t, &tm_result) == NULL) {
        return -1;
    }
#endif

    if (strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S UTC", &tm_result) == 0) {
        return -1;
    }
    return 0;
}

typedef struct {
    bool started;
    int num_chunks;
    bool finished;
} callback_data_t;

void cb_update_starting(void *user_data, size_t total_transfer_bytes) {
    callback_data_t* cb_data = (callback_data_t*) user_data;
    printf("Starting update. Total bytes: %lu\n", (unsigned long) total_transfer_bytes);
    cb_data->started = true;
}
void cb_report_progress(void *user_data, size_t transferred_bytes) {
    callback_data_t* cb_data = (callback_data_t*) user_data;
    ++cb_data->num_chunks;
    printf("Transferred chunk %lu, total transferred bytes: %lu\n", (unsigned long) cb_data->num_chunks, (unsigned long) transferred_bytes);
}

void cb_update_finished(void *user_data, enum cl_firmware_update_result_t result) {
    callback_data_t* cb_data = (callback_data_t*) user_data;
    const char* result_str = "Unknown";
    switch (result) {
        case CL_UF_STATUS_SUCCESS:
            result_str = "Success";
            break;
        case CL_UF_STATUS_TRANSFER_ERROR:
            result_str = "Transfer error";
            break;
        case CL_UF_STATUS_FIRMWARE_CORRUPT:
            result_str = "Firmware corrupt";
            break;
        case CL_UF_STATUS_FIRMWARE_FOR_OTHER_DEVICE:
            result_str = "Firmware for other device";
            break;
        case CL_UF_STATUS_FIRMWARE_FAILED_SIGNATURE_CHECK:
            result_str = "Failed signature check";
            break;
        case CL_UF_STATUS_UNKNOWN_ERROR:
            result_str = "Unknown error";
            break;
    }
    printf("Finished update with result %s\n", result_str);
    cb_data->finished = true;
}

int update_device(cl_device_t* device, const char* path) {
    puts("Updating device");
    callback_data_t data = { 0 };
    cl_firmware_update_callbacks_t callbacks = {
        .user_data = &data,
        .update_starting = cb_update_starting,
        .report_progress = cb_report_progress,
        .update_finished = cb_update_finished,
    };

    guard(
        cl_device_update_firmware_from_path(device, path, &callbacks)
    );
    return 0;
}

int print_config(const char* prefix, const cl_eth_config_t* config) {
    char buffer[1024] = {0};
    char* ptr = buffer;

    // speed
    if (config->link_speed & CL_ETH_LINK_SPEED_10MBIT_FULL_DUPLEX) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - 10Mbit\n");
    }
    if (config->link_speed & CL_ETH_LINK_SPEED_100MBIT_FULL_DUPLEX) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - 100Mbit\n");
    }
    if (config->link_speed & CL_ETH_LINK_SPEED_1000MBIT_FULL_DUPLEX) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - 1000Mbit\n");
    }

    // role
    if (config->link_role & CL_ETH_LINK_ROLE_LEADER) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - Leader\n");
    }
    if (config->link_role & CL_ETH_LINK_ROLE_FOLLOWER) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - Leader\n");
    }

    // aneg
    if (config->aneg_mode & CL_ETH_ANEG_MODE_IEEE_ANEG_ENABLED) {
        ptr += snprintf(ptr, sizeof(buffer) - (ptr - buffer), "     - Aneg\n");
    }

    printf("   %s:\n%s\n",
        prefix,
        strlen(buffer) > 0
        ? buffer
        : "Empty"
    );


    return 0;
}

int print_device_info(cl_device_t* device, char* buffer, size_t buffer_size) {
    guard(
        cl_device_identify(device)
    );

    uint64_t ean;
    guard(
        cl_device_get_ean(device, &ean)
    );
    cl_format_ean(ean, buffer, buffer_size);
    printf("EAN: %s\n", buffer);

    uint64_t serial_number = 0;
    guard(
        cl_device_get_serial_number(device, &serial_number)
    );
    printf("SERIAL: %07lu\n", (unsigned long) serial_number);

    guard(
        cl_device_get_firmware_ean(device, &ean)
    );
    cl_format_ean(ean, buffer, buffer_size);
    printf("FW EAN: %s\n", buffer);

    guard(
        cl_device_get_feature_ean(device, &ean)
    );
    cl_format_ean(ean, buffer, buffer_size);
    printf("FEATURE EAN: %s\n", buffer);

    guard(
        cl_device_get_name(device, buffer, buffer_size)
    );
    printf("DEV NAME: %s\n", buffer);

    guard(
        cl_device_get_nickname(device, buffer, buffer_size)
    );
    printf("DEV NICKNAME: %s\n", buffer);

    guard(
        cl_device_get_pcb_revision(device, buffer, buffer_size)
    );
    printf("PCB REV: %s\n", buffer);

    guard(
        cl_device_get_firmware_version(device, buffer, buffer_size)
    );
    printf("FW VER: %s\n", buffer);

    guard(
        cl_device_get_bootloader_version(device, buffer, buffer_size)
    );
    printf("BL VER: %s\n", buffer);

    uint64_t timestamp;
    guard(
        cl_device_get_manufacturing_date(device, &timestamp)
    );
    guard(
        format_timestamp_u64(timestamp, buffer, buffer_size)
    );
    printf("MF TIME: %s\n", buffer);

    return 0;
}

int print_interface(cl_interface_t* interface_, char* buffer, size_t buffer_size) {

    guard(
        cl_interface_get_nickname(interface_, buffer, buffer_size)
    );
    printf("I: %s\n", buffer);

    guard(
        cl_interface_identify(interface_)
    );

    cl_interface_status_t interface_status = {
        .struct_size = sizeof(interface_status),
        .struct_version = CL_INTERFACE_STATUS_T_VERSION,
    };
    guard(
        cl_interface_get_status(interface_, &interface_status)
    );

    const char* interface_type = "Unknown type";
    switch (interface_status.interface_type) {
        case CL_INTERFACE_TYPE_T1:
            interface_type = "T1";
            break;
        case CL_INTERFACE_TYPE_T:
            interface_type = "T";
            break;
    }

    const char* state_str = "Unknown state";
    switch (interface_status.eth_interface_status.link_state) {
        case CL_ETH_LINK_STATE_UP:
            state_str = "Up";
            break;
        case CL_ETH_LINK_STATE_DOWN:
            state_str = "Down";
            break;
    }

    const char* role_str = "Unknown role";
    switch (interface_status.eth_interface_status.link_role) {
        case CL_ETH_LINK_ROLE_LEADER:
            role_str = "Leader";
            break;
        case CL_ETH_LINK_ROLE_LEADER_PREFERRED:
            role_str = "Leader Preferred";
            break;
        case CL_ETH_LINK_ROLE_FOLLOWER:
            role_str = "Follower";
            break;
        case CL_ETH_LINK_ROLE_FOLLOWER_PREFERRED:
            role_str = "Follower Preferred";
            break;
    }

    const char* speed_str = "Unknown speed";
    switch (interface_status.eth_interface_status.link_speed) {
        case CL_ETH_LINK_SPEED_10MBIT_FULL_DUPLEX:
            speed_str = "10Mbit Full Duplex";
            break;
        case CL_ETH_LINK_SPEED_10MBIT_HALF_DUPLEX:
            speed_str = "10Mbit Half Duplex";
            break;
        case CL_ETH_LINK_SPEED_100MBIT_FULL_DUPLEX:
            speed_str = "100Mbit Full Duplex";
            break;
        case CL_ETH_LINK_SPEED_100MBIT_HALF_DUPLEX:
            speed_str = "100Mbit Half Duplex";
            break;
        case CL_ETH_LINK_SPEED_1000MBIT_FULL_DUPLEX:
            speed_str = "1000Mbit Full Duplex";
            break;
        case CL_ETH_LINK_SPEED_1000MBIT_HALF_DUPLEX:
            speed_str = "1000Mbit Half Duplex";
            break;
    }

    printf("   TYPE: %s\n", interface_type);
    printf("   STATE: %s\n", state_str);
    printf("   ROLE: %s\n", role_str);
    printf("   SPEED: %s\n", speed_str);

    cl_eth_config_t config;
    guard(
        cl_interface_eth_get_config(interface_, &config)
    );

    print_config("CURRENT", &config);

    if (interface_status.interface_type == CL_INTERFACE_TYPE_T1) {
        guard(
            cl_interface_eth_get_supported_configs(interface_, &config)
        );
        print_config("SUPPORTED", &config);

        cl_tc10_state_t tc10_state;
        cl_status_t status = cl_interface_get_tc10_state(interface_, &tc10_state);
        if (status == CL_STATUS_SUCCESS) {
            const char* tc10_state_str;
            switch (tc10_state) {
                case CL_TC10_STATE_NORMAL:
                    tc10_state_str = "Normal";
                    break;
                case CL_TC10_STATE_SLEEP:
                    tc10_state_str = "Sleep";
                    break;
                case CL_TC10_STATE_SLEEP_ABORTED:
                    tc10_state_str = "Sleep Aborted";
                    break;
                case CL_TC10_STATE_SLEEP_FAILED:
                    tc10_state_str = "Sleep Failed";
                    break;
                default:
                    tc10_state_str = "Unknown";
                    break;
            }
            printf("   TC10 STATE: %s\n", tc10_state_str);
        } else if (status == CL_STATUS_OPERATION_NOT_SUPPORTED_BY_DEVICE){
            puts("Failed to read TC10 State, not supported by this device");
        } else {
            puts("Failed to read TC10 State with unknown error");
        }
    }

    return 0;
}

void test_new_functions(cl_device_list_t* device_list) {
    cl_device_t* a = cl_device_list_find_by_info(NULL, 3, device_list);
    cl_device_t* b = cl_device_list_find_by_info(NULL, 10002, device_list);

    uint64_t ean, serial_number;
    char ean_buffer[32], sn_buffer[32];

    if (a) {
        cl_device_get_ean(a, &ean);
        cl_device_get_serial_number(a, &serial_number);
        cl_format_ean(ean, ean_buffer, sizeof(ean_buffer));
        cl_format_serial_number(serial_number, sn_buffer, sizeof(sn_buffer));
        printf("a EAN: %s, SN: %s\n", ean_buffer, sn_buffer);
    } else {
        printf("Device a not found\n");
    }

    if (b) {
        cl_device_get_ean(b, &ean);
        cl_device_get_serial_number(b, &serial_number);
        cl_format_ean(ean, ean_buffer, sizeof(ean_buffer));
        cl_format_serial_number(serial_number, sn_buffer, sizeof(sn_buffer));
        printf("b EAN: %s, SN: %s\n", ean_buffer, sn_buffer);
    } else {
        printf("Device a not found\n");
    }
}

int main(int argc, char** argv) {
    (void) argc; (void) argv;

    uint32_t major, minor, patch;
    cl_get_api_version(&major, &minor, &patch);

    printf("Using version %d.%d.%d of cl_api\n", major, minor, patch);

    cl_client_t* c = cl_client_create();
    cl_device_list_t* device_list;
    cl_status_t status = cl_client_scan_for_devices_with_timeout(c, &device_list, 500);
    if (status) {
        printf("An error occurred");
        cl_client_destroy(c);
        return 1;
    }
    test_new_functions(device_list);
    cl_device_iterator_t* iter = cl_device_iterator_create(device_list);
    cl_device_t* device = cl_device_iterator_next(iter);
    while (device) {
        char buffer[128];
        print_device_info(device, buffer, sizeof(buffer));
        // update_device(device, path);

        my_sleep(SLEEP_DELAY);
        cl_interface_list_ptr_t interfaces;
        status = cl_device_get_interfaces(device, &interfaces);
        if (status || !interfaces) {
            printf("Error getting interfaces\n");
        } else {
            cl_interface_iterator_t* interface_iterator = cl_interface_iterator_create(interfaces);
            cl_interface_t* interface_ = cl_interface_iterator_next(interface_iterator);
            while (interface_) {
                print_interface(interface_, buffer, sizeof(buffer));
                my_sleep(SLEEP_DELAY);
                interface_ = cl_interface_iterator_next(interface_iterator);
            }

            cl_interface_iterator_destroy(interface_iterator);
            cl_interface_list_destroy(interfaces);
        }

        device = cl_device_iterator_next(iter);
    }

    cl_device_iterator_destroy(iter);
    cl_device_list_destroy(device_list);
    cl_client_destroy(c);
    return 0;
}
