/*
 * Copyright (C) 2019 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;
const size_t TelitRadio::FILE_CHUNK_SIZE = 1024;
const std::string TelitRadio::CMD_ABORT_UPLOAD = "+++";
TelitRadio::TelitRadio(const std::string& sName, const std::string& sRadioPort)
: CellularRadio(sName, sRadioPort)
{
}
bool TelitRadio::resetRadio(uint32_t iTimeoutMillis) {
    printInfo("%s| Rebooting radio", getName().c_str());
    if(sendBasicCommand("AT#REBOOT") == SUCCESS) {
        if(iTimeoutMillis > 5000) {
            MTS::Thread::sleep(5000);
            iTimeoutMillis -= 5000;
        }
        return resetConnection(iTimeoutMillis);
    }
    return false;
}
ICellularRadio::CODE TelitRadio::getFirmwareBuild(std::string& sFirmwareBuild) {
    std::string sCmd("AT#CFVR");
    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get firmware build number [%s]",
                getName().c_str(),
                sCmd.c_str());
        return FAILURE;
    }
    size_t start = sResult.find("#CFVR:");
    if (start == std::string::npos) {
        printWarning("%s| Command returned unexpected response [%s]",
                getName().c_str(),
                sCmd.c_str());
        return FAILURE;
    }
    start += sizeof("#CFVR:");
    sFirmwareBuild = MTS::Text::trim(sResult.substr(start, end-start));
    if(sFirmwareBuild.size() == 0) {
        printWarning("%s| Firmware Build Version is empty", getName().c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::getVendorFirmware(std::string& sVendorFirmware) {
    printTrace("%s| Get Telit-specific firmware version", getName().c_str());
    ICellularRadio::CODE rc = FAILURE;
    sVendorFirmware = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sFirmware;
    std::string sFirmwareBuild;
    std::string sCmd("AT#SWPKGV");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    do {
        if (pos != std::string::npos) {
            // Found
            std::vector vLine = MTS::Text::split(sResult, "\r");
            sVendorFirmware = MTS::Text::trim(vLine[1]);
            if(sVendorFirmware.size() == 0) {
                printWarning("%s| Unable to get firmware from radio using command [%s]", getName().c_str(), sCmd.c_str());
                rc = FAILURE;
            } else {
                rc = SUCCESS;
            }
            break;
        }
        // Not Found. Then we will use "AT+CGMR" + "AT#CFVR"
        rc = getFirmware(sFirmware);
        if (rc != SUCCESS){
            break;
        }
        rc = getFirmwareBuild(sFirmwareBuild);
        if (rc != SUCCESS){
            break;
        }
        sVendorFirmware = sFirmware + "," + sFirmwareBuild;
    } while (false);
    return rc;
}
ICellularRadio::CODE TelitRadio::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("ATI4");
    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 TelitRadio::getIccid(std::string& sIccid) {
    printTrace("%s| Get ICCID", getName().c_str());
    sIccid = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT#CCID");
    std::string sResult = CellularRadio::sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    size_t start = sResult.find("#CCID:");
    if(start != std::string::npos) {
        start += sizeof("#CCID:");
        sIccid = MTS::Text::trim(sResult.substr(start, end-start));
        if(sIccid.size() == 0) {
            printWarning("%s| Unable to get ICCID from radio using command [%s]", getName().c_str(), sCmd.c_str());
            return FAILURE;
        }
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::getService(std::string& sService) {
    printTrace("%s| Get Service", getName().c_str());
    sService = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT#PSNT?");
    std::string sResult = CellularRadio::sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get Service from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    size_t start = sResult.find(",");
    if(start != std::string::npos) {
        start += 1; //comma
        std::string sPsnt = MTS::Text::trim(sResult.substr(start, end-start));
        int32_t iService;
        sscanf(sPsnt.c_str(), "%d", &iService);
        switch(iService) {
            case 0: sService = "GPRS"; break;
            case 1: sService = "EGPRS"; break;
            case 2: sService = "WCDMA"; break;
            case 3: sService = "HSDPA"; break;
            case 4: sService = "LTE"; break;
            default: sService = ICellularRadio::VALUE_UNKNOWN; break;
        }
        printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iService, sService.c_str());
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::getNetwork(std::string& sNetwork) {
    Json::Value jData;
    printTrace("%s| Get Network", getName().c_str());
    sNetwork = ICellularRadio::VALUE_NOT_SUPPORTED;
    if(getNetworkStatus(jData) == SUCCESS) {
        if(jData.isMember(ICellularRadio::KEY_NETWORK)) {
            sNetwork = jData[ICellularRadio::KEY_NETWORK].asString();
            return SUCCESS;
        }
    }
    return FAILURE;
}
/*  AT#RFSTS - NETWORK STATUS
    (GSM network)
    #RFSTS:,,,,,,,,,,,,,
    Where:
     - Country code and operator code(MCC, MNC)
     - GSM Assigned Radio Channel
     - Received Signal Strength Indication
     - Localization Area Code
     - Routing Area Code
     - Tx Power
     - Mobility Management state
     - Radio Resource state
     - Network Operator Mode
     - Cell ID
     - International Mobile Subscriber Identity
     - Operator name
     - Service Domain
    0 - No Service
    1 - CS only
    2 - PS only
    3 - CS+PS
     - Active Band
    1 - GSM 850
    2 - GSM 900
    3 - DCS 1800
    4 - PCS 1900
    (WCDMA network)
    #RFSTS:
    ,,,,, RSSI>,,,,,,,,,,,
    ,,[,,]
    Where:
     - Country code and operator code(MCC, MNC)
     - UMTS Assigned Radio Channel
     - Active PSC(Primary Synchronization Code)
     - Active Ec/Io(chip energy per total wideband power in dBm)
     - Active RSCP (Received Signal Code Power in dBm)
     - Received Signal Strength Indication
     - Localization Area Code
     - Routing Area Code
     - Tx Power
     - Discontinuous reception cycle Length (cycle length in ms)
     - Mobility Management state
     - Radio Resource state
     - Network Operator Mode
     - Block Error Rate (e.g., 005 means 0.5 %)
     - Cell ID
     - International Mobile Station ID
     - Operator name
     - Service Domain (see above)
     - Number of Active Set (Maximum 6)
     UARFCN of n th active set
     PSC of n th active set
     Ec/Io of n th active Set
    (LTE Network)
    #RFSTS:
     -
     -
     -
     -
     -
     -
    [] -
     -
     -
     -
     -
     -
    [] -
     -
     -
*/
ICellularRadio::CODE TelitRadio::getNetworkStatus(Json::Value& jData) {
    int32_t iValue;
    std::string sValue;
    const uint32_t GSM_NETWORK_FORMAT = 14;
    const uint32_t WCDMA_NETWORK_FORMAT = 19;
    const uint32_t LTE_NETWORK_FORMAT = 16;
    printTrace("%s| Get Network Status", getName().c_str());
    //Always get common network stats because this should never fail
    //This way the basic stats are always returned even if AT#RFSTS fails below
    getCommonNetworkStats(jData);
    std::string sCmd;
    std::string sResult;
    // LE910 radios have a bug where issuing AT#RFSTS with a locked SIM
    // will cause the radio to stop responding until a radio power cycle
    // Telit Support Portal Case #5069697
    // LE910C1-NS is an LE910, so we stop the scan after the 0.
    if (getName().find("LE910") != std::string::npos) {
        sCmd = "AT+CPIN?";
        sResult = sendCommand(sCmd);
        if (sResult.find("+CPIN:") == std::string::npos) {
            printDebug("%s| AT+CPIN? returned unexpected response: [%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 SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function
        }
        if (sResult.find("SIM PIN") != std::string::npos) {
            printError("%s| The SIM is locked and must first be unlocked", getName().c_str());
            printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str());
            return SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function
        }
    }
    sCmd = "AT#RFSTS";
    sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 200);
    if (sResult.find("#RFSTS:") == std::string::npos) {
        //On LTE radios without signal, this case will run because AT#RFSTS just returns "OK"
        printDebug("%s| Network Status command returned unexpected response: [%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 SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function
    }
    size_t start = sResult.find(":") + 1; //Position right after "#RFSTS:"
    std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start)), ",");
    if (vParts.size() < 3) {
        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 SUCCESS; //return SUCCESS because getCommonNetworkStats() succeeded at top of this function
    } else {
        //Country Code and Operator Code
        std::vector vPLMN = MTS::Text::split(vParts[0], ' ');
        if(vPLMN.size() == 2) {
            jData[ICellularRadio::KEY_MCC] = MTS::Text::strip(vPLMN[0], '"');
            jData[ICellularRadio::KEY_MNC] = MTS::Text::strip(vPLMN[1], '"');
        }
        jData[ICellularRadio::KEY_CHANNEL] = vParts[1];
    }
    if (vParts.size() == GSM_NETWORK_FORMAT ) {
        //Parse as GSM Network Format
        jData[ICellularRadio::KEY_RSSIDBM] = vParts[2];
        jData[ICellularRadio::KEY_LAC] =  vParts[3];
        jData[ICellularRadio::KEY_RAC] = vParts[4];
        jData[ICellularRadio::KEY_TXPWR] = vParts[5];
        jData[ICellularRadio::KEY_MM] = vParts[6];
        jData[ICellularRadio::KEY_RR] = vParts[7];
        jData[ICellularRadio::KEY_NOM] = vParts[8];
        jData[ICellularRadio::KEY_CID] = vParts[9];
        jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[10], '"');
        jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[11], '"');
        if(MTS::Text::parse(iValue, vParts[12]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) {
            jData[ICellularRadio::KEY_SD] = sValue;
        }
        if(MTS::Text::parse(iValue, vParts[13]) && convertActiveBandToString((ACTIVEBAND)iValue, sValue) == SUCCESS) {
            jData[ICellularRadio::KEY_ABND] = sValue;
        }
      // IN003567 ME910C1 radios have some odd behavior with regards to WCDMA.  The ordering of the fields from #RFSTS are
      // the same as LTE up to the 16th field (for ME901C1-WW anyway).  Drop into LTE parsing for ME910C1-WW.
    } else if((vParts.size() >= WCDMA_NETWORK_FORMAT) && (getName().find("ME910C1-WW") == std::string::npos)) {
        Json::Value jDebug;
        //Parse as WCDMA Network Format
        jDebug[ICellularRadio::KEY_PSC] = vParts[2];
        jDebug[ICellularRadio::KEY_ECIO] = vParts[3];
        jDebug[ICellularRadio::KEY_RSCP] = vParts[4];
        jData[ICellularRadio::KEY_RSSIDBM] = vParts[5];
        jData[ICellularRadio::KEY_LAC] = vParts[6];
        jData[ICellularRadio::KEY_RAC] = vParts[7];
        jDebug[ICellularRadio::KEY_TXPWR] = vParts[8];
        jDebug[ICellularRadio::KEY_DRX] = vParts[9];
        jDebug[ICellularRadio::KEY_MM] = vParts[10];
        jDebug[ICellularRadio::KEY_RR] = vParts[11];
        jDebug[ICellularRadio::KEY_NOM] = vParts[12];
        if(vParts[13].size() != 0) {
            jDebug[ICellularRadio::KEY_BLER] = vParts[13];
        } else {
            jDebug[ICellularRadio::KEY_BLER] = "000";
        }
        jData[ICellularRadio::KEY_CID] = vParts[14];
        jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[15], '"');
        jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[16], '"');
        // Get the radio band given the channel (UARFCN)
        RadioBandMap radioBandMap(vParts[1], CellularRadio::ICellularRadio::VALUE_TYPE_CDMA);
        jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName();
        if(MTS::Text::parse(iValue, vParts[17]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) {
            jDebug[ICellularRadio::KEY_SD] = sValue;
        }
        //Ignoring Active Set Values
        //   - Number of Active Set (Maximum 6)
        //   - UARFCN of n th active set
        //   - PSC of n th active set
        //   - Ec/Io of n th active Set
        jData[ICellularRadio::KEY_DEBUG] = jDebug;
    } else if(vParts.size() >= LTE_NETWORK_FORMAT) {
        Json::Value jDebug;
        //Parse as LTE Network Format
        //
        // MD: It is noticed that LTE Network format may vary depending on the firmware version:
        //
        // ,,,,,,[],,,,,,[],,,
        //    Ex 1: #RFSTS: "310 260",2300,-98,-63,-14,AA06,,128,19,0,0501D02,"310260754792598","T-Mobile",3,4,197
        //
        // ,,,,,,,[],,,,,,[],,
        //    Ex 2: #RFSTS:"310 410",5780,-105,-73,-14,4603,255,,128,19,0,0000098,"310410536498694","AT&T",3,17
        //          #RFSTS:"311 480",1150,-96,-66,-9.0,bf35,FF,0,0,19,1,"2ED1B0E","311480148817753","Verizon",2,2,720000,10800
        //          #RFSTS:"310 410",2175,-120,-89,-17.5,4612,FF,0,0,19,1,"4E5E916","310410807276607","AT&T",3,4
        //
        // Additional  parameter in the second example shifts the rest of the parameters. Here we are trying to figure out
        // which format is currently produced based on  field position which always has double quotation marks.
        //
        if (vParts[13].find("\"") != std::string::npos) {
            // parse the RAC and then remove it from the vector
            jData[ICellularRadio::KEY_RAC] = vParts[6];
            vParts.erase(vParts.begin() + 6);
        }
        jDebug["rsrp"] = vParts[2];
        jDebug[ICellularRadio::KEY_RSSIDBM] = vParts[3];
        jDebug["rsrq"] = vParts[4];
        jData["tac"] = vParts[5];
        jDebug[ICellularRadio::KEY_TXPWR] = vParts[6];
        jData[ICellularRadio::KEY_DRX] = vParts[7];
        jDebug[ICellularRadio::KEY_MM] = vParts[8];
        jDebug["rrc"] = vParts[9];
        jData[ICellularRadio::KEY_CID] = MTS::Text::strip(vParts[10], '"');
        jData[ICellularRadio::KEY_IMSI] = MTS::Text::strip(vParts[11], '"');
        jData[ICellularRadio::KEY_NETWORK] = MTS::Text::strip(vParts[12], '"');
        // Get the radio band given the channel (EARFCN)
        RadioBandMap radioBandMap(vParts[1], ICellularRadio::VALUE_TYPE_LTE);
        jData[ICellularRadio::KEY_ABND] = radioBandMap.getRadioBandName();
        jData[ICellularRadio::KEY_LAC] = queryLteLac();
        if(MTS::Text::parse(iValue, vParts[13]) && convertServiceDomainToString((SERVICEDOMAIN)iValue, sValue) == SUCCESS) {
            jDebug[ICellularRadio::KEY_SD] = sValue;
        }
        jData[ICellularRadio::KEY_DEBUG] = jDebug;
    }
    printTrace("%s| Network Status:\n%s\n", getName().c_str(), jData.toStyledString().c_str());
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::convertSignalStrengthTodBm(const int32_t& iRssi, int32_t& iDbm) {
    //Telit Conversion
    if(iRssi < 0 || iRssi == 99) {
        return FAILURE;
    }
    if(iRssi == 0) {
        iDbm = -113;
    } else if(iRssi == 1) {
        iDbm = -111;
    } else if(iRssi <= 30) {
        //28 steps between 2 and 30
        //54 dbm between 53 and 109
        float stepSize = 54.0 / 28.0;
        iDbm = -109 + (int)(stepSize * (iRssi-2));
    } else {
        iDbm = -51;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::convertdBmToSignalStrength(const int32_t& iDBm, int32_t& iRssi) {
    //Telit Conversion
    if(iDBm <= -113) {
        iRssi = 0;
    } else if(iDBm <= -111) {
        iRssi = 1;
    } else if(iDBm <= -53) {
        //54 dbm between -109 and -53
        //28 steps between 2 and 30
        float stepSize = 28.0/54.0;
        iRssi = ((iDBm + 109)*stepSize) + 2;
    } else {
        iRssi = 31;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::setMdn(const Json::Value& jArgs) {
    printTrace("%s| Set MDN", getName().c_str());
    if(!jArgs["mdn"].isString()) {
        return INVALID_ARGS;
    }
    std::string sCmd("AT#SNUM=1,\"");
    sCmd += jArgs["mdn"].asString() + "\"";
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MDN for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
bool TelitRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) {
    // Telit Radios
    //    H.ab.zyx => 3 Main Components
    //    "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family
    //    "a" = Hardware version
    //    "b" = Software Major Version
    //    "z" = is the product type, i.e. DUAL or SC
    //    "y" = is the carrier variant
    //    "x" = is the firmware version
    //    Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, "2" for Verizon, and "3" for U.S. Cellular.
    const uint32_t CARRIER_INDEX = 1;   //y in [zyx]
    bool bResult = false;
    std::vector vParts = MTS::Text::split(sFirmware, '.');
    if(vParts.size() == 3) {
        //CDMA firmware version notation
        if(vParts[0] == "15" || vParts[0] == "18") {
            //DE910 or CE910 -> Good good
            std::string sID = vParts[2];
            if(sID.size() == 3) {
                char cId = sID[CARRIER_INDEX];
                //Good good
                if(cId == '0') {
                    sCarrier = ICellularRadio::VALUE_CARRIER_SPRINT;
                    bResult = true;
                } else
                if(cId == '1') {
                    sCarrier = ICellularRadio::VALUE_CARRIER_AERIS;
                    bResult = true;
                } else
                if(cId == '2') {
                    sCarrier = ICellularRadio::VALUE_CARRIER_VERIZON;
                    bResult = true;
                } else
                if(cId == '3') {
                    sCarrier = ICellularRadio::VALUE_CARRIER_USCELLULAR;
                    bResult = true;
                }
            }
        }
    }
    return bResult;
}
bool TelitRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) {
    // Telit Radios
    //    H.ab.zyx => 3 Main Components
    //    "H" = Hardware -> 15 = DE910 family, 18 = CE910 family, 12 = HE910 family
    //    "a" = Hardware version
    //    "b" = Software Major Version
    //    "z" = is the product type, i.e. DUAL or SC
    //    "y" = is the carrier variant
    //    "x" = is the firmware version
    //    Telit will do their best to keep the carrier variant as "0" for Sprint, "1" for Aeris, and "2" for Verizon.
    const uint32_t HARDWARE_INDEX = 0;   //a in [ab]
    bool bResult = false;
    std::vector vParts = MTS::Text::split(sFirmware, '.');
    if(vParts.size() == 3) {
        //GSM Hardware Version
        if(!(vParts[0] == "15" || vParts[0] == "18")) {
            //Not DE910 or CE910 -> Good good
            std::string sVersion = vParts[1];
            if(sVersion.size() == 2) {
                sHardware = "1.";
                sHardware += sVersion[HARDWARE_INDEX];
                bResult = true;
            }
        }
    }
    return bResult;
}
ICellularRadio::CODE TelitRadio::getIsSimInserted(bool& bData) {
    printTrace("%s| Get SIM insertion status", getName().c_str());
    std::string sCmd("AT#SIMDET?");
    std::string sResult = sendCommand(sCmd);
    const std::string sPrefix = "#SIMDET: ";
    size_t start = sResult.find(sPrefix);
    size_t end = sResult.rfind(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get SIM insertion status from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    if (start == std::string::npos) {
        printDebug("%s| AT#SIMDET? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return FAILURE;
    }
    // #SIMDET: ,
    start += sPrefix.size();
    std::vector vParts = MTS::Text::split(MTS::Text::trim(sResult.substr(start, end-start)), ',');
    if(vParts.size() != 2) {
        printWarning("%s| Unable to parse SIM insertion status from response [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    if (vParts[1] == "1") { // 
        bData = true;
    } else {
        bData = false;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk) {
    std::string sLockStatus;
    ICellularRadio::CODE retCode;
    retCode = getSimLockStatus(sLockStatus);
    if (retCode != SUCCESS) {
        printWarning("%s| Unable determine the number of SIM unlock attempts: SIM lock status is unavailable [%s]", getName().c_str(), sLockStatus.c_str());
        return retCode;
    }
    return getSimLockAttempts(iAttemptsPin, iAttemptsPuk, sLockStatus);
}
ICellularRadio::CODE TelitRadio::getSimLockAttempts(int& iAttemptsPin, int& iAttemptsPuk, const std::string& sLockStatus) {
    printTrace("%s| Get SIM unlock attempts left", getName().c_str());
    std::string sCmd("AT#PCT");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);
    std::string sValue;
    int iValue;
    const std::string sPrefix = "#PCT: ";
    size_t start = sResult.find(sPrefix);
    size_t end = sResult.rfind(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get SIM unlock attempts from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }
    if (start == std::string::npos) {
        printDebug("%s| AT#PCT? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return FAILURE;
    }
    // #PCT: 
    start += sPrefix.size();
    sValue = MTS::Text::trim(sResult.substr(start, end-start));
    if (!MTS::Text::parse(iValue, sValue)) {
        printWarning("%s| Unable to parse SIM unlock attempts from response [%s]", getName().c_str(), sResult.c_str());
        return FAILURE;
    }
    if (sLockStatus == "READY" || sLockStatus == "SIM PIN") {
        iAttemptsPin = iValue;  // Some PIN attempts left, maximum PUK attempts left
        iAttemptsPuk = 10;
    } else {
        iAttemptsPin = 0;  // No PIN attempts left
        iAttemptsPuk = iValue;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::getSupportedCellularModes(CELLULAR_MODES &networks) {
    networks = CELLULAR_MODE_NA;
    std::set wds;
    if ( TelitRadio::wdsList(wds) != SUCCESS ) {
        return FAILURE;
    }
    for(const auto &it : wds) {
        switch (it) {
        case 12: networks = static_cast(networks | CELLULAR_MODE_2G); break;
        case 22: networks = static_cast(networks | CELLULAR_MODE_3G); break;
        case 28: networks = static_cast(networks | CELLULAR_MODE_4G); break;
        case 36: networks = static_cast(networks | CELLULAR_MODE_5G); break;
        }
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::wdsList(std::set &wds)
{
    std::string sCmd("AT+WS46=?");
    std::string cmdResult = sendCommand(sCmd);
    if (cmdResult.find("+WS46:") == std::string::npos) {
        printError("%s| AT+WS46=? returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), cmdResult.c_str());
        return FAILURE;
    }
    if (cmdResult.find('(') == std::string::npos) {
        printError("AT+WS46: error responce %s", cmdResult.c_str());
        return FAILURE;
    }
    std::string s = MTS::Text::split(cmdResult, '(')[1];
    s = MTS::Text::split(s, ')')[0];
    std::vectorv = MTS::Text::split(s, ',');
    for(const auto &it : v)
    {
        if (it.find("-") != std::string::npos) {
            const std::vector &r = MTS::Text::split(it, "-");
            int begin, end;
            if ( ! MTS::Text::parse(begin, r[0]) || ! MTS::Text::parse(end, r[1])) {
                printError("AT+WS46: error parsing network mode range: %s-%s", r[0].c_str(), r[1].c_str());
                return FAILURE;
            }
            for (int i = begin; i<=end; ++i) {
                wds.insert(i);
                if (wds.size()>1024)
                    break;
            }
        } else {
            int v;
            if ( ! MTS::Text::parse(v, it)) {
                printError("AT+WS46: error parsing network mode: %s", it.c_str());
                return FAILURE;
            }
            wds.insert(v);
        }
    }
    if (wds.size()>1024) {
        printError("AT+WS46: network modes count overflow, parsing error");
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::setCellularMode(CELLULAR_MODES networks) {
    std::set supportedWds;
    if ( TelitRadio::wdsList(supportedWds) != SUCCESS ) {
        return FAILURE;
    }
    int wds = 0;
    // 3GPP TS 27.007
    // https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1515
    switch (static_cast(networks)) {
    case CELLULAR_MODE_2G                                                         : wds = 12; break;
    case                    CELLULAR_MODE_3G                                      : wds = 22; break;
    case CELLULAR_MODE_2G | CELLULAR_MODE_3G | CELLULAR_MODE_4G                   : wds = 25; break;
    case                                       CELLULAR_MODE_4G                   : wds = 28; break;
    case CELLULAR_MODE_2G | CELLULAR_MODE_3G                                      : supportedWds.count(28) ? wds = 29 : wds = 25; break;
    case CELLULAR_MODE_2G |                    CELLULAR_MODE_4G                   : wds = 30; break;
    case                    CELLULAR_MODE_3G | CELLULAR_MODE_4G                   : wds = 31; break;
    case CELLULAR_MODE_2G | CELLULAR_MODE_3G | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 35; break;
    case                                                          CELLULAR_MODE_5G: wds = 36; break;
    case                                       CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 37; break;
    case                    CELLULAR_MODE_3G | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 38; break;
    case CELLULAR_MODE_2G                    | CELLULAR_MODE_4G | CELLULAR_MODE_5G: wds = 39; break;
    case                    CELLULAR_MODE_3G                    | CELLULAR_MODE_5G: wds = 40; break;
    case CELLULAR_MODE_2G | CELLULAR_MODE_3G                    | CELLULAR_MODE_5G: wds = 41; break;
    case CELLULAR_MODE_2G                                       | CELLULAR_MODE_5G: wds = 42; break;
    }
    std::string sCmd("AT+WS46=");
    sCmd += std::to_string(wds);
    std::string cmdResult = sendCommand(sCmd);
    if (cmdResult.find(ICellularRadio::RSP_OK) == std::string::npos) {
        printError("%s| AT+WS46= returned unexpected response: [%s][%s]", getName().c_str(), sCmd.c_str(), cmdResult.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::updateFumoLocal(int fd, ICellularRadio::UpdateCb& stepCb) {
    CODE rc = FAILURE;
    rc = fumoLocalInject(fd, stepCb);
    if (rc != SUCCESS) {
        return rc;
    }
    rc = fumoLocalApply(stepCb);
    return rc;
}
ICellularRadio::CODE TelitRadio::fumoLocalInject(int fd, ICellularRadio::UpdateCb& stepCb) {
    CODE rc = FAILURE;
    FOTA_GROUP group = getFotaGroup();
    do {
        callNextStep(stepCb, "FUMO Info: downloading the firmware");
        if ((group == VALUE_GROUP_A) || (group == VALUE_GROUP_B) || (group == VALUE_GROUP_D)) {
            rc = fumoWriteGroupsABD(fd, stepCb);
            if (rc != SUCCESS) {
                printError("Failed to inject the delta file.");
                callNextStep(stepCb, "FUMO Error: failed to download the firmware file");
                break;
            }
        } else if (group == VALUE_GROUP_C) {
            //TODO Not Implemented TelitRadio::fumoWriteGroupC
            printError("Failed to inject the delta file.");
            callNextStep(stepCb, "FUMO Error: not implemented");
            rc = NOT_APPLICABLE;
            break;
        } else {
            printError("Delta firmware upgrade is not supported for this type of radio modem");
            callNextStep(stepCb, "FUMO Error: delta firmware upgrade is not supported for this type of radio modem");
            rc = NOT_APPLICABLE;
            break;
        }
        callNextStep(stepCb, "FUMO Info: firmware downloaded successfully");
    } while (false);
    return rc;
}
ICellularRadio::CODE TelitRadio::fumoLocalApply(ICellularRadio::UpdateCb& stepCb) {
    ICellularRadio::CODE rc;
    std::string sCmd;
    FOTA_GROUP group = getFotaGroup();
    rc = getVendorFirmware(m_sTelitFirmware);
    if (rc != SUCCESS) {
        callNextStep(stepCb, "FUMO Error: Failed to obtain current firmware version");
        return rc;
    }
    printInfo("Current firmware version: %s", m_sTelitFirmware.c_str());
    if ((group == VALUE_GROUP_A) || (group == VALUE_GROUP_D)) {
        // Send "AT#OTAUP=0,0" command to start the upgrade. OK response follows shortly.
        sCmd  = "AT#OTAUP=0,0";
    } else if ((group == VALUE_GROUP_B) || (group == VALUE_GROUP_C)) {
        // Send "AT#OTAUP=2" command to start the upgrade. OK response follows shortly.
        sCmd  = "AT#OTAUP=2";
    } else {
        printError("Delta firmware upgrade is not supported for this type of radio modem");
        callNextStep(stepCb, "FUMO Error: delta firmware upgrade is not supported for this type of radio modem");
        return NOT_APPLICABLE;
    }
    rc = sendBasicCommand(sCmd, 10000);
    if (rc != SUCCESS) {
        printError("FUMO failed, OK not received from the radio");
        callNextStep(stepCb, "FUMO Error: failed to apply the firmware");
        return rc;
    }
    const uint32_t duDetachTimeout = 10000;  // wait for 10 seconds for the radio to detach
    do {
        printInfo("Applying the radio firmware");
        callNextStep(stepCb, "FUMO Info: applying the radio firmware");
        // Wait for the radio to detach from the USB bus
        MTS::Thread::sleep(duDetachTimeout);
        rc = fumoWaitUpgradeFinished(stepCb);
        if (rc != SUCCESS) {
            break;
        }
        rc = fumoCheckNewFirmware(stepCb);
    } while (false);
    if (rc == SUCCESS) {
        printInfo("Radio firmware applied successfully");
        callNextStep(stepCb, "FUMO Done: radio firmware applied successfully");
    } else {
        printError("Radio firmware has not been updated");
        callNextStep(stepCb, "FUMO Error: radio firmware has not been updated");
    }
    return rc;
}
TelitRadio::FOTA_GROUP TelitRadio::getFotaGroup() {
    return VALUE_UNKNOWN;
}
ICellularRadio::CODE TelitRadio::fumoWriteGroupsABD(int fd, ICellularRadio::UpdateCb& stepCb) {
    size_t dPayloadLength;
    size_t nChunks;
    CODE rc;
    rc = getFileSize(fd, dPayloadLength);
    if (rc != SUCCESS) {
        return rc;
    }
    rc = sizeToChunks(dPayloadLength, FILE_CHUNK_SIZE, nChunks);
    if (rc != SUCCESS) {
        return rc;
    }
    printTrace("File size: %d bytes and %d chunks", dPayloadLength, nChunks);
    printTrace("Starting file upload...");
    rc = startFotaWriteABD();
    if (rc != SUCCESS) {
        return rc;
    }
    printTrace("File upload started.");
    callNextStep(stepCb, "FILE Info: Started file upload");
    size_t nChunksPerCent = (nChunks / 100) + 1;
    size_t nFragmentLength = 0;
    std::array vBuffer;
    for (size_t iChunk = 1; iChunk < (nChunks + 1); iChunk++) {
        rc = readChunk(fd, vBuffer.data(), vBuffer.size(), nFragmentLength);
        if (rc != SUCCESS) {
            break;
        }
        rc = sendData(vBuffer.data(), nFragmentLength);
        if (rc != SUCCESS) {
            break;
        }
        if (stepCb && ((iChunk % nChunksPerCent) == 0)) {
            size_t dPercentsCompleted = iChunk / nChunksPerCent;
            callNextStep(stepCb, "FILE Info: Uploaded " + MTS::Text::format(dPercentsCompleted) + "%");
        }
    }
    if (rc != SUCCESS) {
        callNextStep(stepCb, "FILE Error: Upload failed due to internal error");
    } else {
        callNextStep(stepCb, "FILE Info: Upload finished successfully");
    }
    // send +++
    abortFotaWriteABD();
    return rc;
}
ICellularRadio::CODE TelitRadio::startFotaWriteABD() {
    const std::vector vBailStrings{ ICellularRadio::RSP_CONNECT, ICellularRadio::RSP_ERROR };
    const int dTimeout = 1000; //ms
    std::string sCommand, sResult;
    sCommand = "AT#OTAUPW";
    sResult = sendCommand(sCommand, vBailStrings, dTimeout);
    if (sResult.find(ICellularRadio::RSP_CONNECT) == std::string::npos) {
        printError("Radio is not ready to accept the file: [%s]", sResult.c_str());
        return FAILURE;
    }
    return SUCCESS;
}
ICellularRadio::CODE TelitRadio::abortFotaWriteABD() {
    /*
     * To prevent the “+++” from being mistaken for data, the following sequence should be followed:
     * 1) Do not input any character within 1s or longer before inputting “+++”.
     * 2) Input “+++” within 1s, and no other characters can be inputted during the time.
     * 3) Do not input any character within 1s after “+++” has been inputted.
     */
    sleep(1);
    return sendBasicCommand(CMD_ABORT_UPLOAD, 2000, 0x00);
}
ICellularRadio::CODE TelitRadio::fumoWaitUpgradeFinished(ICellularRadio::UpdateCb& stepCb) {
    const uint32_t duAttachTimeout = 6 * 60 * 1000;// wait for 6 minutes for the radio to attach
    const uint32_t duUrcTimeout = 60 * 1000;       // wait for 1 minutes for the next URC message
    const std::string sFotaUrcPrefix = "#OTAEV:";  // prefix for the URC notification messages
    const std::string sFotaUrcEndSuccess = "Module Upgraded To New Fw";
    const std::string sFotaUrcEndFailed = "OTA Fw Upgrade Failed";
    const std::vector vFotaBailStrings{ sFotaUrcPrefix };
    CODE rc = FAILURE;
    std::string sResponse;
    // It's now detached. Try to reconnect
    if (!resetConnection(duAttachTimeout)) {
        printError("Can't connect to the radio in %d ms", (duAttachTimeout));
        callNextStep(stepCb, "FUMO Error: unable to obtain radio after reset");
        return ERROR;
    }
    while (true) {
        sResponse = sendCommand("", vFotaBailStrings, duUrcTimeout, 0x00);
        printTrace("Radio response: [%s]", sResponse.c_str());
        if (sResponse.find(sFotaUrcPrefix) == std::string::npos) {
            printError("No URC messages from the radio in %d ms", duUrcTimeout);
            callNextStep(stepCb, "FUMO Error: timeout, radio is not responding");
            rc = ERROR;
            break;
        }
        if (sResponse.find(sFotaUrcEndSuccess) != std::string::npos) {
            printTrace("Radio module upgraded to new firmware");
            callNextStep(stepCb, "FUMO Info: radio module upgraded to new firmware");
            rc = SUCCESS;
            break;
        }
        if (sResponse.find(sFotaUrcEndFailed) != std::string::npos) {
            printTrace("Radio module firmware upgrade failed");
            callNextStep(stepCb, "FUMO Error: firmware upgrade failed");
            rc = ERROR;
            break;
        }
    }
    return rc;
}
ICellularRadio::CODE TelitRadio::fumoCheckNewFirmware(ICellularRadio::UpdateCb& stepCb) {
    CODE rc = SUCCESS;
    std::string sTelitFirmware;
    rc = getVendorFirmware(sTelitFirmware);
    if (rc != SUCCESS) {
        callNextStep(stepCb, "FUMO Error: Failed to obtain current firmware version");
        return rc;
    }
    printInfo("Firmware version before the upgrade: %s", m_sTelitFirmware.c_str());
    printInfo("Current firmware version: %s", sTelitFirmware.c_str());
    if (sTelitFirmware == m_sTelitFirmware) {
        // Radio will not reset anymore, firmware version left the same, not updated
        printError("Radio firmware version not changed after upgrade");
        rc = FAILURE;
    }
    return rc;
}