/*
 * Copyright (C) 2023 by Multi-Tech Systems
 *
 * This file is part of libmts-io.
 *
 * libmts-io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts-io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts-io.  If not, see .
 *
 */
#include 
#include 
#include 
#include 
#include 
using namespace MTS::IO;
bool SequansRadio::resetRadio(uint32_t iTimeoutMillis) {
    printInfo("%s| Rebooting radio", getName().c_str());
    if (sendBasicCommand("AT^RESET") == SUCCESS) {
        if (iTimeoutMillis > 5000) {
            MTS::Thread::sleep(5000);
            iTimeoutMillis -= 5000;
        }
        return resetConnection(iTimeoutMillis);
    }
    return false;
}
ICellularRadio::CODE SequansRadio::getVendorFirmware(std::string& sVendorFirmware) {
    return getFirmware(sVendorFirmware);
}
ICellularRadio::CODE SequansRadio::getModel(std::string& sModel) {
    printTrace("%s| Get Model", getName().c_str());
    //Always returns SUCCESS because the model should be m_sName
    sModel = getName();
    std::string sCmd("AT+CGMM");
    std::string sResult = sendCommand(sCmd);
    if (sResult.find("OK") == std::string::npos) {
        printWarning("%s| Unable to get model from radio.  Returning [%s]", getName().c_str(), getName().c_str());
        return SUCCESS;
    } else {
        sModel = extractModelFromResult(sResult);
        if (sModel.size() == 0) {
            printWarning("%s| Unable to get model from radio.  Returning [%s]", getName().c_str(), getName().c_str());
            return SUCCESS;
        }
    }
    printDebug("%s| Extracted [%s] from [%s] query", getName().c_str(), sModel.c_str(), sCmd.c_str());
    if (sModel != getName()) {
        printWarning("%s| Model identified [%s] does not match expected [%s]. Returning [%s]",
                     getName().c_str(),  sModel.c_str(), getName().c_str(), sModel.c_str());
    }
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::getIccid(std::string& sIccid) {
    printTrace("%s| Get ICCID", getName().c_str());
    const int iEFiccidId = 0x2FE2;
    const uint8_t iOffsetHigh = 0;
    const uint8_t iOffsetLow = 0;
    const uint8_t iNumBytes = 10;
    CODE rc;
    std::string sEFiccidContent;
    rc = simAccessReadBinary(iEFiccidId, iOffsetLow, iOffsetHigh, iNumBytes, sEFiccidContent);
    if (rc != SUCCESS) {
        printError("%s| Failed to determine the SIM ICCID", getName().c_str());
        return rc;
    }
    int iEFiccidLen = sEFiccidContent.size();
    if (iEFiccidLen != 20) {
        printError("%s| Invalid length of the raw ICCID value: expected [20], actual: [%d]", getName().c_str(), iEFiccidLen);
        return FAILURE;
    }
    // sEFiccidContent is a raw Binary-Coded Decimal string with swapped nibbles.
    for (int i = 0; i < iEFiccidLen; i+=2) {
        std::string sPart = sEFiccidContent.substr(i, 2);
        std::swap(sPart[0], sPart[1]);
        sIccid.append(sPart);
    }
    printDebug("%s| Got ICCID [%s]", getName().c_str(), sIccid.c_str());
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::getService(std::string& sService) {
    printTrace("%s| Get Service", getName().c_str());
    sService = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+COPS?");
    std::string sPrefix("+COPS:");
    std::string sResult;
    CODE rc;
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 100);
    if (SUCCESS != rc) {
        printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return rc;
    }
    std::vector vParts = MTS::Text::split(sResult, ',');
    int32_t iAccessTechnology;
    // +COPS: [,[,][,]]
    if (vParts.size() < 4 || !MTS::Text::parse(iAccessTechnology, vParts[3])) {
        printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    switch(iAccessTechnology) {
        case   0 : sService = "GPRS"  ; break;  // GSM
        case   1 : sService = "GPRS"  ; break;  // GSM Compact
        case   2 : sService = "WCDMA" ; break;  // UTRAN
        case   3 : sService = "EGPRS" ; break;  // GSM W/EGPRS
        case   4 : sService = "HSDPA" ; break;  // UTRAN W/HSDPA
        case   5 : sService = "WCDMA" ; break;  // UTRAN W/HSUPA
        case   6 : sService = "HSDPA" ; break;  // UTRAN W/HSDPA and HSUPA
        case   7 : sService = "LTE"   ; break;  // E-UTRAN
        case   8 : sService = "EGPRS" ; break;  // EC-GSM-IoT 
        case   9 : sService = "LTE"   ; break;  // E-UTRAN (NB-S1 mode) 
        default: sService = ICellularRadio::VALUE_UNKNOWN; break;
    }
    printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iAccessTechnology, sService.c_str());
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::getNetwork(std::string& sNetwork) {
    printTrace("%s| Get Network", getName().c_str());
    sNetwork = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+COPS?");
    std::string sPrefix("+COPS:");
    std::string sResult;
    CODE rc;
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 100);
    if (SUCCESS != rc) {
        printWarning("%s| Unable to get network name from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return rc;
    }
    std::vector vParts = MTS::Text::split(sResult, ",");
    if (vParts.size() > 3) {
        const std::string sValue = vParts[2];
        // +COPS: 0,2,"315010",7
        //            ^start ^end
        size_t start = sValue.find("\"") + 1;
        size_t end = sValue.find("\"", start);
        sNetwork = sValue.substr(start, end-start);
    } else {
        sNetwork = "";  // Not connected to any network
    }
    return SUCCESS;
}
// AT+SQNQRCCI? -- This command queries and reports several information from camped cell.
// +SQNQRCCI: [,,,,,,,,,,]
ICellularRadio::CODE SequansRadio::getNetworkStatusSQN(Json::Value& jData, Json::Value& jDebug) {
    std::string sCmd;
    std::string sResult;
    std::string sPrefix;
    std::vector vParts;
    CODE rc;
    sCmd = "AT+SQNQRCCI?";
    sPrefix = "+SQNQRCCI:";
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 200);
    if (SUCCESS != rc) {
        printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return rc;
    }
    // ,,,,,,,,,,
    //  [0]   [1]     [2]    [3]   [4]   [5]            [6]                    [7]               [8]       [9]     [10]
    vParts = MTS::Text::split(sResult, ",");
    if (vParts.size() < 11) {
        printDebug("%s| Network Status command reponse is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str());
        return FAILURE;
    }
    jData[ICellularRadio::KEY_MCC]     = vParts[0];
    jData[ICellularRadio::KEY_MNC]     = vParts[1];
    jData[ICellularRadio::KEY_CID]     = vParts[2];
    jData["tac"]                       = vParts[4];
    jData[ICellularRadio::KEY_CHANNEL] = vParts[8];
    jData[ICellularRadio::KEY_ABND]    = "EUTRAN BAND" + vParts[10]; // concat string to convert to common format
    return SUCCESS;
}
// AT+CESQ? -- Execution command returns received signal quality parameters.
// +CESQ: ,,,,,
ICellularRadio::CODE SequansRadio::getNetworkStatusSignal(Json::Value& jData, Json::Value& jDebug) {
    std::string sCmd;
    std::string sResult;
    std::string sPrefix;
    std::vector vParts;
    CODE rc;
    sCmd = "AT+CESQ";
    sPrefix = "+CESQ:";
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 200);
    if (SUCCESS != rc) {
        printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return rc;
    }
    // ,,,,,
    //   [0]    [1]   [2]    [3]    [4]    [5]
    vParts = MTS::Text::split(sResult, ",");
    if (vParts.size() < 6) {
        printDebug("%s| Network Status command response is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return FAILURE;
    }
    int iValue;
    if (MTS::Text::parse(iValue, vParts[5])) {
        do {
            if (255 == iValue) {
                break; // invalid, not known or not detectable
            }
            if (iValue > 97 || iValue < 0) {
                printDebug("%s| Network Status command returned unexpected value of RSRP: [%s][%d]", getName().c_str(), sCmd.c_str(), iValue);
                break;
            }
            // converting value to rsrp dBm according to documentation
            jDebug["rsrp"] = std::to_string(iValue - 141);
        } while (false);
    }
    if (MTS::Text::parse(iValue, vParts[4])) {
        do {
            if (255 == iValue) {
                break; // invalid, not known or not detectable
            }
            if (iValue > 34 || iValue < 0) {
                printDebug("%s| Network Status command returned unexpected value of RSRQ: [%s][%d]", getName().c_str(), sCmd.c_str(), iValue);
                break;
            }
            // converting value to rsrq in dBm according to documentation
            jDebug["rsrq"] = std::to_string((iValue-40.0)/2.0);
        } while (false);
    }
    return SUCCESS;
}
// AT+SQNQRUP? -- This command queries and reports the uplink power of the current LTE network.
// +SQNQRUP: ,
ICellularRadio::CODE SequansRadio::getNetworkStatusTxPower(Json::Value& jData, Json::Value& jDebug) {
    std::string sCmd;
    std::string sResult;
    std::string sPrefix;
    std::vector vParts;
    int iValue;
    CODE rc;
    sCmd = "AT+SQNQRUP?";
    sPrefix = "+SQNQRUP:";
    // the key must be present
    jDebug[ICellularRadio::KEY_TXPWR] = "";
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 200);
    if (SUCCESS != rc) {
        printDebug("%s| Network Status command returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return rc;
    }
    // ,
    //    [0]        [1]
    vParts = MTS::Text::split(sResult, ",");
    if (vParts.size() < 2) {
        printDebug("%s| Network Status command response is an unknown format: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return FAILURE;
    }
    double fTxPow;
    if (MTS::Text::parse(fTxPow, vParts[0])) {
        do {
            if (-21474836.0 == fTxPow) {
                break; // invalid, not known or not detectable
            }
            if (fTxPow < -255 || fTxPow > 99) {
                printDebug("%s| Network Status command returned unexpected value of txPower: [%s][%d]", getName().c_str(), sCmd.c_str(), iValue);
                break;
            }
            jDebug[ICellularRadio::KEY_TXPWR] = vParts[0];
        } while (false);
    }
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::getNetworkStatus(Json::Value& jData) {
    printTrace("%s| Get Network Status", getName().c_str());
    // NOTE: Only the LTE radios from Sequans are supported at the moment
    Json::Value jDebug;
    std::string sResult;
    CODE rcMainStatus;
    getCommonNetworkStats(jData);
    rcMainStatus = getNetworkStatusSQN(jData, jDebug);
    getNetworkStatusSignal(jData, jDebug);
    getNetworkStatusTxPower(jData, jDebug);
    if (SUCCESS == getImsi(sResult)) {
        jData[ICellularRadio::KEY_IMSI] = sResult;
    }
    if (SUCCESS == getNetwork(sResult)) {
        jData[ICellularRadio::KEY_NETWORK] = sResult;
    }
    // Fill the debug information only if the main tower information was fetched successfully.
    if (SUCCESS == rcMainStatus) {
        jDebug[ICellularRadio::KEY_RSSIDBM] = jData[ICellularRadio::KEY_RSSIDBM];
        jDebug[ICellularRadio::KEY_SD] = "PS";
        jData[ICellularRadio::KEY_DEBUG] = jDebug;
    }
    printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str());
    return SUCCESS;
}
ICellularRadio::CODE MTS::IO::SequansRadio::convertSignalStrengthTodBm(const int32_t &iRssi, int32_t &iDBm) {
    const int dbmSteps   =  2;
    const int minValue   = -113;
    const int maxValue   = -51;
    const int rssiOffset =  0;
    int rawDbm;
    if (iRssi < 0 || iRssi > 99) {
        return FAILURE;  // invalid, not known or not detectable
    }
    rawDbm = minValue + ((iRssi - rssiOffset) * dbmSteps);
    iDBm = std::min(maxValue, rawDbm);
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::convertdBmToSignalStrength(const int32_t& iDBm, int32_t& iRssi) {
    const int dbmSteps   =  2;
    const int minValue   = -113;
    const int rssiOffset =  0;
    if (iDBm < -113) {
        iRssi = 0;
    } else if (iDBm > -51) {
        iRssi = 31;
    } else {
        iRssi = ((iDBm - minValue) / dbmSteps) + rssiOffset;
    }
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::setMdn(const Json::Value& jArgs) {
    printTrace("%s| Set MDN", getName().c_str());
    return NOT_APPLICABLE;
}
ICellularRadio::CODE SequansRadio::setUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION mode) {
    printTrace("%s| Set UE Mode Of Operation", getName().c_str());
    std::string sValue;
    switch (mode) {
        case ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE1:
            sValue = "3";
            break;
        case ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE2:
            sValue = "0";
            break;
        case ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE1:
            sValue = "1";
            break;
        case ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE2:
            sValue = "2";
            break;
        default:
            printError("%s| Set UE Mode Of Operation: invalid argument", getName().c_str());
            return INVALID_ARGS;
    }
    const int dTimeout = 1000; // ms
    const std::string sCmd = "AT+CEMODE=" + sValue;
    return sendBasicCommand(sCmd, dTimeout);
}
ICellularRadio::CODE SequansRadio::getUeModeOfOperation(ICellularRadio::UE_MODES_OF_OPERATION& mode) {
    printTrace("%s| Get UE Mode Of Operation", getName().c_str());
    std::string sCmd = "AT+CEMODE?";
    std::string sPrefix = "+CEMODE:";
    std::string sResult;
    if (SUCCESS != sendBasicQuery(sCmd, sPrefix, sResult, 1000)) {
        printError("%s| Unable to get UE Mode Of Operation from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    uint8_t uiValue;
    if (!MTS::Text::parse(uiValue, sResult)) {
        printError("%s| Unable to parse CEMODE from response [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    CODE rc;
    switch (uiValue) {
        case 0:
            mode = ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE2;
            rc = SUCCESS;
            break;
        case 1:
            mode = ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE1;
            rc = SUCCESS;
            break;
        case 2:
            mode = ICellularRadio::UE_MODES_OF_OPERATION::CS_PS_MODE2;
            rc = SUCCESS;
            break;
        case 3:
            mode = ICellularRadio::UE_MODES_OF_OPERATION::PS_MODE1;
            rc = SUCCESS;
            break;
        default:
            printError("%s| Unable to parse CEMODE from response [%s]", getName().c_str(), sResult.c_str());
            mode = ICellularRadio::UE_MODES_OF_OPERATION::UNKNOWN_MODE;
            rc = FAILURE;
            break;
    }
    return rc;
}
ICellularRadio::CODE SequansRadio::setRxDiversity(const Json::Value& jArgs) {
    return NOT_APPLICABLE;
}
const std::vector SequansRadio::getRegistrationCommands() {
    return { "CEREG" };
}
SequansRadio::SequansRadio(const std::string& sName, const std::string& sRadioPort)
: CellularRadio (sName, sRadioPort)
{
}
ICellularRadio::CODE SequansRadio::getIsSimInserted(bool& bData) {
    printTrace("%s| Get SIM insertion status", getName().c_str());
    // there is no command to check SIM presence
    return NOT_APPLICABLE;
}
ICellularRadio::CODE SequansRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) {
    printTrace("%s| Get SIM unlock attempts left", getName().c_str());
    CODE rc = FAILURE;
    rc = getSimLockAttemptsByType("SIM PIN", iAttemptsPin);
    if (rc != SUCCESS) {
       return rc;
    }
    rc = getSimLockAttemptsByType("SIM PUK", iAttemptsPuk);
    if (rc != SUCCESS) {
        return rc;
    }
    return SUCCESS;
}
ICellularRadio::CODE SequansRadio::getSimLockAttemptsByType(const std::string& sArg, int& iAttempts) {
    std::string sCmd("AT+CPINR");
    std::string sPrefix("+CPINR:");
    std::string sResult;
    int iValue;
    CODE rc = FAILURE;
    sCmd += "=\"";
    sCmd += sArg;
    sCmd += "\"";
    printDebug("%s| Sending SIM lock attempt query [%s]", getName().c_str(), sCmd.c_str());
    rc = sendBasicQuery(sCmd, sPrefix, sResult, 500);
    if (SUCCESS != rc) {
        return rc;
    }
    std::vector vParts = MTS::Text::split(sResult, ',');
    if (vParts.size() < 3 || ! MTS::Text::parse(iValue, vParts[1])) {
        printWarning("%s| Unable to parse SIM unlock attempts from response [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    iAttempts = iValue;
    return SUCCESS;
}
const std::vector& SequansRadio::getDiagCommands(bool) {
    // TODO: Fill the list of commands in scope of the "L6G1 - Cellular Diagnostics" feature
    // Declare as static to initialize only when used, but cache the results.
    const static std::vector vCommands {
    };
    return vCommands;
}