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

#if defined(__APPLE__) && !defined(NOFRAMEWORK)
#include <libximc/ximc.h>
#else
#include <ximc.h>
#endif

device_enumeration_t search_devices(int* device_count)
{
    printf("\nSearching for devices...\n");
    const int probe_flags = ENUMERATE_PROBE | ENUMERATE_NETWORK;
    const char* enumerate_hints = "addr=";
    device_enumeration_t devenum = enumerate_devices(probe_flags, enumerate_hints);
    if (!devenum) {
        printf("Error searching for devices\n");
        *device_count = 0;
        return devenum;
    }

    *device_count = get_device_count(devenum);
    if (*device_count < 1) {
        printf("No devices found\n");
        return devenum;
    }

    printf("Found %d devices\n", *device_count + 1);
    return devenum;
}

void print_device_list(device_enumeration_t devenum, int device_count)
{
    printf("\nFound devices list:\n");
    int i = 0;
    for (i = 0; i < device_count; ++i) {
        printf(" #%d. %s\n", i + 1, get_device_name(devenum, i));
    }
    printf(" #%d. virtual\n", i + 1);
}

char* create_virtual_device_name()
{
    static char device_name[256];
    static char path[255];

#if defined(_WIN32) || defined(_WIN64)
    strcpy(path, getenv("USERPROFILE"));
    strcat(path, "\\virt_controller.bin");
    strcpy(device_name, "xi-emu:///");
    strcat(device_name, path);
#else
    strcpy(path, getenv("HOME"));
    strcat(path, "/virt_controller.bin");
    strcpy(device_name, "xi-emu:///");
    strcat(device_name, path);
#endif
    printf("Virtual controller file path: '%s'\n", path);

    return device_name;
}

char* get_device_name_from_list(device_enumeration_t devenum, int device_count)
{
    char buffer[256];
    static char device_name[256];
    while (1) {
        print_device_list(devenum, device_count);
        printf("When using an Ethernet (TCP protocol) connection, multi-user interaction with the device is possible. In this case, the same device may be controlled simultaneously from multiple sources, which can potentially lead to control conflicts.\nEnter device number from the list:\n");
        fgets(buffer, sizeof(buffer), stdin);
        int number;
        if (sscanf(buffer, "%d", &number) != 1) {
            continue;
        }

        if (1 <= number && number <= device_count) {
            char* name = get_device_name(devenum, number - 1);
            strcpy(device_name, name);
            printf("Selected device: '%s'\n", device_name);
            return device_name;
        }

        if (number == device_count + 1) {
            return create_virtual_device_name();
        }
    }
}

char* choose_device()
{
    int device_count = 0;
    device_enumeration_t devenum = search_devices(&device_count);
    char* device_name;
    if (device_count < 1) {
        printf("The real controller is not found or busy with another app.\nThe virtual controller is opening to check the operation of the library...\n");
        device_name = create_virtual_device_name();
    }
    else {
        device_name = get_device_name_from_list(devenum, device_count);
    }
    free_enumerate_devices(devenum);
    return device_name;
}

int main (int argc, char* argv[])
{
    char ximc_version_str[32];
    ximc_version(ximc_version_str);
    printf("This is a ximc test program\nlibximc version %s\n", ximc_version_str);

    char* device_name = choose_device();
    printf("\nOpening device...\n");
    device_t device = open_device(device_name);
    if (device == device_undefined) {
        printf("Error opening device\nPress Enter to exit...");
        char terminating_str[2];
        fgets(terminating_str, 2, stdin);
        return 1;
    }

    printf("Getting status parameters: ");
    status_t status;
    get_status(device, &status);
    printf("position: %d, encoder: %lld, speed: %d\n", status.CurPosition, status.EncPosition, status.CurSpeed);

    printf("Getting engine parameters: ");
    engine_settings_t engine_settings;
    get_engine_settings(device, &engine_settings);
    printf("voltage: %d, current: %d, speed: %d\n", engine_settings.NomVoltage, engine_settings.NomCurrent, engine_settings.NomSpeed);

    const int sec = 3;
    printf("Rotating to the left for %d seconds...\n", sec);
    command_left(device);
    msec_sleep(sec * 1000);

    printf("Getting status parameters: ");
    get_status(device, &status);
    printf("position: %d, encoder: %lld, speed: %d\n", status.CurPosition, status.EncPosition, status.CurSpeed);

    printf("Getting calibrated status parameters: ");
    calibration_t calibration;
    // Setting calibration constant to 0.1 (one controller step equals this many units)
    calibration.A = 0.1;
    // We have to set microstep mode to convert microsteps to calibrated units correctly
    calibration.MicrostepMode = engine_settings.MicrostepMode;
    status_calb_t status_calb;
    get_status_calb(device, &status_calb, &calibration);
    printf("position: %.3f mm, speed: %.3f mm/s\n", status_calb.CurPosition, status_calb.CurSpeed);

    printf("Stopping engine...");
    command_stop(device);
    printf(" Done\n" );

    printf("Getting status parameters: ");
    get_status(device, &status);
    printf("position: %d, encoder: %lld, speed: %d\n", status.CurPosition, status.EncPosition, status.CurSpeed);

    printf("Closing device...");
    close_device(&device);
    printf(" Done\n");

    printf("Press Enter to exit...");
    char terminating_str[2];
    fgets(terminating_str, 2, stdin);
    return 0;
}
