#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/sysmacros.h> /* major() minor() */
#include <signal.h>
#include <time.h>

#include <apci3120.h>
#include "findboards.h"
#include <apci3120-events.h>

/*
 * Global value of the current fd
 */
int current_fd = -1;

#define SINGLE_TRANSFERT_MAX_SIZE PAGE_SIZE
#define SEQUENCE_ARRAY_SIZE 2
#define NUMBER_OF_SEQUENCE 40
#define NUMBER_OF_ACQUISITON SEQUENCE_ARRAY_SIZE * NUMBER_OF_SEQUENCE

#define ACQUISITION_TIMING_us 10
#define EXPECTED_TIME_BETWEEN_INTERRUPTS_us ACQUISITION_TIMING_us * NUMBER_OF_ACQUISITON


// Define a structure to hold the time
static struct timespec start_time, end_time;

void print_elapsed_time(void) {
    struct timespec elapsed_time;
    long long elapsed_ns;
	double elapsed_us;

    // Calculate elapsed time in nanoseconds
    elapsed_time.tv_sec = end_time.tv_sec - start_time.tv_sec;
    elapsed_time.tv_nsec = end_time.tv_nsec - start_time.tv_nsec;
	if (elapsed_time.tv_nsec < 0) {
        elapsed_time.tv_sec--;
        elapsed_time.tv_nsec += 1000000000L;
    }

    // Convert to milliseconds
    elapsed_ns = elapsed_time.tv_sec * 1000000000LL + elapsed_time.tv_nsec;
    elapsed_us = elapsed_ns / 1000.0;

    printf("Time elapsed: %.2f us Expected: %d us\n", elapsed_us,EXPECTED_TIME_BETWEEN_INTERRUPTS_us);
}

/*
 * Enables to stop the acquisition
 */
void exit_test(int sig)
{
	int error = 0;
	printf("Exit the test \n");

	error = ioctl (current_fd, CMD_APCI3120_StopSequenceAcquisition, NULL);
	printf("Stop sequence returned: %d \n", error);

	error = ioctl (current_fd, CMD_APCI3120_ResetBoardInterruptRoutine, NULL);
	printf("ReSet board interrupt routine returned: %d \n", error);
	if ( error != 0)
	{
		close(current_fd);
		exit(EXIT_FAILURE);
	}

	close(current_fd);

	exit(EXIT_SUCCESS);
}


void apci3120_run_test(int fd)
{
	int error;
	int cpt = 0;
	int i = 0;
	int interrupt_count = 0;
	static int old_channels_value[SEQUENCE_ARRAY_SIZE] = {0};

	// Initialize the elements of the sequence
	struct apci3120_sequence_acquisition_data sequence_data;

	sequence_data.sequence_array_size = SEQUENCE_ARRAY_SIZE;
	for ( cpt = 0; cpt < sequence_data.sequence_array_size; cpt++ )
	{
		sequence_data.sequence_channels[cpt] = cpt;
		sequence_data.gain[cpt] = APCI3120_1_GAIN;
		sequence_data.polarity[cpt] = APCI3120_BIPOLAR;
	}

	sequence_data.number_of_acquisition = NUMBER_OF_ACQUISITON;
	sequence_data.dma_mode = APCI3120_DMA_USED;
	sequence_data.extern_trigger = APCI3120_DISABLE;
	sequence_data.acquisition_mode = APCI3120_SIMPLE_MODUS;
	sequence_data.acquisition_cycle = APCI3120_CONTINUOUS;
	sequence_data.delay_timing = 0;
	sequence_data.acquisition_timing = ACQUISITION_TIMING_us;

	printf("=> CMD_APCI3120_SetBoardInterruptRoutine \n");
	error = ioctl (fd, CMD_APCI3120_SetBoardInterruptRoutine, NULL);
	printf("Set Board Interrupt routine returned: %d \n", error);

	sleep(1);

	printf("=> CMD_APCI3120_InitAnalogInputSequenceAcquisition\n");
	error = ioctl (fd, CMD_APCI3120_InitAnalogInputSequenceAcquisition, &sequence_data);
	printf("Initialize returned: %d \n", error);
	if ( error != 0)
	{
		exit_test(0);
	}

	sleep(1);

	error = ioctl (fd, CMD_APCI3120_StartAnalogInputSequenceAcquisition, NULL);
	printf("Start sequence returned: %d \n", error);
	if ( error != 0)
	{
		exit_test(0);
	}

	/*
	 * Handle CTRL+C
	 */
	signal( SIGINT, exit_test);

	/* Empty the event buffer */
	printf("Empty the event buffer\n");
	{
		if ( ioctl (fd,CMD_apci3120_EventEmpty,NULL) != 0 )
		{
			printf("Error CMD_apci3120_EventEmpty\n");
			exit_test(0);
		}
	}

	/*
	 * Endless while, only interruptible by CTRL+C
	 */
	while(1)
	{
        apci3120_event_t evt[NUMBER_OF_SEQUENCE];
		// Register start time
        clock_gettime(CLOCK_MONOTONIC, &start_time);
		error = ioctl(fd, CMD_apci3120_EventWait, evt);
        if (error != 0)
        {
            printf("Error CMD_apci3120_EventWait %d\n",error);
            exit_test(0);
        }
        printf("Interrupt count %d\n", interrupt_count);
        for (int y = 0; y < NUMBER_OF_SEQUENCE; y++)
        {
            printf("Event %d, Source: %d\n", y, evt[y].source);
            printf("Content:\n");
            for (i = 0; i < sequence_data.sequence_array_size; i++)
            {
                printf("Channel[%d]: %d \t", i, evt[y].param1[i]);
				printf("Channel %i diff: %d \t", i, evt[y].param1[i]-old_channels_value[i]);
				old_channels_value[i] = evt[y].param1[i];
            }
            printf("\n");
        }
		// Register end time
        clock_gettime(CLOCK_MONOTONIC, &end_time);
        print_elapsed_time();
        interrupt_count++;
	}
}

//--------------------------------------------------------------------------------
int main(int argc, char** argv)
{
	int i=0;
	int card_choice = 0;
	int * apci3120_card_fd;
	unsigned int apci3120_card_number = 0;
	{
		apci3120_card_number = apci_find_boards("apci3120",&apci3120_card_fd);

		printf("total: %d cards\n",apci3120_card_number);
	}
	printf("Hit CTRL+C to exit the test\n");

	// No cards
	if ( apci3120_card_number == 0)
	{
		printf("No cards found, exit ...\n");
		return 0;
	}
	// JUST one card
	if ( apci3120_card_number == 1)
	{
		current_fd = apci3120_card_fd[0];
		// Execute directly the test on this card
		apci3120_run_test(current_fd);
		return 0;
	}
	// More than one card
	else
	{
		printf("Choose the card on which you want to do the test:\n");
		printf("Possible choice:\t");
		for ( i = 0; i< apci3120_card_number; i++)
		{
			printf("%d \t", i);
		}
		printf("\n");
		scanf("%d",&card_choice);

		if ( card_choice < 0 || card_choice >= apci3120_card_number)
		{
			printf("Incorrect card choice, exit ...\n");
			return 0;
		}
		else
		{
			current_fd = apci3120_card_fd[card_choice];
			printf("Running test on card: %d\n", card_choice);
			apci3120_run_test(apci3120_card_fd[card_choice]);
		}
	}
	return 0;
}
