// bytran -|- spectral calculations for portable devices using the HITRAN database
//
// Developed by Dzianis Pliutau (Denis Plutov) and Kostya Roslyakov
// www.bytran.org | bytran@bytran.org
// Copyright (c) 2016 Dzianis Pliutau
// Distributed under the MIT license
//
// The MIT License (MIT)
// Copyright (c) 2016 Dzianis Pliutau
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



// The class is used to control bytran remotely i.e. over Bluetooth or RS-232 and retrieve the calculation results for possible integration with other systems
#include "remotecontrol.h"
#include <QLocale>
#include <QEventLoop>

RemoteControl::RemoteControl(QObject *parent) : QObject(parent)
{
    // Clearing the buffered commands to process
    this->pendingCommands = "";

    // Initializing the serial port object
    serial = new SerialPort();
    connect(this->serial, SIGNAL(commandReceived(QString)), this, SLOT(bufferAndProcessCommands(QString)));

    // Initializing the bluetooth object
    bluetooth = new BluetoothSpp();
    connect(this->bluetooth, &BluetoothSpp::commandReceived, this, &RemoteControl::bufferAndProcessCommands);

    // Object for communication over web sockets
    webSockets = new WebSockets(1234);
    connect(this->webSockets, SIGNAL(commandReceived(QString)), this, SLOT(bufferAndProcessCommands(QString)));

    // Initializng variables to their default values
    this->communicationMode = 0;
    setCommunicationMode(this->communicationMode);
    this->broadcastManualInput = false;
}

// Methods to get and set the communication mode
int RemoteControl::getCommunicationMode() { return this->communicationMode; }
void RemoteControl::setCommunicationMode(int newCommunicationMode)
{
    // Saving the newlly assigned communication mode
    this->communicationMode = newCommunicationMode;

    // Communication disabled
         if (this->communicationMode == 0) { this->bluetooth->disableBluetooth();
                                             this->serial->disconnectSerialPort();
                                             this->webSockets->enableWebSockets(false); }
    // Bluetooth selected
    else if (this->communicationMode == 1) { this->bluetooth->enableBluetooth();
                                             this->serial->disconnectSerialPort();
                                             this->webSockets->enableWebSockets(false); }
    // Serial port selected
    else if (this->communicationMode == 2) { this->bluetooth->disableBluetooth();
                                             this->serial->connectSerialPort();
                                             this->webSockets->enableWebSockets(false); }
    // Web sockets selected
    else if (this->communicationMode == 3) { this->bluetooth->disableBluetooth();
                                             this->serial->disconnectSerialPort();
                                             this->webSockets->enableWebSockets(true); }
                                      // Communication disabled if none of the options (shouldn't happen)
                                      else { this->bluetooth->disableBluetooth();
                                             this->serial->disconnectSerialPort();
                                             this->webSockets->enableWebSockets(false); }
}

// Method gets and sets the broadcast manual input variable
void RemoteControl::setBroadcastManualInput(bool newBroadcastManualInput) { this->broadcastManualInput = newBroadcastManualInput; }
bool RemoteControl::getBroadcastManualInput() { return this->broadcastManualInput; }

// Method to broadcast manual input values
void RemoteControl::broadcastCommand(QString command, int index)
{
    QString message;

    // Only broadcast manual output if something is connected (over bluetooth, WiFi, or Serial Port)
    if (getRemoteDeviceIsConnected() && this->broadcastManualInput)
    {
        message = getValueStr(command, index, true);
        sendMessage(message);
    }
}

// Method to send data to a remote device using the currently used connection method
void RemoteControl::sendMessage(const QString &message)
{
    if (getRemoteDeviceIsConnected())
    {
        if (this->communicationMode == 1) { this->bluetooth->sendMessage(message); }
        if (this->communicationMode == 2) { this->serial->sendMessage(message); }
        if (this->communicationMode == 3) { this->webSockets->sendMessage(message); }
    }
}
void RemoteControl::sendMessage(const QByteArray &message)
{
    if (getRemoteDeviceIsConnected())
    {
        // Need to implement the binary mode for bluetooth
        if (this->communicationMode == 2) { this->webSockets->sendMessage(message); }
        if (this->communicationMode == 3) { this->webSockets->sendMessage(message); }
    }
}

// Method to determine if any device is connected (over Bluetooth, WiFi/Ethernet, or the Serial port)
bool RemoteControl::getRemoteDeviceIsConnected()
{
    if (this->communicationMode != 0)
    {
        if ((this->webSockets->getConnectedDeviceIpAddress() != "") || (this->bluetooth->getRemoteDeviceIsConnected()) || (this->serial->getRemoteDeviceIsConnected()))
            { return true; }
        else
            { return false; }
    }
    else { return false; }
}

// Methods to get the objects containing the variables to modify and transfer the value of
void RemoteControl::setWavelength(Wavelength *wavelengthIn) { this->wavelength = wavelengthIn; }
void RemoteControl::setParams(Params *paramsIn) { this->params = paramsIn; }
void RemoteControl::setSensors(Sensors *sensorsIn) { this->sensors = sensorsIn; }
void RemoteControl::setInstrumentFunction(InstrumentFunction *instrFunctIn) { this->instrFunct = instrFunctIn; }
void RemoteControl::setAtmModel(Atmosphere *atmIn) { this->atm = atmIn; }
void RemoteControl::setHitranOnlineDownloader(HitranOnlineDownloader *hitranIn) { this->hitran = hitranIn; }
void RemoteControl::setYAxis(YAxis *yaxisIn) { this->yaxis = yaxisIn; }

// Method to get access to the serial port data object
SerialPort *RemoteControl::getSerialPort() { return this->serial; }

// Method to get access to the bluetooth data object
BluetoothSpp *RemoteControl::getBluetooth() { return this->bluetooth; }

// Method to get a reference to the WebSockets object
WebSockets *RemoteControl::getWebSockets() { return this->webSockets; }

// Method to obtain a reference to the results saver object to be able to return the results of the calculations
void RemoteControl::setResultsSaver(ResultsSaver *inResultsSaver) { this->resultsSaver = inResultsSaver; }

// Method is used to append the newly received commands to the pending commands string and process all available complete commands
void RemoteControl::bufferAndProcessCommands(const QString &message)
{
    int commandStartIndex;   // Command start index
    int commandEndIndex;     // Command end index
    QString oneCommandMessage;   // Message with a single command


    // Processing commands only if a remote device is connected (this check is probably redundant)
    if (getRemoteDeviceIsConnected()) {

        // Appending newly received commands to the pending commands string
        this->pendingCommands.append(message);

        // Extracting all complete commands from the pending commands string and processing them
        commandStartIndex = this->pendingCommands.indexOf("#", 0, Qt::CaseInsensitive);
        if (commandStartIndex >= 0)
        {
            commandEndIndex = this->pendingCommands.indexOf("#", commandStartIndex + 1, Qt::CaseInsensitive);
            if (commandEndIndex == -1) { commandEndIndex = this->pendingCommands.length(); }
            while ( commandStartIndex >= 0 )
            {
                oneCommandMessage = this->pendingCommands.mid(commandStartIndex, commandEndIndex - commandStartIndex);
                processCommandString(oneCommandMessage);
                this->pendingCommands.remove(commandStartIndex, commandEndIndex - commandStartIndex);
                commandStartIndex = this->pendingCommands.indexOf("#", 0, Qt::CaseInsensitive);
                commandEndIndex = this->pendingCommands.indexOf("#", commandStartIndex + 1, Qt::CaseInsensitive);
                if (commandEndIndex == -1) { commandEndIndex = this->pendingCommands.length(); }
            }
        }
        // If there is no # character in the string, empty the buffer string invalid input()
        else { this->pendingCommands = ""; }
    }
}

// Slot is used to set a parameter value based on the message received (the command and the value assigned)
void RemoteControl::processCommandString(const QString &message)
{
    QString command;      // Command with subcommand (if applicable)
    QString mainCommand;  // Main command
    QString subCommand;   // Second command for the RESULTs
    QString oneCommandMessage;  // Message with a single command
    QString value;              // Value
    int index;                  // Index parameter
    QByteArray valueByteArr;    // Value represented as a byte array
    QStringList splitString;    // String split into separate components
    QLocale loc;                // Object to convert string to numerical representation
    int commandStartIndex;      // Command start index
    QString response;           // Response string if the data is requested from a remote client
    QString responseBin;        // Response in a binary mode (as a byte array)
    bool ok;  // Result of the QLocale conversion
    int i;    // Loop variable
    int strIndex;    // Auxiliary index (second auxiliary index)


    // Finding where the command starts and removing the # character
    commandStartIndex = message.indexOf("#", 0, Qt::CaseInsensitive);
    oneCommandMessage = message.mid(commandStartIndex + 1);

    // Extracting the command and the value from the string
    splitString = oneCommandMessage.split(";", QString::SkipEmptyParts, Qt::CaseInsensitive);
    mainCommand = splitString[0].toUpper();

    // Adjusting the paramter (in case the delimited value contains semicolons;)
    if (mainCommand == "DELIMITER") {
        splitString.clear();
        commandStartIndex = oneCommandMessage.indexOf(";", 0, Qt::CaseInsensitive);
        splitString.append(mainCommand);
        splitString.append(oneCommandMessage.mid(commandStartIndex + 1));
    }

    // If the command contains the RESULT keyword, then appending the command with the sub-command following the RESULT
    if (mainCommand == "RESULT") { subCommand = splitString[1].toUpper();
                                   command = mainCommand + ";" + subCommand; }
                            else { command = mainCommand;
                                   subCommand = mainCommand; }

    // Also need to take care of the index value (for molecules only)
    if ((command == "RESULT;SPEC") || (command == "RESULT;MCONC") || (command == "MCONC") || (command == "MSEL")) {

        // String index is different depending on the command requested
        strIndex = 2;
        if ((command == "MCONC") || (command == "MSEL"))
        {
            strIndex = 1;
            if (splitString.length() >= 3) { value = splitString[2]; }
        }

        index = loc.toInt(splitString[strIndex], &ok);
        if (!ok) // This option is to be able to request molecular spectra by typing in the molecule chemical formula
        {
            index = -1020304050;
            if (splitString[strIndex].toUpper() == "INSTR") { index = -1; }
            if (splitString[strIndex].toUpper() == "TOTAL") { index = 0; }
            for (i  = 0; i < params->getMol()->count(); i++)
            {
                if (splitString[strIndex].toUpper() == params->getMolAt(i)->getChemForm()) { index = i + 1; }
            }
        }
    }
    else {
        index = 0;
        if (splitString.length() > 1) { value = splitString[1]; }
    }

    // Transfering the command for further processing
    if ((splitString.last() != "?") && (mainCommand != "RESULT"))
    {
        // Using the string or binary value assignment mode
        if (command.contains("_BIN")) {
            valueByteArr = value.toUtf8();
            setValueBin(command, valueByteArr, index);
        }
        else {
            setValueStr(command, value, index);
        }
    }
    else  // ? means the user is requesting information about the current value of a variable, he is not trying to assign a new value to a variable
    {
        if (command.contains("_BIN")) {
            responseBin = getValueBin(command, index);
            sendMessage(responseBin);
        }
        else {
            response = getValueStr(command, index, false);
            sendMessage(response);
        }
    }
}

// Slot to emit a ready message and set the BUSY variable to false
void RemoteControl::emitReadyMessage()
{
    QString message = "READY";
    if (getRemoteDeviceIsConnected()) {        
        sendMessage(message);
        emit ready();
    }
}

// Slot to set the BUSY variable (on spectrum update this signal is emitted 2 times, need to find out why as it looks like the spectrum is being plotted 2 times making things slower)
void RemoteControl::setReady() { emit ready(); }

// Method is used to set selected variables in the bytran program based on the remotely passed command and value
// The index parameter is only used for molecules to specify which molecule the value needs to be changed for
void RemoteControl::setValueStr(QString command, QString value, int index)
{
    QLocale c;  // Used to convert from string to numerical data types
    quint8 uint8Var;  // Auxiliary integer variable (unsigned 8 bit)
    QString message;  // Message to send to the remote control user
    bool boolVar;     // Auxiliary boolean variable


    // The only command with no parameters or indexes
    if (command == "CALC") {

        // Sending a command to calculate
        // Though unlikely, also need to handle the situation when the signal is never emitted (so that the loop would still end)
        QEventLoop calcLoop;
        // Test to make sure this signal will be emitted at some point or else the program will get stuck
        connect(this, SIGNAL(ready()), &calcLoop, SLOT(quit()));
        emit calculate();
        calcLoop.exec();
    }

    // Setting the format of the output string (this is the only command accepting a string format parameter)
    if (command == "FORMAT") {
        if ((value == 'e') || (value == 'E') || (value == 'f') || (value == 'g') || (value == 'G')) {
            params->setOutStrFormat(*(value.toLatin1().data())); }
        else {
            message = "FORMAT : Parameter out of range. Allowed values:  e, E, f, g, G.";
            sendMessage(message);
        }
    }

    // Setting the delimiter for the numerical output string
    if (command == "DELIMITER") { params->setStrDelimiter(value); }

    // Molecular concentration and the selected state commands need to have the molecule index supplied
    if ((command == "MCONC") || (command == "MSEL"))
    {
        if (index >= 1) {
            if (command == "MCONC") { params->getHorizParams()->setPartPress(index - 1, c.toDouble(value)); }
            if (command == "MSEL") { params->getMolAt(index - 1)->setSelected(c.toInt(value)); }
        }
        else {
            message = command + " : Parameter out of range. The molecule index has to be >= 1.";
            sendMessage(message);
        }
    }

    // Other commands which requite a value passed with the command
    if ((command != "CALC") && (command != "FORMAT") && (command != "MCONC") && (command != "MSEL"))
    {
        if ((c.toDouble(value) >= 0))
        {
            // These are ongoing configuration paramters (may be modified any time)
            if (command == "SWAVE") { wavelength->setStartWn(c.toDouble(value)); }
            if (command == "EWAVE") { wavelength->setEndWn(c.toDouble(value)); }
            if (command == "YPARAM") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 1) {
                    yaxis->setDisplayedValue(uint8Var);
                    emit updateSpectra(true, true); }
                else {
                    message = "YPARAM : Parameter out of range. Allowed values: 0 and 1.";
                    sendMessage(message); }
            }
            if (command == "YUNITS") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 3) {
                    yaxis->setYPlotUnits(uint8Var);
                    emit updateSpectra(true, true); }
                else {
                    message = "YUNITS : Parameter out of range. Allowed values: 0 to 3.";
                    sendMessage(message); }
            }
            if (command == "TOTAL") {
                boolVar = (c.toInt(value) != 0);

                // Sending a command to calculate
                // Though unlikely, also need to handle the situation when the signal is never emitted (so that the loop would still end)
                QEventLoop plotUpdateLoop;
                // Test to make sure this signal will be emitted at some point or else the program will get stuck
                connect(this, SIGNAL(ready()), &plotUpdateLoop, SLOT(quit()));

                // Emitting signals to update the plots
                if (boolVar == true) {
                    emit showPlainTotalChanged(true);
                    emit showInstrTotalChanged(true);
                }
                emit showTotalChanged(boolVar);

                // Starting the plot update loop
                plotUpdateLoop.exec();
            }
            if (command == "NUMWNPTS") { wavelength->setNumPoints(c.toUInt(value)); }
            if (command == "PATH") { params->getHorizParams()->setPathLength(c.toDouble(value)); }
            if (command == "TPRES") { params->getHorizParams()->setTotalPress(c.toDouble(value)); }
            if (command == "USEBAROM") { sensors->setEnableTotalPressureSensor((c.toInt(value) != 0)); }
            if (command == "TEMP") { params->getHorizParams()->setTemp(c.toDouble(value)); }
            if (command == "USETHERM") { sensors->setEnableTemperatureSensor((c.toUInt(value) != 0)); }
            if (command == "USEHUMID") { sensors->setEnableHumiditySensor((c.toInt(value) != 0)); }
            if (command == "USESENSTAG") { sensors->setUseSensorTag((c.toInt(value) != 0)); }
            if (command == "SMPLPERIOD") { sensors->setSampligPeriod(c.toInt(value)); }
            if (command == "SMPLSTOAVG") { sensors->setNumSamplesToAverage(c.toInt(value));}
            if (command == "HITOPT") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 2) {
                    hitran->setHitranFileUsage(uint8Var); }
                else {
                    message = "HITOPT : Parameter out of range. Allowed values: 0 to 2.";
                    sendMessage(message); }
            }
            if (command == "LSHAPE") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 3) {
                    params->setLineshapeProfile(uint8Var); }
                else {
                    message = "LSHAPE : Parameter out of range. Allowed values: 0 to 3.";
                    sendMessage(message); }
            }
            if (command == "LTHRESH") { params->setIntensThresh(c.toDouble(value)); }
            if (command == "LWINGSAT") { params->setWingContribSatHw(c.toInt(value)); }
            if (command == "LWINGABS") { params->setWingContribAbs(c.toDouble(value)); }
            if (command == "LBROAD") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 2) {
                    params->setLineBroadening(uint8Var); }
                else {
                    message = "LBROAD : Parameter out of range. Allowed values: 0 to 2.";
                    sendMessage(message); }
            }
            if (command == "LSHIFTUSE") { params->setUseLineShift((c.toInt(value) != 0)); }
            if (command == "INSTRFUNCT") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 7) {
                    instrFunct->setInstrFunctIndex(uint8Var); }
                else {
                    message = "INSTRFUNCT : Parameter out of range. Allowed values: 0 to 7.";
                    sendMessage(message); }
            }
            if (command == "INSTRRES") { instrFunct->setInstrResol(c.toDouble(value)); }
            if (command == "INSTRWING") { instrFunct->setInstrFunctWing(c.toDouble(value)); }
            if (command == "ATMMODEL") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                if (uint8Var <= 5) {
                    atm->setActiveAtmModelNum(uint8Var); }
                else {
                    message = "INSTRFUNCT : Parameter out of range. Allowed values: 0 to 5.";
                    sendMessage(message); }
                emit applyCurrentAtmModel();
            }
            if (command == "ALLCALC") { params->setCalcNotSelectedMols((c.toInt(value) != 0)); }
            if (command == "PREC") {
                uint8Var = static_cast<quint8> (c.toUInt(value));
                params->setOutStrPrecision(uint8Var);
            }
        }
        else {
            if (c.toDouble(value) >= 0) { message = command + " : Parameter out of range. Parameter values have to be >= 0."; }
            sendMessage(message);
        }
    }

    // Signal is emitted to update values in the open dialogs
    emit valuesUpdated();
}
// The index value is currently only used to get states for individual molecules and to specify which calculation results data to request
QString RemoteControl::getValueStr(QString command, int index, bool broadcastMode)
{
    QString value;
    QLocale loc;  // Object to perform conversions into string representations
    QString message;  // Message to send to the remote control user


    if (command == "SWAVE") { value = loc.toString(wavelength->getStartWn(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "EWAVE") { value = loc.toString(wavelength->getEndWn(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "YPARAM") { value = loc.toString(yaxis->getDisplayedValue()); }
    if (command == "YUNITS") { value = loc.toString(yaxis->getYPlotUnits()); }
    if (command == "TOTAL") { value = loc.toString(yaxis->getShowTotal()); }
    if (command == "NUMWNPTS") { value = QString::number(wavelength->getNumPoints()); }
    if (command == "PATH") { value = loc.toString(params->getHorizParams()->getPathLength(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "TPRES") { value = loc.toString(params->getHorizParams()->getTotalPress(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "USEBAROM") { value = loc.toString(sensors->getTotalPressureSensorIsEnabled()); }
    if (command == "TEMP") { value = loc.toString(params->getHorizParams()->getTemp(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "USETHERM") { value = loc.toString(sensors->getTemperatureSensorIsEnabled()); }  // Need to make sure this will covert to 1 or 0 in the string
    if (command == "USEHUMID") { value = loc.toString(sensors->getHumiditySensorIsEnabled()); }     // Need to make sure this will convert to 1 or 0 in the string
    if (command == "USESENSTAG") { value = loc.toString(sensors->getUseSensorTag()); }              // Need to make sure this will convert to 1 or 0 in the string
    if (command == "SMPLPERIOD") { value = QString::number(sensors->getSamplingPeriod()); }
    if (command == "SMPLSTOAVG") { value = QString::number(sensors->getNumSamplesToAverage()); }
    if (command == "HITOPT") { value = loc.toString(hitran->getHitranFileUsage()); }
    if (command == "LSHAPE") { value = loc.toString(params->getLineshapeProfile()); }
    if (command == "LTHRESH") { value = loc.toString(params->getIntensThresh(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "LWINGSAT") { value = loc.toString(params->getWingContribSatHw()); }
    if (command == "LWINGABS") { value = loc.toString(params->getWingContribAbs(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "LBROAD") { value = loc.toString(params->getLineBroadening()); }
    if (command == "LSHIFTUSE") { value = loc.toString(params->getUseLineShift()); }  // Need to make sure this will covert nto 1 or 0 in the string
    if (command == "INSTRFUNCT") { value = loc.toString(instrFunct->getInstrFunctIndex()); }
    if (command == "INSTRRES") { value = loc.toString(instrFunct->getInstrResol(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "INSTRWING") { value = loc.toString(instrFunct->getInstrFunctWing(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "ATMMODEL") { value = loc.toString(atm->getAtiveAtmModelNum()); }
    if (command == "ALLCALC") { value = loc.toString(params->getCalcNotSelectedMols()); }        // Need to make sure this will covert to 1 or 0 in the string
    if (command == "CALC") { if (!broadcastMode) { emit calculate(); } }
    if (command == "MCONC") { value = loc.toString(params->getHorizParams()->getPartPress(index - 1), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "MSEL") { value = loc.toString(params->getMolAt(index - 1)->getSelected()); }  // Need to make sure this will covert to 1 or 0 in the string

    // Commands to get the current string output format
    if (command == "FORMAT") { value = params->getOutStrFormat(); }
    if (command == "PREC") { value = loc.toString(params->getOutStrPrecision()); }
    if (command == "DELIMITER") { value = params->getStrDelimiter(); }

    // These data will be retrieved from the results saver object (these results corresond to the last calculation performed)
    if (command == "RESULT;WNGRID") {
        value = resultsSaver->getDelimitedWnGridString(params->getStrDelimiter(), params->getOutStrFormat(), params->getOutStrPrecision());
    }
    if (command == "RESULT;SWAVE") { value = loc.toString(resultsSaver->getWavelength()->getStartWn(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "RESULT;EWAVE") { value = loc.toString(resultsSaver->getWavelength()->getEndWn(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "RESULT;NUMWNPTS") { value = loc.toString(resultsSaver->getWavelength()->getNumPoints()); }
    if (command == "RESULT;PATH") { value = loc.toString(resultsSaver->getPathlength(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "RESULT;TPRES") { value = loc.toString(resultsSaver->getTotalPress(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "RESULT;TEMP") { value = loc.toString(resultsSaver->getTemperature(), params->getOutStrFormat(), params->getOutStrPrecision()); }
    if (command == "RESULT;MCONC") {
        qDebug() << "Getting the results saver concentration";
        value = loc.toString(resultsSaver->getPartPressAt(index - 1), params->getOutStrFormat(), params->getOutStrPrecision()); }
    // Index: -1 - Smoothed total, 0 - High resolution total, not smoothed, N - Individual component for each molecule
    if (command == "RESULT;SPEC")
    {
        // Also need to enable the high-res component spec and the instrument processed one if they have been turned off

        // Check the index here and if not in range, let the requester know
        if ((index < -1) || (index > (params->getMol()->count() - 1)))
        {
            if (index == -1020304050) {
                message = "RESULT;SPEC : String parameter unknown. Allowed values: TOTAL, INSTR or the molecule chemical formula.";
                sendMessage(message);
            }
            else {
                message = "RESULT;SPEC : Index out of range. Allowed values: -1 to " + QString::number(params->getMol()->count());
                sendMessage(message);
            }
        }
        else
        {
            // Checking the state of the plot variable (separate molecules vs. total) and updating the spectrum if necessary
            // Switching to total spectra from separate molecules
            if ((yaxis->getShowTotal() == 0) && ((index == -1) || (index == 0)))
            {
                setValueStr("TOTAL", "1", index);
            }
            // Switching to separate molecules from total spectra
            if ((yaxis->getShowTotal() == 1) && ((index > 0) && (index < (params->getMol()->count() - 1))))
            {
                setValueStr("TOTAL", "0", index);
            }

            // Data is retrieved based on the corresponding column titles for the saved spectra
            if (index > 0) {
                value = resultsSaver->getDelimitedSpectrumString(params->getMolAt(index - 1)->getChemForm(), params->getStrDelimiter(), params->getOutStrFormat(), params->getOutStrPrecision());
                if (value == "") {
                    message = params->getMolAt(index - 1)->getChemForm() + " : The requested molecular spectrum was either not selected for calculation or else its intensity is too low to display in the plot under the current vertical axis min / max settings.";
                    sendMessage(message);
                }
            }
            else {
                if (index == 0)
                {
                    if (yaxis->getDisplayedValue() == 0) {
                        value = resultsSaver->getDelimitedSpectrumString("Total", params->getStrDelimiter(), params->getOutStrFormat(), params->getOutStrPrecision()); }
                    else {
                        value = resultsSaver->getDelimitedSpectrumString("Radiance", params->getStrDelimiter(), params->getOutStrFormat(), params->getOutStrPrecision()); }
                    if (value == "") {
                        message = "The requested instrument function processed total spectrum is either disabled for display or else its intensity is too low to display in the plot under the current vertical axis min / max settings.";
                        sendMessage(message);
                    }
                }
                if (index == -1)
                {
                    value = resultsSaver->getDelimitedSpectrumString("Instr. funct.", params->getStrDelimiter(), params->getOutStrFormat(), params->getOutStrPrecision());

                    if (value == "") {
                        message = "The requested high resolution total spectrum is either disabled for display or else its intensity is too low to display in the plot under the current vertical axis min / max settings.";
                        sendMessage(message);
                    }
                }
            }
        }
    }

    // In the broadcast mode the entire command with arguments is returned
    if (broadcastMode == true)
    {
        // "RESULT;SPEC" and "RESULT;WNGRID" should be excluded in this case or treated in a special case
        if (command.contains("RESULT") == false)
        {
            if ((command == "MCONC") || (command == "MSEL")) { return "#" + command + ";" + QString::number(index) + ";" + value; }
            else { return "#" + command + ";" + value; }
        }
    }
    else {
        return value;
    }
}

// Method to convert to quint8 (and checking the range), not used right now, maybe will use later
quint8 RemoteControl::toQuint8CheckRange(QString value, quint8 maxValue, quint8 prevValue, QString command, QString allowedRange)
{
    quint8 intVar;
    QString message;
    QLocale c;

    intVar = static_cast<quint8> (c.toUInt(value));
    if (intVar <= maxValue) {
        return intVar;
    }
    else {
        message = command + " : Parameter out of range. Allowed values: " + allowedRange;
        sendMessage(message);
        return prevValue;
    }
}

// https://stackoverflow.com/questions/35886641/byte-array-to-double-conversion-fails-in-qt
// https://forum.qt.io/topic/74262/conversion-qbytearray-to-double-array/3
void RemoteControl::setValueBin(QString command, QByteArray value, int index)
{
    if (command == "SWAV_BIN") { wavelength->setStartWn(fromByteArrayToDouble(value)); }
    if (command == "EWAV_BIN") { wavelength->setEndWn(fromByteArrayToInt(value)); }
    if (command == "NWPO_BIN") { wavelength->setNumPoints(fromByteArrayToInt(value)); }
    if (command == "PATH_BIN") { params->getHorizParams()->setPathLength(fromByteArrayToDouble(value)); }
    if (command == "TPRE_BIN") { params->getHorizParams()->setTotalPress(fromByteArrayToDouble(value)); }
    if (command == "UBSE_BIN") { sensors->setEnableTotalPressureSensor(fromByteArrayToBoolean(value)); }
    if (command == "TEMP_BIN") { params->getHorizParams()->setTemp(fromByteArrayToDouble(value)); }
    if (command == "UTSE_BIN") { sensors->setEnableTemperatureSensor(fromByteArrayToBoolean(value)); }
    if (command == "UHSE_BIN") { sensors->setEnableHumiditySensor(fromByteArrayToBoolean(value)); }
    if (command == "HDOP_BIN") { hitran->setHitranFileUsage(fromByteArrayToUInt8(value)); }
    if (command == "LPRO_BIN") { params->setLineshapeProfile(fromByteArrayToUInt8(value)); }
    if (command == "LITH_BIN") { params->setIntensThresh(fromByteArrayToDouble(value)); }
    if (command == "LWCS_BIN") { params->setWingContribSatHw(fromByteArrayToInt(value)); }
    if (command == "LWCA_BIN") { params->setWingContribAbs(fromByteArrayToDouble(value)); }
    if (command == "LBRO_BIN") { params->setLineBroadening(fromByteArrayToUInt8(value)); }
    if (command == "LSUS_BIN") { params->setUseLineShift(fromByteArrayToBoolean(value)); }
    if (command == "IFTY_BIN") { instrFunct->setInstrFunctIndex(fromByteArrayToUInt8(value)); }
    if (command == "IRES_BIN") { instrFunct->setInstrResol(fromByteArrayToDouble(value)); }
    if (command == "IFWI_BIN") { instrFunct->setInstrFunctWing(fromByteArrayToDouble(value)); }
    //if (command == "AAMO_BIN") { atm->setActiveAtmModelNum(fromByteArrayToInt(value)); }  Need to change for unsigned
    if (command == "MCON_BIN") { params->getHorizParams()->setPartPress(index, fromByteArrayToDouble(value)); }
    if (command == "MSEL_BIN") { params->getMolAt(index)->setSelected(fromByteArrayToInt(value)); }
    if (command == "CFAM_BIN") { params->setCalcNotSelectedMols(fromByteArrayToBoolean(value)); }
    if (command == "CALC_BIN") { emit calculate(); }
    if (command == "GWGR_BIN") { ; }

    // Signal is emitted to update values in the open dialogs
    emit valuesUpdated();
}
// https://stackoverflow.com/questions/6280041/convert-double-array-to-qbytearray
QByteArray RemoteControl::getValueBin(QString command, int index)
{
    QByteArray byteArr;  // Byte array containing the requested values

    if (command == "SWAV") { byteArr = fromDoubleToByteArray(wavelength->getStartWlCurrUnits()); }
    if (command == "EWAV") { byteArr = fromDoubleToByteArray(wavelength->getEndWlCurrUnits()); }
    if (command == "NWPO") { byteArr = fromIntToByteArray(wavelength->getNumPoints()); }
    if (command == "PATH") { byteArr = fromDoubleToByteArray(params->getHorizParams()->getPathLength()); }
    if (command == "TPRE") { byteArr = fromDoubleToByteArray(params->getHorizParams()->getTotalPress()); }
    if (command == "UBSE") { byteArr = fromBooleanToByteArray(sensors->getTotalPressureSensorIsEnabled()); }
    if (command == "TEMP") { byteArr = fromDoubleToByteArray(params->getHorizParams()->getTemp()); }
    if (command == "UTSE") { byteArr = fromBooleanToByteArray(sensors->getTemperatureSensorIsEnabled()); }
    if (command == "UHSE") { byteArr = fromBooleanToByteArray(sensors->getHumiditySensorIsEnabled()); }
    if (command == "HDOP") { byteArr = fromIntToByteArray(hitran->getHitranFileUsage()); }
    if (command == "LPRO") { byteArr = fromIntToByteArray(params->getLineshapeProfile()); }
    if (command == "LITH") { byteArr = fromDoubleToByteArray(params->getIntensThresh()); }
    if (command == "LWCS") { byteArr = fromIntToByteArray(params->getWingContribSatHw()); }
    if (command == "LWCA") { byteArr = fromDoubleToByteArray(params->getWingContribAbs()); }
    if (command == "LBRO") { byteArr = fromIntToByteArray(params->getLineBroadening()); }
    if (command == "LSUS") { byteArr = fromBooleanToByteArray(params->getUseLineShift()); }
    if (command == "IFTY") { byteArr = fromIntToByteArray(instrFunct->getInstrFunctIndex()); }
    if (command == "IRES") { byteArr = fromDoubleToByteArray(instrFunct->getInstrResol()); }
    if (command == "IFWI") { byteArr = fromDoubleToByteArray(instrFunct->getInstrFunctWing()); }
    if (command == "AAMO") { byteArr = fromIntToByteArray(atm->getAtiveAtmModelNum()); }
    if (command == "MCON") { byteArr = fromDoubleToByteArray(params->getHorizParams()->getPartPress(index)); }
    if (command == "MSEL") { byteArr = fromBooleanToByteArray(params->getMolAt(index)->getSelected()); }
    if (command == "CFAM") { byteArr = fromBooleanToByteArray(params->getCalcNotSelectedMols()); }
    if (command == "CALC") { ; }
    if (command == "GWGR") { ; }
    if (command == "DATA") { // Index: -1 - Smoothed total, 0 - High resolution, not smoothed, N - Individual component for each molecule
        ;
    }

    return byteArr;
}

// Methods to convert from byte array to Double and Int values (the byte array contains the Double and Int number representations)
double RemoteControl::fromByteArrayToDouble(QByteArray byteArr)
{
    double value;
    QDataStream stream(byteArr);
    stream.setByteOrder(QDataStream::LittleEndian);
    stream >> value;

    // Returning the converted value
    return value;
}
int RemoteControl::fromByteArrayToInt(QByteArray byteArr)
{
    int value;
    QDataStream stream(byteArr);
    stream.setByteOrder(QDataStream::LittleEndian);
    stream >> value;

    // Returning the converted value
    return value;
}
quint8 RemoteControl::fromByteArrayToUInt8(QByteArray byteArr)
{
    quint8 value;
    QDataStream stream(byteArr);
    stream.setByteOrder(QDataStream::LittleEndian);
    stream >> value;

    // Returning the converted value
    return value;
}
bool RemoteControl::fromByteArrayToBoolean(QByteArray byteArr)
{
    bool boolValue = false;
    return boolValue;
}

// Methods to convert from regular data types to byte array representations to be transfered over the communication channel in binary form
QByteArray RemoteControl::fromDoubleToByteArray(double value)
{
    QByteArray array(reinterpret_cast<const char*>(&value), sizeof(value));
    return array;
}
QByteArray RemoteControl::fromIntToByteArray(int value)
{
    QByteArray array(reinterpret_cast<const char*>(&value), sizeof(value));
    return array;
}
QByteArray RemoteControl::fromBooleanToByteArray(bool value)
{
    QByteArray arr;
    return arr;
}
