#include <iostream>
#include <thread>
#include <string>
#include "messagedispatcher.h"
#include "e384commlib_errorcodes.h"
#include "e384commlib_global.h"
#include "e384commlib_global_addendum.h"
#include "utils.h"


using namespace e384CommLib;

int main()
{
    MessageDispatcher* md = nullptr;
    RxOutput_t rxOutput;
    int16_t* data;

    // Detect the available devices
    std::vector<std::string> deviceIds;
    ErrorCodes_t detectError = md->detectDevices(deviceIds);

    if(detectError != Success){
        if(detectError == ErrorNoDeviceFound)
            std::cout << "Error device not found: " << std::hex << detectError << std::endl;
        else{
            std::cout << "Another error: " << std::hex << detectError << std::endl;
        }
        return -1;
    }

    for(int i = 0; i < (int)deviceIds.size(); i++){
        std::cout << "Detected device " + std::to_string(i)+ ": " + deviceIds[i] << std::endl;
    }

    // Connect to the available device
    MessageDispatcher::connectDevice(deviceIds[0], md);

    // Check the available voltage and current channels
    uint16_t voltageChannelsNum;
    uint16_t currentChannelsNum;
    md->getChannelNumberFeatures(voltageChannelsNum, currentChannelsNum);
    std::cout << "Available voltage channels: " + std::to_string(voltageChannelsNum) << std::endl;
    std::cout << "Available current channels: " + std::to_string(currentChannelsNum) << std::endl;
    std::cout << std::endl;

    // Check the available current ranges
    std::vector<RangedMeasurement_t> currentRanges;
    uint16_t defaultOptions;
    md->getVCCurrentRanges(currentRanges, defaultOptions);
    std::cout << "Available current ranges "  << std::endl;
    for(int i = 0; i < (int)currentRanges.size(); i++){
        std::cout << "\t Current range " + std::to_string(i) + ": " + currentRanges[i].niceLabel() << std::endl;
    }
    std::cout << std::endl;

    std::vector<RangedMeasurement_t> voltageRanges;
    md->getVCVoltageRanges(voltageRanges);

    // Check the available sampling rates
    std::vector<Measurement_t> samplingRates;
    md->getSamplingRatesFeatures(samplingRates);
    std::cout << "Available sampling rates "  << std::endl;
    for(int i = 0; i < (int)samplingRates.size(); i++){
        std::cout << "\t Sampling rate " + std::to_string(i) + ": " + samplingRates[i].niceLabel() << std::endl;
    }
    std::cout << std::endl;

    // set current range 100nA
    bool applyFlag = true;
    md->setVCCurrentRange(0, applyFlag);

    // set sampling rate 1.25MHz
    md->setSamplingRate(5, applyFlag);
    Measurement_t samplingRate;
    md->getSamplingRate(samplingRate);

	// Wait 5s and then purge to avoid downloading old data
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    md->purgeData();

    // turn on digital offset compensation on all channels
    std::vector<uint16_t> channelIndexes;
    channelIndexes.push_back(0);
    std::vector<bool> onValues;
    onValues.push_back(true);
    std::cout << "Start digital offset compensation: " << md->digitalOffsetCompensation(channelIndexes, onValues, true) << std::endl;
    
	// allocate the buffer needed to read data while the digital offset compensation runs
	int16_t* dataWait;
    ErrorCodes_t asd = md->allocateRxDataBuffer(dataWait);
    unsigned int AccDataToRead = 0;
    while(AccDataToRead <= (unsigned int)(10.0 * samplingRate.getNoPrefixValue())){
        // Read packets. This has to be done for the digital offset compensation to work: for optimization reasons the commlib uses the data processed by the getNextMessage method
        // to compute the voltge values to apply
        md->getNextMessage(rxOutput, dataWait);

        if (rxOutput.dataLen > 0) {
            if (rxOutput.msgTypeId == (MsgDirectionDeviceToPc + MsgTypeIdAcquisitionData)) {
                AccDataToRead = AccDataToRead + rxOutput.dataLen / (voltageChannelsNum + currentChannelsNum);
            }

        } else {
			// if I get no data, wait 1 second before the next getNextMessage
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
    }
	// deallocate buffer for digital offset compensation data
    md->deallocateRxDataBuffer(dataWait);


    // turn off digital offset compensation on all channels
    onValues.clear();
    onValues.push_back(false);
    std::cout << "Stop digital offset compensation: " << md->digitalOffsetCompensation(channelIndexes, onValues, true) << std::endl;

    // Check digital offset compensation success
    std::vector <LiquidJunctionStatus_t> liquidJunctionStatuses;
    std::vector <Measurement_t> liquidJunctionVoltageses;
    md->getLiquidJunctionStatuses(channelIndexes, liquidJunctionStatuses);
    switch (liquidJunctionStatuses[0]) {
    case LiquidJunctionSucceded:
        std::cout << "Success" << std::endl;
        break;

    case LiquidJunctionInterrupted:
        std::cout << "Early interruption" << std::endl;
        break;

    case LiquidJunctionFailedOpenCircuit:
    case LiquidJunctionFailedTooManySteps:
    case LiquidJunctionFailedSaturation:
        std::cout << "Failed" << std::endl;
        break;
    }

    md->getLiquidJunctionVoltages(channelIndexes, liquidJunctionVoltageses);
    std::cout << "Compensated voltage: " << liquidJunctionVoltageses[0].niceLabel() << std::endl;
    std::cout << std::endl;


    // Protocol definition, square wave by means of 3 items
    // Everytime a new protocol is defined, its protId should be incremental, otherwise it can be hard understanding the data returned from the device
    uint16_t protId = 0;
    uint16_t itemsNum = 3;
    uint16_t sweepsNum = 1;
    Measurement_t vRest;
    vRest.value = 0.0;
    vRest.prefix = UnitPfxNone;
    vRest.unit = "V";
    bool stopProtocolFlag = true;
    md->setVoltageProtocolStructure(protId, itemsNum, sweepsNum, vRest, stopProtocolFlag);

    Measurement_t v0 = { 0.0, UnitPfxMilli, "V" };
    Measurement_t vStep = { 0.0, UnitPfxMilli, "V" };
    Measurement_t t0 = { 100.0, UnitPfxMilli, "s" };
    Measurement_t tStep = { 0.0, UnitPfxMilli, "s" };
    uint16_t currentItem = 0;
    uint16_t nextItem = 1;
    uint16_t repsNum = 0; // If the protocol element is meant to run indefinetly, set this to 0. 
    uint16_t applySteps = 0;
    bool vHalfFlag = false;
    md->setVoltageProtocolStep(currentItem, nextItem, repsNum, applySteps, v0, vStep, t0, tStep, vHalfFlag);

    v0 = { 100.0, UnitPfxMilli, "V" };
    vStep = { 0.0, UnitPfxMilli, "V" };
    t0 = { 100.0, UnitPfxMilli, "s" };
    tStep = { 0.0, UnitPfxMilli, "s" };
    currentItem = 1;
    nextItem = 2;
    repsNum = 0;
    applySteps = 0;
    vHalfFlag = false;
    md->setVoltageProtocolStep(currentItem, nextItem, repsNum, applySteps, v0, vStep, t0, tStep, vHalfFlag);

    v0 = { -100.0, UnitPfxMilli, "V" };
    vStep = { 0.0, UnitPfxMilli, "V" };
    t0 = { 100.0, UnitPfxMilli, "s" };
    tStep = { 0.0, UnitPfxMilli, "s" };
    currentItem = 2;
    nextItem = 0;
    repsNum = 0;
    applySteps = 0;
    vHalfFlag = false;
    md->setVoltageProtocolStep(currentItem, nextItem, repsNum, applySteps, v0, vStep, t0, tStep, vHalfFlag);

    md->startProtocol();

    std::vector<uint16_t> chanIdxs;
    chanIdxs.push_back(0);

    // Define how much data save to file
    unsigned int myDataToRead = (unsigned int)(10.0 * samplingRate.getNoPrefixValue()); // 10s data at 1.25MHz
    AccDataToRead = 0;

    md->purgeData();
    FILE* fid = fopen("myLog.dat", "wb");
    ErrorCodes_t ret = md->allocateRxDataBuffer(data);

    bool offsetModified = false;

    while(AccDataToRead <= myDataToRead){
        if (AccDataToRead > myDataToRead / 2 && !offsetModified) { // after 5 seconds apply a voltage offset to make the output data more interesting
            offsetModified = true;
			
			// This is how a constant voltage offset is applied
            std::vector<Measurement_t> vSteps;
            vSteps.push_back({100.0, UnitPfxMilli, "V"});
            md->setVoltageHoldTuner(chanIdxs, vSteps, true);
        }

        // Read packets
        md->getNextMessage(rxOutput, data);

        if (rxOutput.dataLen > 0) {
            if (rxOutput.msgTypeId == (MsgDirectionDeviceToPc + MsgTypeIdAcquisitionData)) {
                AccDataToRead = AccDataToRead + rxOutput.dataLen / (voltageChannelsNum + currentChannelsNum);

                // Print data buffer to file
                fwrite(&data[0], 2, rxOutput.dataLen, fid);
            }

        } else {
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }
    }
    md->deallocateRxDataBuffer(data);

    fclose(fid);

    std::cout << "Ended voltage protocol" << std::endl;
    std::cout << std::endl;

    std::cout << "Disconnect the device" << std::endl;
    md->disconnectDevice();

    return 0;

}
