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

#include "hal_linux/hal_linux.h"

#include "bindings/bricklet_industrial_dual_relay.h"

#define DUAL_RELAY_1_UID "Nnn"
#define DUAL_RELAY_2_UID "NjK"
//#define DUAL_RELAY_1_UID "No7"
//#define DUAL_RELAY_2_UID "Kjc"

static void check(int rc, const char *c) {
    if (rc < 0) {
        printf("Failed to %s: %s (return code %d, errno %d)\n", c, tf_strerror(rc), rc, errno);
    }
}

TF_HalContext hal;

void handler(TF_IndustrialDualRelay *device, uint8_t channel, bool value, void *user_data) {
    return;
}

int main(int argc, char **argv) {
    printf("Relay tester - HAL Linux\n");

    TF_Port ports[9] = {{
        .chip_select_pin=23,
        .port_name = 'A'
    }, {
        .chip_select_pin=22,
        .port_name = 'B'
    }, {
        .chip_select_pin=25,
        .port_name = 'C'
    }, {
        .chip_select_pin=26,
        .port_name = 'D'
    }, {
        .chip_select_pin=27,
        .port_name = 'E'
    }, {
        .chip_select_pin=24,
        .port_name = 'F'
    }, {
        .chip_select_pin=7,
        .port_name = 'G'
    }, {
        .chip_select_pin=6,
        .port_name = 'H'
    }, {
        .chip_select_pin=5,
        .port_name = 'I'
    }};

    check(tf_hal_linux_init(&hal, "/dev/spidev0.0", ports, sizeof(ports)/sizeof(ports[0])), "init hal");

    TF_IndustrialDualRelay r1, r2;
    tf_industrial_dual_relay_create(&r1, DUAL_RELAY_1_UID, &hal);
    tf_industrial_dual_relay_create(&r2, DUAL_RELAY_2_UID, &hal);

    uint32_t test_start = tf_hal_current_time_us(&hal);
    uint32_t test_end = test_start + 60 * 1000 * 1000;

    tf_industrial_dual_relay_register_monoflop_done_callback(&r1, handler, NULL);
    tf_industrial_dual_relay_register_monoflop_done_callback(&r2, handler, NULL);

    while(!tf_hal_deadline_elapsed(&hal, test_end)) {
        tf_industrial_dual_relay_set_monoflop(&r1, 0, true, 1000);
        uint32_t time_remaining = 1001;
        for(int i = 0; i < 1000; ++i) {
            uint32_t loop_start = tf_hal_current_time_us(&hal);
            if (i % 100 == 0) {
                bool ret_value;
                uint32_t ret_time;
                uint32_t ret_time_remaining;
                check(tf_industrial_dual_relay_get_monoflop(&r1, 0, &ret_value, &ret_time, &ret_time_remaining), "get monoflop 1");
                if(ret_time_remaining > time_remaining)
                    tf_hal_log_info("Monoflop 1 (running): ret_time_remaining was not monotonic (was %d, last time_remaining was %d)\n", ret_time_remaining, time_remaining);
                else
                    time_remaining = ret_time_remaining;
                if(ret_time_remaining > 0 && !ret_value)
                    tf_hal_log_info("Monoflop 1 (running): value was false but time_remaining was > 0 (%d)\n", ret_time_remaining);


                check(tf_industrial_dual_relay_get_monoflop(&r2, 0, &ret_value, &ret_time, &ret_time_remaining), "get monoflop 2");

                if(ret_time_remaining != 0)
                    tf_hal_log_info("Monoflop 2 (not running): ret_time_remaining should be 0 but was %d\n", ret_time_remaining);
                if(ret_value)
                    tf_hal_log_info("Monoflop 2 (not running): value was true but monoflop is not running\n", ret_time_remaining);
            }
            // Tick callbacks to keep the clock signal up
            while(!tf_hal_deadline_elapsed(&hal, loop_start + 1000)) {
                check(tf_industrial_dual_relay_callback_tick(&r1, 0), "callback tick 1");
                check(tf_industrial_dual_relay_callback_tick(&r2, 0), "callback tick 2");
                tf_hal_sleep_us(&hal, 100);
            }
        }
        time_remaining = 1001;
        tf_industrial_dual_relay_set_monoflop(&r2, 0, true, 1000);
        for(int i = 0; i < 1000; ++i) {
            uint32_t loop_start = tf_hal_current_time_us(&hal);
            if (i % 100 == 0) {
                bool ret_value;
                uint32_t ret_time;
                uint32_t ret_time_remaining;
                check(tf_industrial_dual_relay_get_monoflop(&r1, 0, &ret_value, &ret_time, &ret_time_remaining), "get monoflop 1");
                if(ret_time_remaining != 0)
                    tf_hal_log_info("Monoflop 1 (not running): ret_time_remaining should be 0 but was %d\n", ret_time_remaining);
                if(ret_value)
                    tf_hal_log_info("Monoflop 1 (not running): value was true but monoflop is not running\n", ret_time_remaining);


                check(tf_industrial_dual_relay_get_monoflop(&r2, 0, &ret_value, &ret_time, &ret_time_remaining), "get monoflop 2");
                if(ret_time_remaining > time_remaining)
                    tf_hal_log_info("Monoflop 2 (running): ret_time_remaining was not monotonic (was %d, last time_remaining was %d)\n", ret_time_remaining, time_remaining);
                else
                    time_remaining = ret_time_remaining;
                if(ret_time_remaining > 0 && !ret_value)
                    tf_hal_log_info("Monoflop 2 (running): value was false but time_remaining was > 0 (%d)\n", ret_time_remaining);
            }
            // Tick callbacks to keep the clock signal up
            while(!tf_hal_deadline_elapsed(&hal, loop_start + 1000)) {
                check(tf_industrial_dual_relay_callback_tick(&r1, 0), "callback tick 1");
                check(tf_industrial_dual_relay_callback_tick(&r2, 0), "callback tick 2");
                tf_hal_sleep_us(&hal, 100);
            }
        }
    }

    printf("Relay 1 error counters: %d %d %d %d\n",
        r1.tfp.spitfp.error_count_checksum,
        r1.tfp.spitfp.error_count_frame,
        r1.tfp.error_count_frame,
        r1.tfp.error_count_unexpected);

    printf("Relay 2 error counters: %d %d %d %d\n",
        r2.tfp.spitfp.error_count_checksum,
        r2.tfp.spitfp.error_count_frame,
        r2.tfp.error_count_frame,
        r2.tfp.error_count_unexpected);

    return 0;
}
