#ifdef MERGELATER
/*
 * ph_rcvr.c
 * Library for back-end receiver of pH data, e.g. back-end mpHox receiving pH data collected by front end mFET
 *
 *  Created on: Nov 19, 2021
 *      Author: Irene Hu
 */

#include "ph_back.h"


//******************************************************************************
//* Initialize and UART wrapper
//* references to uart are in this section only
//******************************************************************************

void phb_init(void)
// similar to other devices, init function now only sets up the uart port
{
    uart4_init();

    uart4_setup(PH_BAUD, 16000000);

    uart4_echo_set(0);
}

void phb_openphf(void)
// powers on phf
// copied from ctd_base, since we're using ctd port
{
#if BOARD_MPHOX >= 1   || BOARD_MSC == 2      // was BOARD_MFET >= 2  || BOARD_MPHOX >= 1
    // turn on Vbat supply on J8 MPHOX
    //ROM_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_0, 0xFF);    // J8 CTDpwr PD0 Power on
    pwr_ctd_on();       //defd in board_util.c
#endif

}

void phb_closephf(void)
// powers off phf
// copied from ctd_base, since we're using ctd port
{

#if BOARD_MPHOX >= 1  || BOARD_MSC == 2
    //ROM_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_0, 0x00);    // J8 CTDpowr PD0 Power off
    pwr_ctd_off();
#endif

}


unsigned char _phb_getc(void)
{
    return(uart4_rx_byte());
}

void _phb_putc(unsigned char c)
{
    uart4_tx_byte(c);
}

int phb_rxBytesAvail(void)
{
    return(uart4_rx_bytes_avail());
}

void _phb_flushRx(void)
{
    uart4_rx_bufr_flush();
}

int _phb_write(const char *pcBuf, uint32_t ui32Len)
{
    return(uart4_write(pcBuf, ui32Len));
}


//******************************************************************************
//* Public Methods - User Menu
//******************************************************************************

void phb_comm_test(void)
// a test menu for mFET-mpHOX ph comms test
// adapted from adv test menu
{
    uprintf("\nWelcome to the ph comms test menu!  woof!\n");
    uprintf("\nCTD port is configured for pH and will not be compatible w/ CTD again until restart.\n");
    // init is called in test.c, which calls this menu

    int retVal;
    time_t timer;
    time_t currentTime;
    struct tm *time_struct;
    char input;
    char buff[256];

    // for case 2
    double totCtTime_min;
    char fn_append[MAX_FILENAME];
    int charCount;

    // Copied from test menu loop
    while(1)
    {
        uprintf("\n\n\npH Menu:\n");
        uprintf("1 -- Measure once\n");
        uprintf("2 -- Measure continuously\n");
        uprintf("3 -- Power on front-end sensor\n");
        uprintf("4 -- Power off front-end sensor\n");
        uprintf("5 -- Stop continuous measurement (error case)\n");
        uprintf("9 -- Exit to Test Menu\n");
        uprintf("\nEnter Selection [9]: ");

        UARTFlushRx();
        timer = 30000 + ROM_HibernateRTCGet();  //30 second

        while(1)
        {
            if( UARTRxBytesAvail() )
            {
                input = get_key();
                break;
            }

            //if( timer <= ROM_HibernateRTCGet() ) sleep(IDLE, 0);
        }

        switch(input)
        {
            case '1':
            {   // measure once

                _phb_flushRx(); // error in comms can cause data to come from phf that wasn't processed

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                uprintf("[%s] Sending command to measure once\n", buff);
                // measure function will print the rest of the narration

                struct pH_ptdata pHDatStruct;

                retVal = phb_measOnce(&pHDatStruct);

                if (retVal == 0) {

                    retVal = ph_printpHDataHdr(NULL, 1);
                    uprintf("\n");

#ifdef PRINT_PH_TIMING
                    uprintf("%.4f\t", difftime(ROM_HibernateRTCGet(), currentTime));
                    // difftime for subtracting time_t
                    // https://stackoverflow.com/questions/45614595/how-to-subtract-two-time-t-values-difftime-returns-double-instead-time-t
#endif

                    retVal = ph_printpHData(&pHDatStruct, NULL, 1);
                } else {
                    currentTime = ROM_HibernateRTCGet();
                    time_struct = localtime(&currentTime);
                    strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);
                    uprintf("\n[%s] Error getting one measurement.\n", buff);
                }

                break;
            }

            case '2':
            {
               // copied from ADV menu, use same structure

               while(1) {
                   uprintf("\nEnter measurement duration (min): ");
                   if(getUserInput(buff, 20)) // assume this blocks until something is received?
                   {
                       if(sscanf(buff, "%lf", &totCtTime_min) == 1)
                       {
                           if(totCtTime_min <= 0)
                           {
                               uprintf("\nMeasurement duration must be greater than 0.\n");
                               continue;
                           }

                           break;
                       }
                       else // sscanf returned something besides 1
                       {
                           uprintf("\nPlease enter a single number.\n");
                           continue;
                       }
                   }
                   //else break; // no default value, so take this break out
               }

               while(1) { // no default values, so keep asking until get a value

                   uprintf("\nEnter data file name (5 chars, no extension): ");
                   if (charCount = getUserInput(buff, MAX_FILENAME)) // if >0 chars received
                   {
                       if (charCount > 5) buff[5] = 0; // force it to 5
                       strcpy(fn_append, buff);

                       if (strcmp(fn_append,"NULL") == 0) // secret code to test out not writing to file
                           uprintf("\nWill not write to file.\n");
                       else
                           uprintf("\nFile append sequence: %s\n", fn_append);

                       break;
                   }
               }

               _phb_flushRx(); // error in comms can cause data to come from phf that wasn't processed

               currentTime = ROM_HibernateRTCGet();
               time_struct = localtime(&currentTime);
               strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

               uprintf("[%s] ", buff);
               // measure function will print the rest of the narration

               if (strcmp(fn_append,"NULL") == 0)
                   retVal = phb_measCont(16, totCtTime_min, NULL, 1);
               else
                   retVal = phb_measCont(16, totCtTime_min, fn_append, 1);

               currentTime = ROM_HibernateRTCGet();
               time_struct = localtime(&currentTime);
               strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

               if (retVal == 1)
                   uprintf("\n[%s] Exited with errors.\n", buff);
               else
                   uprintf("\n[%s] Finished measuring and recording data.\n", buff);

               break;
            }

            case '3':
            {   // power on phf

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                uprintf("[%s] Powering on front-end sensor\n", buff);

                phb_openphf();
                break;
            }

            case '4':
            {   // power of phf

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                uprintf("[%s] Powering off front-end sensor\n", buff);

                phb_closephf();
                break;
            }

            case '5':
            {   // exit continuous measurement - for an error case

                _phb_flushRx(); // error in comms can cause data to come from phf that wasn't processed

                currentTime = ROM_HibernateRTCGet();
                time_struct = localtime(&currentTime);
                strftime(buff, sizeof(buff),TIMESTAMP_FMT, time_struct);

                uprintf("[%s] Sending command to front-end sensor to stop continuous measurement\n", buff);

                retVal = phb_stopMeas();
                if (retVal == 1)
                    uprintf("\n[%s] Failed.\n", buff);
                else
                    uprintf("\n[%s] Ack received.\n", buff);
                break;
            }

            default: // Exit
                uprintf("\nPress enter to bring up main menu\n");
                return;

        } // end switch
    } // end while for menu printing
}


//******************************************************************************
//* Public Methods - Measure
//******************************************************************************

int phb_measOnce(struct pH_ptdata *pHDatStruct)
// tells front end pH sensor to measure once, and writes data into the passed-in structure
// returns 0 if success, 1 if not
{
    _phb_flushRx(); // reset
    char cmd[] = PH_MEASONCE;
    if (_phb_sendTwoCharCmd(cmd) == 1) return 1;

    if (_phb_receiveAck() != 0) return 1;

    // wait a reasonable amount of time for a response
    // cmd timeout is at 5s right now, so that should be more than enough time

    pHDatStruct->id = _phb_sortIncomingDataStruct();
    if (pHDatStruct->id != PH_IDPTD) {
        uprintf("Did not receive correct ID byte. Got %#x\n", pHDatStruct->id);
        return 1;
    }

    pHDatStruct->sync = PH_SYNCBYTE;

    // there is no timeout in the getdata fxn, may want to check for bytes available here
    phb_readpHThermData(pHDatStruct);

    return 0;
}


int phb_measCont(double measFreq_Hz, double measDuration_min, char* fn_append, int txFlg)
// measure at measFreq_Hz Hz for measDuration_min minutes
// pass in a character array containing a <=5-char append string sequence
// consistent w/ ADV code, not passing in FIL* to save stack space (even tho only one file here)
// pass NULL for fn_append to not write to file
// pass 1 for txFlg to print to screen
// return 0 if success, 1 if not
{
    int writeToFile;
    if (fn_append == NULL) writeToFile = 0;
    else writeToFile = 1;

    if (!writeToFile & !txFlg) // well i don't know what you're doing in this case
        return 1;

    //uint64_t measDuration_ms = (uint64_t)(measDuration_min * 60 * 1000);
    //uprintf("Meas duration in ms: %" PRIu64 "\n", measDuration_ms); // debug help
    unsigned long measDuration_ms = (unsigned long)(measDuration_min * 60 * 1000); // unsigned long to match timer output, to which it will be compared
    uprintf("Meas duration in ms: %lu\n", measDuration_ms); // debug help
    double measPer_ms = 1000/measFreq_Hz;
    uint64_t count = 0; //  timing is calculated off a total count; allows self-correction for small inaccuracies in timing
    // esp necessary bc eg 16 Hz is 62.5 ms, and we're stuck with whole ms
    int numerrs = 0;

    struct pH_ptdata pHDatStruct;
    pHDatStruct.sync = PH_SYNCBYTE; // set for checksum calc

    // polling for cancel
    int inChar;

    // think i'm not allowed to declare these under an if
    char filename[MAX_FILENAME];
    FIL ph_ofs; // file object
    FRESULT fresult;

    unsigned long elapsedTime;

    if (writeToFile) {

        if(sys_data.app_cfg[APPCFG_MICROSD_ENABLE] != 1)   // copied from Thom fix 6 Aug 2021
        {
            uprintf("Some lil flag is not set properly and the microsd is not enabled. Will not be written to file.\n");
            if (!txFlg) return 1;
        } // rather than only opening files etc if condition is met, just quit now if it isn't

        sprintf(filename, "ph_%s.txt", fn_append);
        fresult = f_open(&ph_ofs, filename, FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
        if(fresult != FR_OK)
        {
            // != FR_OK
            uprintf("File %s f_open error: %s\n", filename, StringFromFresult(fresult));
            //error_store("f_open error");
            if (!txFlg) return 1;
            writeToFile = 0;
        }
        else  // FILE is open
        {
            uprintf("Data will be written to file named %s.\n", filename);
            // Seek to the end, to append our file
            fresult = f_lseek(&ph_ofs, ph_ofs.fsize);
            if(fresult != FR_OK)
            {
                uprintf("File %s f_lseek error: %s\n", filename, StringFromFresult(fresult));
            }
        }
    }

    uprintf("Initiating pH measurements for %.2f minutes at %g Hz (%g ms between measurements).\n", measDuration_min, measFreq_Hz, measPer_ms);

    if (measFreq_Hz >= PH_ADCFREQ_VRS) { // for now, set ADC polling at 20 Hz.  it can't go faster than that
        uprintf("Warning: frequency %.2f Hz is faster than pH ADC frequency %d Hz; measurements will duplicate.\n", measFreq_Hz, PH_ADCFREQ_VRS);
    }

    if (phb_startMeas() == 1) {
        uprintf("Failed to start measurement; exiting.\n");
        return 1;
    }

    // pause for a moment
    ROM_SysCtlDelay(MILLISECOND*200);

    // sadly i can't set &ph_ofs NULL if not writing to file and just pass it through, since it's a pointer.
    // but anyways also need to add an extra field for this function's count

    if (writeToFile) {
        sd_fprintf(&ph_ofs, "phb count\t");
        if (txFlg) uprintf("phb count\t");
        ph_printpHDataHdr(&ph_ofs, txFlg);
        sd_fprintf(&ph_ofs, "\n");
        if (txFlg) uprintf("\n");
    } else {
        uprintf("phb count\t");
        ph_printpHDataHdr(NULL, txFlg);
        uprintf("\n");
    }

    Timer_1ms(TRUE); // reset timer

    while( Timer_1ms(FALSE) <= measDuration_ms) {
        if (Timer_1ms(FALSE) >= (count+1) * measPer_ms) { // use count+1 bc we ask for meas after it has happened
            _phb_flushRx(); // bad previous measurement seems to leave some characters in the buffer
            if (phb_getMeas(&pHDatStruct) == 1) {
                uprintf("Failed to get pH data %" PRIu64 "; waiting for next point\n", count);
                numerrs++;
            } else { // got the measurement
                if (writeToFile) sd_fprintf(&ph_ofs, "%" PRIu64 "\t", count);
                if (txFlg) uprintf("%" PRIu64 "\t", count);

#ifdef PRINT_PH_TIMING
                if (writeToFile) sd_fprintf(&ph_ofs, "%.4f\t",(double)Timer_1ms(FALSE)/1000);
                if (txFlg) uprintf("%.4f\t",(double)Timer_1ms(FALSE)/1000);
#endif
                if (writeToFile) {
                    ph_printpHData(&pHDatStruct, &ph_ofs, txFlg);
                    sd_fprintf(&ph_ofs, "\n");
                    if (txFlg) uprintf("\n");
                } else {
                    ph_printpHData(&pHDatStruct, NULL, txFlg);
                    uprintf("\n");
                }
            }

            count++; // increments whether or not successfully got the measurement
        } // end of this measurement - go back to top of loop, wait for next measurement time

        // poll for a ctrl-X to quit - copied partly from optode code
        // polls here after a measurement, or if waiting for next measurement
        if(UARTRxBytesAvail())
        {
            inChar = UARTgetc();
            if(inChar == CTRL_X)
            {
                uprintf("Ctrl-x received, aborting measurement.\n");
                break;
            }
        }


    } // end of measurement duration

    if (phb_stopMeas() == 1) {
        uprintf("Error stopping measurement.\n"); // but keep going to close file etc
    }

    elapsedTime = Timer_1ms(FALSE);

    uprintf("Total %" PRIu64 " pH data received, plus %d readin errors, over a period of %.2f min\n", count-numerrs, numerrs, ((double)(elapsedTime))/60000);
    //uprintf("Total %" PRIu64 " pH data received, plus %d readin errors, over a period of %lu ms\n", count-numerrs, numerrs, elapsedTime);

    // close files
    if (writeToFile) {
        fresult = f_close( &ph_ofs );
        if(fresult != FR_OK)
        {
            uprintf("f_close error: %s\n", StringFromFresult(fresult));
            //error_store("f_close error");
        }
    }

    return 0;

}


//******************************************************************************
//* Public Methods
//******************************************************************************


int phb_readpHThermData(struct pH_ptdata *pHDatStruct)
// read a pH+thermistor data, starting from offset 2 (assumes it's been properly ID'ed)
// stores in pHDatStruct
// unlike ADV, no passing in checksum - it's all calculated here
// returns 0 if success, 1 if fail
{
    if (!pHDatStruct) return 1;

    int i, checksum;

    // assign sync byte and ID before passing in the struct pointer
    // no need to keep reassigning it, since i intend to reuse it
    // (at least for now... having an array of them and printing all out together would be different)

    // try to read it all in one go
    // note: no timeout
    char *iterator = (char *)pHDatStruct;
    for (i=2; i<sizeof(struct pH_ptdata); i++) { // start at 2 bc already read in synch and id
        iterator[i] = _phb_getc();
    }

    checksum = ph_calcChecksum(pHDatStruct);

    if (checksum != pHDatStruct->checksum) // checksum was read in as a block

        pHDatStruct->checksum = 1; // failed
    else
        pHDatStruct->checksum = 0; // ok
    // reuse checksum field for this - for now, it's planned to be used by front to store checksum

    return 0;
}


int phb_startMeas(void)
// tells front end pH to start measuring
// no longer specifying frequency - will operate in a polled mode
// (see if it's fast enough...)
// returns 0 if success, 1 if not
{
    char cmd[] = PH_MEASCONT;
    if (_phb_sendTwoCharCmd(cmd) == 1) return 1;

    if (_phb_receiveAck() != 0) return 1;

    return 0;
}


int phb_getMeas(struct pH_ptdata *pHDatStruct)
// poll for and receive one measurement from phf, and writes data into the passed-in structure
// assumes phf is in continuous measurement status (called phb_startmeas)
// timeout needs to be really fast because it wants data at eg 16 Hz, and needs to process ADV data too
// set synch byte of struct in calling fxn - not doing it here, to save time
// returns 0 if success, 1 if not
{
    char cmd[] = PH_GETMEAS;
    if (_phb_sendTwoCharCmd(cmd) == 1) return 1;

    unsigned long time1;
    time1 = Timer_1ms(FALSE); // allows timer to be in use

    while (phb_rxBytesAvail() < 2) {
        if ( Timer_1ms(FALSE) - time1 > PHGETMEAS_TIMEOUT_MS) {
            uprintf("Get meas cmd: Bytes not available on port.\n");
            return 1;
        }
    }

    pHDatStruct->id = _phb_sortIncomingDataStruct();
    if (pHDatStruct->id != PH_IDPTD) {
        uprintf("Did not receive correct ID byte. Got %#x\n", pHDatStruct->id);
        if(phb_rxBytesAvail()) {
            uprintf("Flush buffer: ");
            while (phb_rxBytesAvail() > 0) {
                uprintf("%#x ", _phb_getc());
            }
            uprintf("\n");
        }
        return 1;
    }

    // there is no timeout in the getdata fxn, may want to check for bytes available here
    phb_readpHThermData(pHDatStruct);

    return 0;

}


int phb_stopMeas(void)
// tells front end pH sensor to stop measuring
// returns 0 if success, 1 if not
{
    char cmd[] = PH_STOPMEAS;
    if (_phb_sendTwoCharCmd(cmd) == 1) return 1;

    if (_phb_receiveAck() != 0) return 1;

    return 0;
}


//******************************************************************************
//* Private Methods
//******************************************************************************

int _phb_sendTwoCharCmd(char *cmdToRcvr)
// send a two-character command to receiver (mfet).
// cmdToRcvr is a 2-char null-terminated string (copy ADV convention)
// return 0 for success, 1 for fail (only fails if input format was incorrect)
{
    // check the input
    if (cmdToRcvr[2] != '\0') return 1;

    _phb_write(cmdToRcvr, 2);

    return 0;
}

int _phb_receiveAck()
// receive response of Ack or Nack
// return 0 if Ack, 1 if Nack, and 2 if something else
{
    Timer_10ms(TRUE); // reset 10ms tick - assumes timer not being used for anything else, otherwise need 2 time variables and calc a difference

    while (phb_rxBytesAvail() <= 0) {
        if ( Timer_10ms(FALSE) > ACK_TIMEOUT_MS/10) {
            uprintf("Timed out waiting for ACK from mfet.\n");
            return 2;
        }
    }

    /*
    char char1 = _phb_getc();
    char char2 = _phb_getc();

    if (char1 == PH_ACK && char2 == PH_ACK) return 0;
    if (char1 == PH_NACK && char2 == PH_NACK) return 1;
    */

    char rcvd[2];
    rcvd[0] = _phb_getc();
    rcvd[1] = _phb_getc();

    if (rcvd[0] == PH_ACK && rcvd[1] == PH_ACK) return 0;
    if (rcvd[0] == PH_NACK && rcvd[1] == PH_NACK) {
        uprintf("Received NACK from pH front.\n");
        return 1;
    }

    uprintf("Received something besides ACK or NACK from pH front.\n");
    return 2;
}


int _phb_sortIncomingDataStruct()
// reads incoming two bytes, meant to correspond to start of an incoming data structure
// first byte should be sync byte
// returns an ID # (byte 2) to identify which structure is coming in, or -1 if sync byte didn't match or either byte didn't read properly
// keep same structure as ADV because it works - sync byte allows syncing data structure, ID byte for future expansion
// no longer passing checksum around; sync and ID are part of struct and calculated all within parsing fxn
{
    unsigned char recByte, recByte2;

    // first byte is sync byte- same for all
    // port from c++ - it no longer returns -1 if no byte, it just blocks.  so instead, first check for bytes available
    // do both bytes together, and allow a timeout - this is often called right after sending the command
    // reasonable that there would be some dead time
    unsigned long time1;
    time1 = Timer_10ms(FALSE); // allows timer to be in use

    while (phb_rxBytesAvail() < 2) {
        if ( Timer_10ms(FALSE) - time1 > CMD_TIMEOUT_MS/10) {
            uprintf("Bytes not available on port.\n");
            return -1;
        }
    }

    recByte = _phb_getc();
    if (recByte != PH_SYNCBYTE) {
        uprintf("Did not receive correct sync byte. Got %#x\n", recByte);
        return -1;
    }

    recByte2 = _phb_getc();

    return recByte2;

}
#endif
