#include <stdlib.h>                                                     /* for EXIT_SUCCESS */
#include <stdio.h>                                                      /* for console I/O */
#include "windows.h"
#include "SiUSBXp.h"                                                    /* USBXpress API calls */

#define TRUE 1
#define FALSE 0
#define ON 1
#define OFF 0

/* packet purposes */
#define GET_STATUS 1
#define TIMER_ON 2
#define TIMER_OFF 3
#define READ_VALUE 4
#define EXIT 5
#define READ_CONTINUOUS 6
#define USE_TEMP_SENSOR 7
#define USE_ANALOG_INPUT 8
#define IGNORE_VALUE 10
#define VALID_VALUE 11

typedef struct
{
    SI_DEVICE_STRING devStrings[5];                                     /* array to hold device descriptor strings */
} DEVICE;


typedef struct                                                          /* buffer to device */
{
    unsigned char reason;
    unsigned char adcStatus;
    unsigned char timerStatus;
    unsigned char adcResultLow;
    unsigned char adcResultHigh;
} USB_BUFFER;


/* global variables */
DWORD numDevices = 0;
SI_STATUS status;                                                       /* holds status of various API calls */
DWORD devIdx, strType;                                                  /* various indexes */
HANDLE devHandle = INVALID_HANDLE_VALUE;                                /* device handle; no idea why init to INVALID_HANDLE_VALUE */
USB_BUFFER readBuffer, writeBuffer;                                     /* read and write buffers, to hold stuff from and to send to device */
int getQueueSuccess;                                                    /* flag to indicate queue ready to be read */
char doContinuous = 'N';                                                /* flag to confirm continous readout of ADC results */
DWORD numBytesInQueue = 0, queueStatus = SI_SUCCESS;                    /* SI_CheckRXQueue() stuff */
DWORD numBytesToWrite = (DWORD)sizeof(USB_BUFFER), numBytesWritten;     /* SI_Write() stuff; always writes same number of bytes */
DWORD numBytesToRead = numBytesToWrite, numBytesReturned;               /* SI_Read() stuff; always requests same number of bytes */
double fahrenheit = 0;                                                  /* holds value from ADC, converted to degrees F */
int adcResultCombined = 0;                                              /* holds ADC0H and ADC0L as one variable */


/*
 * SafeMalloc() is a wrapper function to perform safe malloc()s
 */
void *SafeMalloc(size_t size)
{
    void *vp;

    if ((vp = malloc(size)) == NULL)
    {
        fputs("Out of memory\n", stderr);
        exit(EXIT_FAILURE);
    }

    return(vp);
}


/*
 * PrintOptions() prints list of options for user to choose from
 */
void PrintOptions(void)
{
    printf("\nWhat would you like to do?\n");
    printf("[1] Check device status\n");
    printf("[2] Turn timer on\n");
    printf("[3] Turn timer off\n");
    printf("[4] Display ADC result\n");
    printf("[5] Exit\n");
    printf("[6] Display ADC results (continuous)\n");
    printf("[7] Switch to temperature sensor as ADC input\n");
    printf("[8] Switch to pin P3.5 as ADC input\n");
}


/*
 * WriteBuffer() is a wrapper function for SI_Write().  It writes buffer to device and also checks response.
 */
void WriteBuffer(void)
{
    /* send packet to device, get status */
    status = SI_Write(devHandle, &writeBuffer, numBytesToWrite, &numBytesWritten);
    if (status == SI_SUCCESS)                                           /* if write was successful... */
    {
        printf("Got SI_Write() success status.\n");
        getQueueSuccess = 0;
        while (!getQueueSuccess)                                        /* continue to check read queue until get a response from device... */
        {
            printf("Performing SI_CheckRXQueue()...\n");
            /* check the read queue to see if there's anything there to read; if not, keep checking... */
            while ( (status = SI_CheckRXQueue(devHandle, &numBytesInQueue, &queueStatus)) != SI_SUCCESS )   // TODO:  don't assume it's either success or not
                printf("SI_CheckRXQueue() not successful...\n");
            if (queueStatus == SI_RX_READY)                             /* queue is ready to be read... */
            {
                printf("queueStatus is ready.\n");
                getQueueSuccess = 1;
            }
            else
            {
                printf("queueStatus is NOT READY:  %x.\n", queueStatus);
                getQueueSuccess = 0;
            }
        }

        printf("Reading from queue...\n");                              /* now read the queue, do error checks... */
        status = SI_Read(devHandle, &readBuffer, numBytesToRead, &numBytesReturned);
        if (status != SI_SUCCESS)    // TODO:  robustify this whole conditional
        {
            fputs("Something went wrong while reading from buffer.\n", stderr);
            exit(EXIT_FAILURE);
        }
        else if (numBytesReturned != numBytesWritten)
        {
            fputs("Number of bytes read doesn't match number of bytes requested.\n", stderr);
            exit(EXIT_FAILURE);
        }
    }
    else
        printf("SI_Write() not successful, returned %x.\n", status);
}


/*
 * GetAndPrintStatus() requests status from device and prints status for ADC, timer, and current ADC input.
 */
void GetAndPrintStatus(void)
{
    writeBuffer.reason = GET_STATUS;                                    /* let device know that we want status */
    WriteBuffer();                                                      /* write packet */
    
    /* print status received from device */
    printf("\n\n------- STATUS -------\n");
    printf("ADC status is:  %s\n", (readBuffer.adcStatus == ON) ? "ON" : "OFF");
    printf("Timer status is:  %s\n", (readBuffer.timerStatus == ON) ? "ON" : "OFF");
    printf("ADC input is from %s\n", (readBuffer.reason == USE_TEMP_SENSOR) ? "temperature sensor" : "analog input");
    printf("----- END STATUS -----\n\n");
}


/*
 * TimerEnable() enables or disables the timer.
 */
void TimerEnable(int enabled)
{
    if (enabled)
        writeBuffer.reason = TIMER_ON;                                  /* in packet, tell device to turn timer on... */
    else
        writeBuffer.reason = TIMER_OFF;                                 /* ... or off. */

    printf("Writing to buffer...\n");
    WriteBuffer();                                                      /* write packet */
    printf("Buffer written.\n");
}


/*
 * GetAndPrintADCResult() requests ADC results from the device.  Results are printed either one at a time, or in continuous mode.
 */
void GetAndPrintADCResult(int continuous)
{
    if (continuous)                                                     /* do you want a continuous stream of results? */
    {
        printf("Are you sure? (1/0)\n");                                /* confirm, since need ctrl+c to exit if yes */
        scanf("%d", &doContinuous);                                     /* get response; TODO:  make better */
        if (doContinuous == 1)
        {
            while(1)
            {
                writeBuffer.reason = READ_VALUE;                        /* tell device that we want the ADC result */
                WriteBuffer();                                          /* write packet */
                /* when WriteBuffer() returns, the packet sent from device has been read */
                /* put ADC0H and ADC0L values into one variable */
                adcResultCombined = (readBuffer.adcResultHigh << 4) + readBuffer.adcResultLow;
                fahrenheit = (adcResultCombined * 9 / 5) + 32;          /* convert result to degrees F */
                if (readBuffer.reason == USE_TEMP_SENSOR)               /* check which input was used for ADC, and print out appropriate form of result */
                    printf("\n\nADC result = %f degrees F\n", fahrenheit);
                else
                    printf("\n\nADC result = 0x%x\n", adcResultCombined);
            }
        }
    }
    if (!continuous)                                                    /* same as "continuous" mode, but done only once */
    {
        writeBuffer.reason = READ_VALUE;
        WriteBuffer();
        adcResultCombined = (readBuffer.adcResultHigh << 4) + readBuffer.adcResultLow;
        fahrenheit = (adcResultCombined * 9 / 5) + 32;
        if (readBuffer.reason == USE_TEMP_SENSOR)
            printf("\n\nADC result = %f degrees F\n", fahrenheit);
        else
            printf("\n\nADC result = 0x%x\n", adcResultCombined);
    }
}


/*
 * SwitchADCInput() switches ADC input from temperature sensor or analog input pin.
 */
void SwitchADCInput(unsigned char whichInput)
{
    writeBuffer.reason = whichInput;                                    /* indicate desired input */
    WriteBuffer();                                                      /* write packet */
}


/*
 * main() detects USBXpress devices and prompts user to pick desired device if found.
 * It prints device descriptor strings of chosen device.
 * The main program loop prints list of available options, and gets user input.
 */
int main(void)
{
    int choice = 0;                                                     /* choice from displayed options */

    status = SI_GetNumDevices(&numDevices);                             /* store number of devices into NumDevices; return status */
    if (status == SI_SUCCESS)                                           /* got number of devices */
    {
        printf("Got %d device(s).\n", numDevices);
        /* devPtr is a pointer to an array of DEVICE structs.  Allocate numDevices of DEVICEs. */
        DEVICE *devPtr = (DEVICE *)SafeMalloc(sizeof(DEVICE) * numDevices);

        for (devIdx = 0; devIdx < numDevices; ++devIdx)                 /* go through each device found... */
        {
            for (strType = 0; strType <= SI_RETURN_PID; ++strType)      /* go through each string type... */
            {
                /* get the product string for the current index and store into corresponding DEVICE's device string */
                status = SI_GetProductString(devIdx, devPtr[devIdx].devStrings[strType], strType);
                if (status != SI_SUCCESS)
                    printf("Couldn't get product string for device %d.\n", devIdx);
                else
                {
                    switch (strType)                                    /* ... and print each string type */
                    {
                        case SI_RETURN_SERIAL_NUMBER:
                            printf("Device %d:  Serial number is \"%s\".\n", devIdx, devPtr[devIdx].devStrings[strType]);
                            break;
                        case SI_RETURN_DESCRIPTION:
                            printf("Device %d:  Description is \"%s\".\n", devIdx, devPtr[devIdx].devStrings[strType]);
                            break;
                        case SI_RETURN_LINK_NAME:
                            printf("Device %d:  Link name is \"%s\".\n", devIdx, devPtr[devIdx].devStrings[strType]);
                            break;
                        case SI_RETURN_VID:
                            printf("Device %d:  Vendor ID is \"%s\".\n", devIdx, devPtr[devIdx].devStrings[strType]);
                            break;
                        case SI_RETURN_PID:
                            printf("Device %d:  Product ID is \"%s\".\n", devIdx, devPtr[devIdx].devStrings[strType]);
                            break;
                    }
                }
            }
        }
    }
    else if (status == SI_DEVICE_NOT_FOUND)                             /* device not found, exit */
    {
        printf("Device not found.\n");
        printf("numDevices = %d.\n", numDevices);
        printf("status = 0x%x.\n", status);

        exit(EXIT_SUCCESS);
    }
    else
        printf("Invalid parameter.\n");


    /* TODO:  prompt for which device want to use; for now, assume only 1 device */

    status = SI_Open((DWORD)0, &devHandle);                             /* open device and get handle */
    if ( (status == SI_DEVICE_NOT_FOUND) || (status == SI_INVALID_PARAMETER) )
    {
        fputs("Device not found or invalid parameter.\n", stderr);
        exit(EXIT_FAILURE);
    }
    else
        printf("\nHandle established.\n\n");

    
    while (1)                                                           /* main loop:  print options, get response */
    {
        PrintOptions();
        scanf("%d", &choice);
        switch (choice)
        {
            case GET_STATUS:                                            /* check status */
                GetAndPrintStatus();
                break;
            case TIMER_ON:                                              /* enable timer */
                TimerEnable(ON);
                break;
            case TIMER_OFF:                                             /* disable timer */
                TimerEnable(OFF);
                break;
            case READ_VALUE:                                            /* get ADC result */
                GetAndPrintADCResult(OFF);
                break;
            case EXIT:                                                  /* exit */
                TimerEnable(OFF);
                exit(EXIT_SUCCESS);
                break;
            case READ_CONTINUOUS:                                       /* get stream of ADC result values */
                GetAndPrintADCResult(ON);
                break;
            case USE_TEMP_SENSOR:                                       /* use temperature sensor as ADC input */
                SwitchADCInput(USE_TEMP_SENSOR);
                break;
            case USE_ANALOG_INPUT:                                      /* use pin P3.5 as ADC input */
                SwitchADCInput(USE_ANALOG_INPUT);
                break;
            default:
                printf("You entered %d.  That wasn't a valid option.\n", choice);
                printf("Please choose again.\n\n");
        }
    }

    return(EXIT_SUCCESS);
}