/*
 * apf_test.c
 *
 *  Created on: Mar 12, 2025
 *      Author: tm
 */

#include "board_util.h"
#include "system.h"
#include "sleep.h"
#include "string.h"
#include "uartstdio.h"
#include "user_io.h"
#include "microsd.h"
#include "fatfs/source/ff.h"
#include "uart3.h"
#include "serial.h"
#include "sample.h"
#include "config.h"
#include "test.h"
#include "i2c_pcal64xx.h"
#include "i2c_bme280.h"
#include "i2c_ina260.h"
#include "i2c_fram.h"
#include "apf_init.h"
#include "apf_util.h"
#include "apf.h"
#include "apf_math.h"
#include "apf_numbr.h"
#include "apf_util.h"
#include "mms_test.h"
#include "mms_spec.h"
#include "serial_cmd_fxn.h"
#include "main.h" // need for sys_data


// next four are declared in mms_spec.c (to be written into?), might as well reuse but can't see a reason they need to be global extern
extern uint16_t SpecData[256*1];
extern uint16_t DarkData[256*1];
//extern double ZeroData[260];
extern double AbsData[260];
extern float DataMean, DataStdDev; // originally in apf_util.c
extern unsigned char prnBuf[SIZEOF_PRNBUF+4]; // there's one in sample.c, but for some reason everyone uses and nobody declares

// three functions for Ben's hardware checkout
// Ben + IH 4/16/25
// are in this file following an original organization vision from Thom which may have since changed
// may want to reorganize;

int apf_test_probe()
// adapted from MSC1 code apf_menu.c case 'B' as well as use of mms_lampPwrOnUsingSpec from mms_takeIsusSample() in mms_spec.c
// No more toggle. Turns it on at the start and off at the end, regardless of initial state
{
    int mmsDbgFlg = 0;

    if(mmsDbgFlg == 1) { uprintf("Function apf_test_probe() in apf_test.c");  prn_nl();  }

    uprintf("Probe optimization test....make sure there is a covered probe!"); prn_nl();

    if( mms_specPwrOn() != TRUE ) {
        return( ERROR );
    }

    if(mmsDbgFlg == 1) { uprintf("Spec power turned on, calling mms_lampPwrOnUsingSpec");  prn_nl();  }

    mms_lampPwrOnUsingSpec( SpecData, TRUE ); // 250ms exposure time is hardcoded in here
    // after calling mms_lampPwrOnUsingSpec, max is stored in DataMean extern var and min is stored in DataStdDev
    // w/ TRUE argument, function will print out max already so we don't actually need them here
    // can check with Ben someday if he wants a different output format
    //ref_ch_mean = DataMean;     //value set in LampPwrOnUsingSpec() = MaxPixelInten
    //ref_ch_sd   = DataStdDev;   //value set in LampPwrOnUsingSpec() = MinPixelInten

    mms_lampPwrOff();
    mms_specPwrOff();

    if(mmsDbgFlg == 1) { uprintf("powered off lamp and spec.");  prn_nl();  }

    return( OK );

}


int apf_test_spec(uint8_t testNum)
// adapted from MSC1 code apf_menu.c case 'E' as well as this code's mms_takeIsusSample() in mms_spec.c
{
    uint16_t *dataptr;
    int mmsDbgFlg = 0;
    int i, j;

    if(mmsDbgFlg == 1) { uprintf("Function apf_test_spec() in apf_test.c");  prn_nl();  }

    uprintf("Test spec function! Outputs results from specified number of dark scans."); prn_nl();

    if(testNum == 0) { // if forget to input argument, comes in as 0
        uprintf("  No or invalid number entered, defaulting to single dark scan."); prn_nl();
        testNum = 1;
    } else if(testNum > 10) {
        uprintf("  Please input an integer no larger than 10."); prn_nl();
        return( ERROR );
    }
    uprintf("  Dark scans to be taken = %u", (unsigned int)testNum); prn_nl();

    if( mms_specPwrOn() != TRUE ) {
        return( ERROR );
    }
    // mms_scanSpectra turns the spec on, too.  Follow model of takeIsusSample() and do it here too
    if(mmsDbgFlg == 1) { uprintf("Spec power turned on");  prn_nl();  }

    // need to dynamically allocate data array bc unknown # tests
    dataptr = calloc((size_t) (256 * testNum), sizeof(uint16_t));
    if (dataptr == NULL) {
        uprintf("Error allocating memory"); prn_nl();
    }

    if(mmsDbgFlg == 1) { uprintf("Calling mms_scanSpectra DARK scan");  prn_nl();  }

    // do as many dark scans as requested, don't print or write
    if( mms_scanSpectra( testNum, dataptr, DARKCURRENT_SCAN, 0, 0 ) != TRUE ) {
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed, apf_test_spec() in apf_test.c");  prn_nl();  }
        mms_lampPwrOff();
        mms_specPwrOff();
        free(dataptr);
        return( ERROR );
    }

    // print formatted (row-column) data, start at lower wavelengths and work up
    for (i = 0; i < 256; i++) {
        wdog_pet();
        for (j = 0; j < testNum; j++) {
            if (j == 0) {
                uprintf("%.5lf,", apfArray.LambdaData[i + 1]);
                //uprintf("%d,", i); // test
            }
            if (j == (testNum - 1)) {
                uprintf("%hu", dataptr[i + (256 * j)]); prn_nl();
            } else {
                uprintf("%hu,", dataptr[i + (256 * j)]);
            }
            UARTFlushTx(false); // replace fflush(stdout);
        }
    }
    // deallocate array
    free(dataptr);

    mms_lampPwrOff();
    mms_specPwrOff();

    return( OK );


}
//Test lamp function!
//  Total Lamp On Time  = 0 Msec
//  Lamp On/Off Cycles  = 0
//  System Batt Voltage = 14.991
//  System Load Current =  0.040

int apf_test_lamp()
// adapted from MSC1 IsusLampTest() in apf_menu.c ('F') as well as this code's
//   mms_takeIsusSample() in mms_spec.c and Thom's uSD hammer function in zone_thom.c
// there is a shell for IsusLampTest in mms_spec.c as well
{
    int mmsDbgFlg = 0;
    int i;
    float mean1; // , mean2;
    float fSysVolt, fSysAmp;
    float fBrdPressure, fBrdTempC;
    uint32_t uiHumidity;
    struct tm *tp;
    uint32_t strIndx;

    if(mmsDbgFlg == 1) { uprintf("Function apf_test_lamp() in apf_test.c");  prn_nl();  }

    uprintf("Test lamp function!"); prn_nl();
    uprintf("  Total Lamp On Time  = %u mSec", apf_fram.isus_lampOn_mSec);  prn_nl();
    uprintf("  Lamp On/Off Cycles  = %u", apf_fram.isus_lampOn_ctr); prn_nl();

    ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );
    prn_nl(); uprintf("  System Batt Voltage = %6.3f", fSysVolt); prn_nl();
    uprintf("  System Load Current = %6.3f", fSysAmp); prn_nl();

    if(bme280_read_data() == 1) {
        fBrdPressure = bme280_get_fPressure();
        fBrdTempC = bme280_get_fTemperature();
        uiHumidity = bme280_get_uiHumidity();
    } else {
        fBrdPressure = NAN;
        fBrdTempC = NAN;
        uiHumidity = 0;
    }

    prn_nl();
    uprintf("  Housing Temperature = %5.2f C", fBrdTempC); prn_nl();
    uprintf("  Housing    Humidity = %u %%", uiHumidity);  prn_nl();
    uprintf("  Housing    Pressure = %5.2f mBar", fBrdPressure); prn_nl();

    if (apf.isus_lamp_pwr == TRUE) {
        mms_lampPwrOff();
        prn_nl(); uprintf("Lamp was turned off for initial DarkCurrent."); prn_nl(); // this function is so wordy; should i hide these behind dbg flag?
        delay_msec(1000);;           //wait a sec...
    }

    // skip Luke's spec power check/turnon code in favor of our normal one
    // most likely the spec is not already on.
    if( mms_specPwrOn() != TRUE ) {
        return( ERROR );
    }
    if(mmsDbgFlg == 1) { uprintf("Spec power turned on");  prn_nl();  }

    uprintf("Taking DarkCurrent Scan with DU Lamp OFF"); prn_nl();
    if( mms_scanSpectra( 1, DarkData, DARKCURRENT_SCAN, 0, 0 ) != TRUE ) { // neither print nor write; used to print, but it seems unnecessary
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed");  prn_nl();  }
        mms_specPwrOff();
        return( ERROR );
    }
    mean1 = DataMean; // written to by mms_scanSpectra
    // stdev1 = DataStdDev;

    uprintf("Turning lamp on."); prn_nl();
    mms_lampPwrOnUsingSpec( SpecData, TRUE );

    uprintf("Taking Spectra Scan"); prn_nl();
    if( mms_scanSpectra( 1, SpecData, INTENSITY_SCAN, 0, 0 ) != TRUE ) {   // neither print nor write; used to print, but it seems unnecessary
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra fail, ret err");  prn_nl();  }
        mms_lampPwrOff();   //LampPwrOff();
        mms_specPwrOff( );  //SpecPwrOff( SpecPort );
        return( ERROR );
    }
    //mean2 = DataMean;
    //stdev2 = DataStdDev;
    // not sure why we saved these as they are not used anywhere

    // no more shutter; lamps are now ordered without shutter
    /*LampCloseShutter();

    printf("Taking DarkCurrent Scans with DU Lamp ON\n");
    ScanSpectra(SpecPort, stdout, 1, LampOnDC, DARKCURRENT_SCAN);
    mean3 = DataMean;
    stdev3 = DataStdDev;*/

    mms_lampPwrOff(); // should we delay here too?

    // why do we even need this next bit, doesn't seem to get used anywhere
    /*uprintf("Taking DarkCurrent Scan with DU Lamp OFF"); prn_nl();
    if( mms_scanSpectra( 1, LampOffDC, DARKCURRENT_SCAN, 1, 0 ) != TRUE ) { // prints, doesn't write
        if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed");  prn_nl();  }
        mms_lampPwrOff();
        mms_specPwrOff();
        return( ERROR );
    }*/

    mms_specPwrOff();
    if(mmsDbgFlg == 1) { uprintf("power off lamp and spec, calculating...");  prn_nl();  }

    if (apfArray.EcoefsCnt == 256) {
        CalcAbs(&apfArray.ZeroData[0], &SpecData[0], &AbsData[0], mean1, 256);
        // CalcAbs(&ZeroData[0], &SpecData[0], &AbsData[0], mean1, 256); // MSC1
        // CalcAbs(&ZeroData[apf_eeprom.isus_pixel_beg-1], &SpecData[apf_eeprom.isus_pixel_beg-1], &AbsData[0], dc_mean, apf_eeprom.isus_fit_pixels); // mms_takeIsusSample()
        //CalcAbs(&ZeroData[apf_eeprom.isus_pixel_beg-1], &SpecData[apf_eeprom.isus_pixel_beg-1], &AbsData[0], mean1, apf_eeprom.isus_fit_pixels);
    }

    tp = get_RTC_time(); // t = localtime(&data_time);
    sprintf(apf_eeprom.logIsus, "//ISUS//lamptest.dat"); // warning, I'm just writing over this apf variable!!  lamptest wants to write to file

    fram_apfArray_store();
    eeprom_apf_cfg_store();

    strIndx = 0;
    strIndx += sprintf((char*)&prnBuf[strIndx], "A,%02d/%02d/%02d %02d:%02d:%02d",
                tp->tm_mon+1, tp->tm_mday, tp->tm_year+1900, tp->tm_hour, tp->tm_min, tp->tm_sec);
    strIndx += sprn_nl((char*)prnBuf, strIndx);


    //strIndx += sprintf((char*)&prnBuf[strIndx], "#WAVELENGTH,INTENSITY,DARK_CURRENT,ABSORBANCE\r\n");
    strIndx += sprintf((char*)&prnBuf[strIndx], "#WAVELENGTH,INTENSITY,DARK_CURRENT,ABSORBANCE");
    strIndx += sprn_nl((char*)prnBuf, strIndx);
    appendIsusProfileDataToSDCard((char*)prnBuf);      //exercise the fxn for writing ISUS log
    // Luke gave an option to display or not; we'll just display it
    UARTwrite((const char*)prnBuf, strIndx);
    UARTFlushTx(false); // replace fflush(stdout);

    for (i = 0; i < 256; i++) {
        wdog_pet();
        if (apfArray.EcoefsCnt != 256)
            AbsData[i + 1] = -1.0;
        //uprintf("%d,",i);
        strIndx = sprintf((char*)prnBuf, "%.5f,%hu,%hu,%.5f", apfArray.LambdaData[i + 1], SpecData[i], DarkData[i], AbsData[i + 1]);
        strIndx += sprn_nl((char*)prnBuf, strIndx);
        appendIsusProfileDataToSDCard((char*)prnBuf);
        UARTwrite((const char*)prnBuf, strIndx);
        UARTFlushTx(false);
    }

    UARTFlushTx(false);

    return( OK );
}


// IH 5/9/25 this is the 'baa' command from MSC1
// associated MSC1 function "IsusAcquireData(FILE *fptr)" in apf_menu.c
// current implementation will loop until keypress with 1 dark scan followed by litScansPerDark light scans
// IH modifying 9/16/25 for isus fast sample, add printFlg and writeFlg
// interval_s is sampling period in s; if a value is input that is too small, it will sample as fast as possible
// offBtwnScansFlg: set high to turn off lamp between scans with mandatory 500ms wait
// may not want to just leave lamp on, in case its signal starts to decay over time
// if writeFlg is set, will write to the prof file stored in memory
int cmd_scan_spectra(uint8_t litScansPerDark, uint16_t interval_s, int offBtwnScansFlg, int printFlg, int writeFlg)
{
    float fSysVolt, fSysAmp, fBrdPressure, fBrdTempC;
    uint32_t uiHumidity;
    uint8_t loopctr;
    int mmsDbgFlg = 0;
    uint32_t strPtr;
    char key;
    uint32_t tickStart, tickLast, tickThis, tickTemp;

    tickStart = Timer_1ms();

    //if(mmsDbgFlg == 1) { uprintf("Function cmd_scan_spectra() in apf_test.c");  prn_nl();  }

    if (interval_s >= sys_data.autoSleepSec + apf_eeprom.isus_spec_period/1000) {
        // found first variable in apf_m2m_mode_set in serial_cmd_fxn.c, hopefully denotes how long before falls asleep.
        // found second variable in mms_spec.c
        uprintf("Interval between samples is too long.  System risks falling asleep."); prn_nl();
        return( ERROR );
    }

    if(litScansPerDark == 0) { // if forget to input argument, comes in as 0
        uprintf("  No or invalid argument entered for number of light scans between dark, defaulting to 10."); prn_nl();
        litScansPerDark = 10;
    }

    uprintf("ISUS fast acquisition. Time between scans is %us. Will output one dark scan for every %u light scans. ", interval_s, litScansPerDark);
    if (offBtwnScansFlg) {
        uprintf("Lamp will turn off between light scans for 500ms." );
    } else {
        uprintf("Lamp will stay on between dark scans. ");
    }
    prn_nl();

    if( mms_specPwrOn() != TRUE ) {
            return( ERROR );
    }
    if(mmsDbgFlg == 1) { uprintf("Spec power turned on");  prn_nl();  }

    // MSC1 turns on lamp here
    //print_write_isusdura_header(printFlg, writeFlg, ISUS_SENSOR_MODE, &dat_file);  // was OutputIsusHeader(dat_file); defd in apf_util.c
    if (printFlg) apf_print_isus_header(); // serial_cmd_fxn.c
    // header ONLY prints to screen. For write to file, it's written with creation of prof file

    ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );
    strPtr = 0;
    strPtr += sprn_nl((char*)prnBuf, strPtr);
    strPtr += sprintf((char*)&prnBuf[strPtr], "H, System Batt Voltage , %6.3f", fSysVolt);
    strPtr += sprn_nl((char*)prnBuf, strPtr);
    strPtr += sprintf((char*)&prnBuf[strPtr], "H, System Load Current , %6.3f", fSysAmp);
    strPtr += sprn_nl((char*)prnBuf, strPtr);

    if(bme280_read_data() == 1) {
        fBrdPressure = bme280_get_fPressure();
        fBrdTempC = bme280_get_fTemperature();
        uiHumidity = bme280_get_uiHumidity();
    } else {
        fBrdPressure = NAN;
        fBrdTempC = NAN;
        uiHumidity = 0;
    }

    //strPtr += sprn_nl((char*)prnBuf, strPtr);
    strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing Temperature , %5.2f C", fBrdTempC);
    strPtr += sprn_nl((char*)prnBuf, strPtr);
    strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing    Humidity , %u %%", uiHumidity);
    strPtr += sprn_nl((char*)prnBuf, strPtr);
    strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing    Pressure , %5.2f mBar", fBrdPressure);
    strPtr += sprn_nl((char*)prnBuf, strPtr);

    if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )  { // fix 6 Aug 2021
        appendIsusProfileDataToSDCard((char*)prnBuf);
    } // if sys_data.app_cfg[APPCFG_MICROSD_ENABLE]

    if(printFlg == 1) { UARTwrite((char *)prnBuf, strlen((char *)prnBuf)); }

    prn_nl(); uprintf("Entering Acquisition Loop, press ENTER to stop after next scan."); prn_nl();

    loopctr = litScansPerDark;

    tickLast = Timer_1ms();
    tickThis = tickLast;

    while(1) {
        // Luke's original code does not exit function on error condition, just break
        // what is desired behavior?

        // now on timer
        while((tickThis - tickLast) < interval_s * 1000) {
            tickThis = Timer_1ms();
            wdog_pet();
        } // not yet ready to act

        tickLast = tickThis; // new start time of last sample

        if (loopctr == litScansPerDark) {
            // if we're not toggling lamp every time, need to turn it off here
            if (!offBtwnScansFlg) mms_lampPwrOff();

            // adding 9/18/25 as a timing test, to move this out of mms_scanSpectra()
            bme280_getValues(&apf.sys_humidity, &apf.sys_temperature, &apf.sys_pressure);

            if( mms_scanSpectra( 1, DarkData, DARKCURRENT_SCAN, printFlg, writeFlg ) != TRUE ) { // mms_scanSpectra prints %hu, takeIsusSample is the one that prints out hex
                if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed on dark scan, ret err");  prn_nl();  }
                mms_specPwrOff();
                return( ERROR );
            }
            loopctr = 0;

            if (!offBtwnScansFlg) { // turn on lamp here, so its turnon time is factored in here
                if (mms_lampPwrOnUsingSpec( SpecData, FALSE ) != TRUE) {
                // note, MSC1 uses lampPwrOn(), but no functions in MSC3 use that
                // so we use lampPwrOnUsingSpec instead
                    if(mmsDbgFlg == 1) { uprintf("mms_lampPwrOnUsingSpec failed, ret err");  prn_nl();  }
                    mms_lampPwrOff();
                    mms_specPwrOff();
                    return( ERROR );
                }
            }

        } else {
            // this next function will check if the lamp is already on and exit if it is,
            // so can call regardless of whether offBtwnScansFlg is set
            // can i save time by not calling?
            if (offBtwnScansFlg) {
                if (mms_lampPwrOnUsingSpec( SpecData, FALSE ) != TRUE) {
                // note, MSC1 uses lampPwrOn(), but no functions in MSC3 use that
                // so we use lampPwrOnUsingSpec instead
                    if(mmsDbgFlg == 1) { uprintf("mms_lampPwrOnUsingSpec failed, ret err");  prn_nl();  }
                    mms_lampPwrOff();
                    mms_specPwrOff();
                    return( ERROR );
                }
            }

            //tickTemp = Timer_1ms();
            if( mms_scanSpectra( 1, SpecData, INTENSITY_SCAN, printFlg, writeFlg ) != TRUE ) {   // print, don't write
                if(mmsDbgFlg == 1) { uprintf("mms_scanSpectra failed on light scan, ret err");  prn_nl();  }
                mms_lampPwrOff();   //LampPwrOff();
                mms_specPwrOff( );  //SpecPwrOff( SpecPort );
                return( ERROR );
            }
            //uprintf("Elapsed: %d ", Timer_1ms() - tickTemp); prn_nl();

            if (offBtwnScansFlg) {
                mms_lampPwrOff();
                delay_msec(500); // added by IH so lamp turns off, not sure if it's necessary
            }
            loopctr++;
        }

        //if(kbhit())
        // using some subfunctions used in the functions of user_io.c
        while ( UARTRxBytesAvail() )
        {
            key = UARTgetc();
            if (key == '\r' || key == '\n') {
                uprintf("Received keyboard hit, exiting."); prn_nl();
                uprintf("Total running time: %d seconds.", (Timer_1ms() - tickStart)/1000); prn_nl();
                mms_lampPwrOff();
                mms_specPwrOff();

                // IH adding 9/23/25 - may be nice to output this
                ina260_system_voltageCurrent( &fSysVolt, &fSysAmp );
                strPtr = 0;
                strPtr += sprn_nl((char*)prnBuf, strPtr);
                strPtr += sprintf((char*)&prnBuf[strPtr], "H, System Batt Voltage , %6.3f", fSysVolt);
                strPtr += sprn_nl((char*)prnBuf, strPtr);
                strPtr += sprintf((char*)&prnBuf[strPtr], "H, System Load Current , %6.3f", fSysAmp);
                strPtr += sprn_nl((char*)prnBuf, strPtr);

                if(bme280_read_data() == 1) {
                    fBrdPressure = bme280_get_fPressure();
                    fBrdTempC = bme280_get_fTemperature();
                    uiHumidity = bme280_get_uiHumidity();
                } else {
                    fBrdPressure = NAN;
                    fBrdTempC = NAN;
                    uiHumidity = 0;
                }

                //strPtr += sprn_nl((char*)prnBuf, strPtr);
                strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing Temperature , %5.2f C", fBrdTempC);
                strPtr += sprn_nl((char*)prnBuf, strPtr);
                strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing    Humidity , %u %%", uiHumidity);
                strPtr += sprn_nl((char*)prnBuf, strPtr);
                strPtr += sprintf((char*)&prnBuf[strPtr], "H, Housing    Pressure , %5.2f mBar", fBrdPressure);
                strPtr += sprn_nl((char*)prnBuf, strPtr);

                if( (sys_data.app_cfg[APPCFG_MICROSD_ENABLE] == 1) && (writeFlg == 1) )  { // fix 6 Aug 2021
                    appendIsusProfileDataToSDCard((char*)prnBuf);
                } // if sys_data.app_cfg[APPCFG_MICROSD_ENABLE]

                if(printFlg == 1) { UARTwrite((char *)prnBuf, strlen((char *)prnBuf)); }
                return ( OK );
            }
        }
    }
}

