/*
 * 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 <http://www.gnu.org/licenses/>.
 *
 */

#include "mts/MTS_IO_CellularRadio.h"

#include <unistd.h>
#include <limits.h>

#include <mts/MTS_IO_MccMncTable.h>
#include <mts/MTS_Thread.h>
#include <mts/MTS_Timer.h>
#include <mts/MTS_Logger.h>
#include <mts/MTS_Text.h>

using namespace MTS::IO;

namespace {
    typedef struct
    {
        const char    *name;
        int32_t        low;
        int32_t        high;
    } *pNameRangeMap, nameRangeMap;

    const unsigned int NUM_GSM_BANDS = 7;
    const unsigned int NUM_WCDMA_BANDS = 6;
    const unsigned int NUM_LTE_BANDS = 42;

    // http://niviuk.free.fr/gsm_band.php
    const nameRangeMap GSMband[] =
    {
                {"GSM 450", 259, 293}, {"GSM 480", 306, 340},
                {"GSM 750", 438, 511}, {"GSM 850", 128, 251},
                {"GSM 900 P", 1, 124}, {"GSM 900 E/R", 955, 1023},
                {"GSM DCS 1800/1900", 512, 885},

    };

    // http://niviuk.free.fr/umts_band.php
    const nameRangeMap WCDMAband[] =
    {
                {"BAND I",   10592, 10838}, {"BAND II", 9662, 9938},
                {"BAND III",  1162,  1513}, {"BAND IV", 1537, 1738},
                {"BAND V",    4357,  4458}, {"BAND VI", 4387, 4413}
    };

    // http://niviuk.free.fr/lte_band.php
    const nameRangeMap EULTRAband[] =
    {
                {"EUTRAN BAND1",      0,   599}, {"EUTRAN BAND2",    600,  1199},
                {"EUTRAN BAND3",   1200,  1949}, {"EUTRAN BAND4",   1950,  2399},
                {"EUTRAN BAND5",   2400,  2649}, {"EUTRAN BAND6",   2650,  2749},
                {"EUTRAN BAND7",   2750,  3449}, {"EUTRAN BAND8",   3450,  3799},
                {"EUTRAN BAND9",   3800,  4149}, {"EUTRAN BAND10",  4150,  4749},
                {"EUTRAN BAND11",  4750,  4999}, {"EUTRAN BAND12",  5000,  5179},
                {"EUTRAN BAND13",  5180,  5279}, {"EUTRAN BAND14",  5280,  5379},
                {"EUTRAN BAND17",  5730,  5849}, {"EUTRAN BAND18",  5850,  5999},
                {"EUTRAN BAND19",  6000,  6149}, {"EUTRAN BAND20",  6150,  6449},
                {"EUTRAN BAND21",  6450,  6525}, {"EUTRAN BAND22",  6600,  7399},
                {"EUTRAN BAND23",  7500,  7699}, {"EUTRAN BAND24",  7700,  8039},
                {"EUTRAN BAND25",  8040,  8689}, {"EUTRAN BAND26",  8690,  9039},
                {"EUTRAN BAND27",  9040,  9209}, {"EUTRAN BAND28",  9210,  9659},
                {"EUTRAN BAND29",  9660,  9769}, {"EUTRAN BAND30",  9770,  9869},
                {"EUTRAN BAND31",  9870,  9919}, {"EUTRAN BAND32",  9920, 10359},
                {"EUTRAN BAND33", 36000, 36199}, {"EUTRAN BAND34", 36200, 36349},
                {"EUTRAN BAND35", 36350, 36949}, {"EUTRAN BAND36", 36950, 37549},
                {"EUTRAN BAND37", 37550, 37749}, {"EUTRAN BAND38", 37750, 38249},
                {"EUTRAN BAND39", 38250, 38649}, {"EUTRAN BAND40", 38650, 39649},
                {"EUTRAN BAND41", 39650, 41589}, {"EUTRAN BAND42", 41590, 43589},
                {"EUTRAN BAND43", 43590, 45589}, {"EUTRAN BAND44", 45590, 46589}
    };
}

CellularRadio::CellularRadio(const std::string& sName, const std::string& sRadioPort)
: m_sName(sName)
, m_sRadioPort(sRadioPort)
, m_bEchoEnabled(false)
, m_bEnableEchoOnClose(false)
{
    m_apIo.reset(new MTS::IO::SerialConnection(
                             MTS::IO::SerialConnection::Builder(m_sRadioPort)
                                .baudRate(115200)
                                .useLockFile()
                                .build()));
}


CellularRadio::~CellularRadio() {
    shutdown();
    m_apIo.reset();
}

bool CellularRadio::initialize(uint32_t iTimeoutMillis) {
    if(!m_apIo->open(iTimeoutMillis)) {
        printError("%s| Failed to open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
        return false;
    }

    bool bEnabled;
    ICellularRadio::CODE eCode = getEcho(bEnabled);
    if(eCode == SUCCESS && bEnabled) {
        printDebug("%s| Disabling 'echo'", m_sName.c_str());
        setEcho(false);
        m_bEnableEchoOnClose = true;
    }

    return true;
}

bool CellularRadio::resetConnection(uint32_t iTimeoutMillis) {
    //Close Current Connection
    if(!m_apIo.isNull()) {
        m_apIo->close();
    }

    m_apIo.reset(new MTS::IO::SerialConnection(
                     MTS::IO::SerialConnection::Builder(m_sRadioPort)
                        .baudRate(115200)
                        .useLockFile()
                        .build()));

    //Try to obtain the device port over the given period of time
    MTS::Timer oTimer;
    oTimer.start();
    uint64_t iCurrentTime = 0;
    while(iCurrentTime < iTimeoutMillis) {

        if(!m_apIo->open(iTimeoutMillis - iCurrentTime)) {
            printWarning("%s| Failed to re-open radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
        } else {
            printInfo("%s| Successfully re-opened radio port [%s]", m_sName.c_str(), m_sRadioPort.c_str());
            printDebug("%s| Recovering 'echo' after connection reset", m_sName.c_str()); // see CellularRadio::initialize
            setEcho(m_bEchoEnabled);
            break;
        }

        ::usleep(500000); //500 millis
        iCurrentTime = oTimer.getMillis();
    }
    oTimer.stop();
    return !m_apIo->isClosed();
}

void CellularRadio::shutdown() {

    if(!m_apIo.isNull()) {
        if(m_bEnableEchoOnClose) {
            printDebug("%s| Enabling 'echo'", m_sName.c_str());
            setEcho(true);
            m_bEnableEchoOnClose = false;
        }
        m_apIo->close();
    }
}

const std::string& CellularRadio::getName() const {
    return m_sName;
}

ICellularRadio::CODE CellularRadio::getFirmware(std::string& sFirmware) {
    printTrace("%s| Get Firmware", m_sName.c_str());
    sFirmware = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+CGMR");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get firmware from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    sFirmware = MTS::Text::trim(sResult.substr(0, pos));
    if(sFirmware.size() == 0) {
        printWarning("%s| Unable to get firmware from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    m_sFirmware = sFirmware;

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getFirmwareBuild(std::string& sFirmwareBuild) {
    sFirmwareBuild = ICellularRadio::VALUE_NOT_SUPPORTED;
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getHardware(std::string& sHardware) {
    printTrace("%s| Get Hardware", m_sName.c_str());
    sHardware = ICellularRadio::VALUE_NOT_SUPPORTED;

    if(m_sFirmware.size() == 0) {
        getFirmware(m_sFirmware);
    }

    if(getHardwareVersionFromFirmware(m_sFirmware, sHardware)) {
        return SUCCESS;
    }
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getManufacturer(std::string& sManufacturer) {
    printTrace("%s| Get Manufacturer", m_sName.c_str());
    sManufacturer = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+GMI");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    sManufacturer = MTS::Text::trim(sResult.substr(0, pos));
    if(sManufacturer.size() == 0) {
        printWarning("%s| Unable to get manufacturer from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getImei(std::string& sImei) {
    printTrace("%s| Get IMEI", m_sName.c_str());
    sImei = ICellularRadio::VALUE_NOT_SUPPORTED;

    // AT+CGSN execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CGSN");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);

    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    sImei = MTS::Text::trim(sResult.substr(0, pos));
    if(sImei.size() == 0) {
        printWarning("%s| Unable to get IMEI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getMeid(std::string& sMeid) {
    printTrace("%s| Get MEID", m_sName.c_str());
    return getImei(sMeid);
}

ICellularRadio::CODE CellularRadio::getImsi(std::string& sImsi) {
    printTrace("%s| Get IMSI", m_sName.c_str());
    sImsi = ICellularRadio::VALUE_NOT_SUPPORTED;

    // AT+CIMI execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CIMI");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);

    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    sImsi = MTS::Text::trim(sResult.substr(0, pos));
    if(sImsi.size() == 0) {
        printWarning("%s| Unable to get IMSI from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getSimStatus(std::string& sSimStatus) {
    printTrace("%s| Get SIM Status", getName().c_str());
    sSimStatus = ICellularRadio::VALUE_UNKNOWN;
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getSimStatusSummary(Json::Value& jData) {
    bool bIsSimInserted = false;
    bool bIsSimLocked = true;
    int iAttemptsPin = 0;
    int iAttemptsPuk = 0;
    std::string sSimLockStatus;
    ICellularRadio::CODE retCode;

    do {
        retCode = getIsSimInserted(bIsSimInserted);
        if (retCode != SUCCESS) {
            break;
        }

        if (!bIsSimInserted) {
            // There is no left much to do. Return one field only.
            jData[KEY_IS_SIM_INSERTED] = bIsSimInserted;
            break;
        }

        // The following code assumes that the SIM card is inserted
        retCode = getSimLockStatus(sSimLockStatus);
        if (retCode != SUCCESS) {
            break;
        }

        bIsSimLocked = (sSimLockStatus != "READY");  // SIM PIN, SIM PUK or other values

        retCode = getSimLockAttempts(iAttemptsPin, iAttemptsPuk);
        if (retCode != SUCCESS) {
            break;
        }

        // Everything fetched successfully. Populate the jData object
        jData[KEY_IS_SIM_INSERTED] = bIsSimInserted;
        jData[KEY_IS_SIM_LOCKED] = bIsSimLocked;
        jData[KEY_SIM_LOCK_STATUS] = sSimLockStatus;
        jData[KEY_ATTEMPTS_PIN] = iAttemptsPin;
        jData[KEY_ATTEMPTS_PUK] = iAttemptsPuk;
    } while (false);

    return retCode;
}

ICellularRadio::CODE CellularRadio::getLac(std::string& sLac) {
    Json::Value jData;

    printTrace("%s| Get LAC", m_sName.c_str());
    sLac = ICellularRadio::VALUE_NOT_SUPPORTED;

    if(getNetworkStatus(jData) == SUCCESS) {
        if(jData.isMember(ICellularRadio::KEY_LAC)) {
            sLac = jData[ICellularRadio::KEY_LAC].asString();
            return SUCCESS;
        }
    }

    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getMdn(std::string& sMdn) {
    printTrace("%s| Get MDN", m_sName.c_str());
    sMdn = ICellularRadio::VALUE_NOT_SUPPORTED;

    // AT+CNUM execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CNUM");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);

    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get MDN from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    size_t start = sResult.find("CNUM:");
    if(start != std::string::npos) {
        start += sizeof("CNUM:");
        std::vector<std::string> vParts = MTS::Text::split(sResult.substr(start, end - start), ',');
        if(vParts.size() < 3) {
            printWarning("%s| Unable to parse MDN from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        sMdn = MTS::Text::strip(vParts[1], '"');
        if(sMdn.size() == 0) {
            printWarning("%s| Unable to get MDN from radio using command [%s].  MDN may not be set.", m_sName.c_str(), sCmd.c_str());
        }
    } else {
        sMdn = "";
        printWarning("%s| Unable to get MDN from radio using command [%s].  MDN may not be set.", m_sName.c_str(), sCmd.c_str());
    }

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getMsid(std::string& sMsid) {
    printTrace("%s| Get MSID", m_sName.c_str());
    sMsid = "";

    std::string sImsi;
    if(getImsi(sImsi) == SUCCESS) {
        if(sImsi.size() >= 10) {
            sMsid = sImsi.substr(sImsi.size() - 10);
            printTrace("IMSI: [%s] MEID [%s]", sImsi.c_str(), sMsid.c_str());
            return SUCCESS;
        }
    }
    printWarning("%s| Unable to get MSID from radio", m_sName.c_str());
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getType(std::string& sType) {
    printTrace("%s| Get Type", m_sName.c_str());
    sType = ICellularRadio::VALUE_NOT_SUPPORTED;
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getCarrier(std::string& sCarrier) {
    printTrace("%s| Get Carrier", m_sName.c_str());
    if(m_sCarrier == "") {
        Json::Value jData;
        if(getNetworkStatus(jData) == SUCCESS) {
            if(jData.isMember(ICellularRadio::KEY_MCC) && jData.isMember(ICellularRadio::KEY_MNC)) {
                std::string sMcc = jData[ICellularRadio::KEY_MCC].asString();
                std::string sMnc = jData[ICellularRadio::KEY_MNC].asString();
                Json::Value jLookup = MccMncTable::getInstance()->lookup(sMcc, sMnc);
                printTrace("%s| MCC-MNC Lookup: [%s][%s][%s]", m_sName.c_str(),
                           sMcc.c_str(), sMnc.c_str(), jLookup.toStyledString().c_str());
                if(jLookup.isMember(ICellularRadio::KEY_CARRIER)) {
                    m_sCarrier = jLookup[ICellularRadio::KEY_CARRIER].asString();
                } else {
                    printWarning("%s| MCC-MNC Lookup did not contain carrier", m_sName.c_str());
                    return FAILURE;
                }
            } else {
                printWarning("%s| Network Status did no contain MCC or MNC", m_sName.c_str());
                return FAILURE;
            }
        } else {
            return FAILURE;
        }
    }

    sCarrier = m_sCarrier;
    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getTower(std::string& sTower) {
    Json::Value jData;

    printTrace("%s| Get Tower", m_sName.c_str());
    sTower = ICellularRadio::VALUE_NOT_SUPPORTED;

    if(getNetworkStatus(jData) == SUCCESS) {
        if(jData.isMember(ICellularRadio::KEY_CID)) {
            sTower = jData[ICellularRadio::KEY_CID].asString();
            return SUCCESS;
        }
    }
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getTime(std::string& sDate, std::string& sTime, std::string& sTimeZone) {
    Json::Value jData;

    printTrace("%s| Get Time", m_sName.c_str());
    sDate = "";
    sTime = "";
    sTimeZone = "";

    std::string sCmd("AT+CCLK?");
    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    size_t start = sResult.find("CCLK: ");
    if(start != std::string::npos) {
        start += sizeof("CCLK: ");
        std::string sValue = MTS::Text::trim(sResult.substr(start, end - start));
        sValue = MTS::Text::strip(sValue, '"');

        std::vector<std::string> vParts = MTS::Text::split(sValue, ',');
        if(vParts.size() != 2) {
            printWarning("%s| Unable to parse Date from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }


        std::vector<std::string> vDateParts = MTS::Text::split(vParts[0], '/');
        if(vDateParts.size() != 3) {
            printWarning("%s| Unable to parse Date from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }

        //The Date format is YY/MM/DD -> Change to MM/DD/YY
        sDate = vDateParts[1] + "/" + vDateParts[2] + "/" + vDateParts[0];

        vParts = MTS::Text::split(vParts[1], '-');
        if(vParts.size() != 2) {
            printWarning("%s| Unable to parse Time from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }
        sTime = vParts[0];

        int32_t iZoneUnits; //the difference, expressed in quarters of an hour, between the local time and GMT
        if(!MTS::Text::parse(iZoneUnits, MTS::Text::strip(vParts[1], '+'))) {
            printWarning("%s| Unable to parse Time Zone from response [%s]", m_sName.c_str(), sResult.c_str());
            return FAILURE;
        }

        int32_t iZone = iZoneUnits/4;  //Divide by 4 to get hours difference
        int32_t iZonePartial = (iZoneUnits % 4) * 15;  //Remainder in minutes
        std::string sPlusSign = "+";
        if(iZonePartial < 0) {
            //Remove negative sign from partial and clear plus sign component
            iZonePartial *= -1;
            sPlusSign = "";
        }
        std::stringstream ss;
        ss << sPlusSign << iZone;
        if(iZonePartial != 0) {
            ss << ":" << iZonePartial;
        }

        sTimeZone = ss.str();
        return SUCCESS;

    } else {
        printWarning("%s| Unable to get Time from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
    }

    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getRoaming(bool& bRoaming) {
    Json::Value jData;

    printTrace("%s| Get Roaming", m_sName.c_str());
    bRoaming = false;

    REGISTRATION eReg;
    if(getRegistration(eReg) == SUCCESS) {
        bRoaming = (eReg == ROAMING);
        return SUCCESS;
    }
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getSignalStrength(int32_t& rssi) {
    printTrace("%s| Get Signal Strength", m_sName.c_str());

    // AT+CSQ execution can take up to 300ms according to the Quectel datasheet. Setting timeout to 500ms just for sure.
    std::string sCmd("AT+CSQ");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 500);

    if (sResult.find("+CSQ: ") == std::string::npos) {
        printDebug("%s| Signal Strength command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }

    size_t start = sResult.find(':');
    size_t stop = sResult.find(',', start);
    if(start == std::string::npos || stop == std::string::npos) {
        printDebug("%s| Signal Strength command returned malformed response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }
    std::string signal = sResult.substr(start + 2, stop - start - 2);

    sscanf(signal.c_str(), "%d", &rssi);
    printDebug("%s| Signal Strength: [%d]", m_sName.c_str(), rssi);

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getModemLocation(std::string&) {
    printTrace("%s|CellularRadio getModemLocation - not supported", m_sName.c_str());
    return FAILURE;
}

ICellularRadio::CODE CellularRadio::getEcho(bool& bEnabled) {
    printTrace("%s| Echo Test", m_sName.c_str());
    std::string sResult = sendCommand("AT");
    if(sResult.size() == 0) {
        return NO_RESPONSE;
    }

    if(sResult.find("AT") != std::string::npos) {
        bEnabled = true;
    } else {
        bEnabled = false;
    }
    m_bEchoEnabled = bEnabled;
    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::setEcho(bool bEnabled) {
    ICellularRadio::CODE eCode = FAILURE;
    if(bEnabled) {
        eCode = sendBasicCommand("ATE1");
        m_bEchoEnabled = (eCode == SUCCESS ) ? true : m_bEchoEnabled;
    } else {
        eCode = sendBasicCommand("ATE0");
        m_bEchoEnabled = (eCode == SUCCESS ) ? false : m_bEchoEnabled;
    }

    return eCode;
}

ICellularRadio::CODE CellularRadio::getStaticInformation(Json::Value& jData) {
    printTrace("%s| Get Static Information", m_sName.c_str());

    printTrace("%s| Static Information:\n%s\n", m_sName.c_str(), jData.toStyledString().c_str());

    return FAILURE;
}

// Get the LAC for the LTE radio that's not in the #RFSTS or +QENG response
std::string CellularRadio::queryLteLac() {
    std::string CGREGstring;
    std::string originalCGREG;
    std::string result;

    CGREGstring = queryCGREGstring();
    if (CGREGstring == ICellularRadio::RSP_ERROR) {
        originalCGREG = "0";
    } else {
        originalCGREG = CGREGstring.at(CGREGstring.find(",") - 1); //Position right before first comma ("+CGREG: 0,1")
    }

    // Temporarily set CGREG=2 to get more info
    setCGREG("2");

    CGREGstring = queryCGREGstring();
    if (CGREGstring == ICellularRadio::RSP_ERROR) {
        result = ICellularRadio::VALUE_UNKNOWN;
    } else {
        size_t start = CGREGstring.find(":") + 1; //Position right after "+CGREG:"
        std::vector<std::string> vParts = MTS::Text::split(MTS::Text::trim(CGREGstring.substr(start)), ",");
        if(vParts.size() < 3) {
            result = ICellularRadio::VALUE_UNAVAILABLE;
        } else {
            result = MTS::Text::strip(vParts[2], '"');
        }
    }

    setCGREG(originalCGREG);

    return result;
}

void CellularRadio::setCGREG(std::string value) {
    std::string sCmd("AT+CGREG=" + value);
    std::string cmdResult(sendCommand(sCmd));
    if (cmdResult.find("OK") == std::string::npos) {
        printDebug("%s| AT+CGREG=%s returned unexpected response: [%s][%s]", m_sName.c_str(), value.c_str(), sCmd.c_str(), cmdResult.c_str());
    }
}

std::string CellularRadio::queryCGREGstring() {
    std::string sCmd("AT+CGREG?");
    std::string cmdResult(sendCommand(sCmd));
    if (cmdResult.find("+CGREG:") == std::string::npos) {
        printDebug("%s| AT+CGREG? returned unexpected response: [%s][%s]", m_sName.c_str(), sCmd.c_str(), cmdResult.c_str());
        return ICellularRadio::RSP_ERROR;
    }
    return cmdResult;
}

void CellularRadio::getCommonNetworkStats(Json::Value& jData) {

    bool bRoaming = false;
    if(getRoaming(bRoaming) == SUCCESS) {
        jData[ICellularRadio::KEY_ROAMING] = bRoaming;
    }

    int32_t iRssi;
    if(getSignalStrength(iRssi) == SUCCESS) {
        jData[ICellularRadio::KEY_RSSI] = iRssi;
        int32_t dBm;
        if(!jData.isMember(ICellularRadio::KEY_RSSIDBM) && convertSignalStrengthTodBm(iRssi, dBm) == SUCCESS) {
            //Add RSSI in dBm format
            jData[ICellularRadio::KEY_RSSIDBM] = MTS::Text::format(dBm);
        }
    }

    std::string sService;
    if(getService(sService) == SUCCESS) {
        jData[ICellularRadio::KEY_SERVICE] = sService;
    }
    std::string sDate, sTime, sTimeZone;
    if(getTime(sDate, sTime, sTimeZone) == SUCCESS) {
        jData[ICellularRadio::KEY_DATETIME] = sDate + " " + sTime + " GMT" + sTimeZone;
    }

    std::string sNetworkReg;
    REGISTRATION eReg;
    if (getRegistration(eReg) == SUCCESS) {
        if (convertRegistrationToString(eReg, sNetworkReg) == SUCCESS) {
            jData[ICellularRadio::KEY_NETWORK_REG] = sNetworkReg;
        }
    }

    std::string sCurrentCellMode;
    CELLULAR_MODES eModes;
    if (getCellularMode(eModes) == SUCCESS) {
        if (convertCellModesToString(eModes, sCurrentCellMode) == SUCCESS) {
            jData[ICellularRadio::KEY_CELL_MODE] = sCurrentCellMode;
        }
    }
}

ICellularRadio::CODE CellularRadio::getSimLockStatus(std::string& sData)
{
    printTrace("%s| Get SIM lock status", m_sName.c_str());

    // SIM card may introduce a delay to AT+CPIN? execution. Setting timeout to 1s as set in PPP chat patches.
    std::string sCmd("AT+CPIN?");
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 1000);

    const std::string sPrefix = "+CPIN: ";
    size_t start = sResult.find(sPrefix);
    size_t end = sResult.rfind(ICellularRadio::RSP_OK);

    if (start == std::string::npos || end == std::string::npos) {
        printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    start += sPrefix.size();
    sData = MTS::Text::trim(sResult.substr(start, end-start));
    if(sData.size() == 0) {
        printWarning("%s| Unable to get SIM lock status from radio using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

void CellularRadio::initMipProfile(Json::Value& jData) {
    jData[ICellularRadio::KEY_MIP_ID] = 0;
    jData[ICellularRadio::KEY_MIP_ENABLED] = false;
    jData[ICellularRadio::KEY_MIP_NAI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_HOMEADDRESS] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_PRIMARYHA] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_SECONDARYHA] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNAAASPI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNHASPI] = ICellularRadio::VALUE_UNKNOWN;
    jData[ICellularRadio::KEY_MIP_MNAAASS] = false;
    jData[ICellularRadio::KEY_MIP_MNHASS] = false;
}

const std::vector<std::string> CellularRadio::getRegistrationCommands() {
    std::string sType;
    convertModelToType(getName(), sType);

    if (sType == VALUE_TYPE_LTE) {
        return { "CREG", "CGREG", "CEREG" };
    } else {
        return { "CREG", "CGREG" };
    }
}

ICellularRadio::REGISTRATION CellularRadio::parseRegResponse(std::string sResult) {
    size_t start = sResult.find(',');
    size_t stop = sResult.find(' ', start);
    std::string sRegStat = sResult.substr(start + 1, stop - start - 1);
    int32_t value;
    sscanf(sRegStat.c_str(), "%d", &value);

    return (ICellularRadio::REGISTRATION)value;
}

ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration, const std::string& sType) {
    std::string sCmd = "AT+" + sType + "?";
    std::string sResp = "+" + sType + ": ";
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000);
    if (sResult.find(sResp) == std::string::npos) {
        if(sResult.size() == 0) {
            printDebug("%s| Registration command returned no response", m_sName.c_str());
            return NO_RESPONSE;
        }
        printDebug("%s| Registration command returned unexpected response: [%s]", m_sName.c_str(), sResult.c_str());
        return FAILURE;
    }

    eRegistration = parseRegResponse(sResult);
    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::getRegistration(REGISTRATION& eRegistration) {
    /* REGISTRATION_PRIORITY:
     *   REGISTERED     = 0
     *   ROAMING        = 1
     *   DENIED         = 2
     *   SEARCHING      = 3
     *   NOT_REGISTERED = 4
     *   UNKNOWN        = 5
     */
    uint8_t uRegPriority[] = { 4, 0, 3, 2, 5, 1 };
    ICellularRadio::CODE ret = ERROR;
    REGISTRATION eReg = UNKNOWN;
    eRegistration = UNKNOWN;

    // We need to check CREG, CGREG, and CEREG for possible success.
    // Depending on the radio, carrier, roaming, sim card, cellular mode and some other factors the registration
    // will come back differently depending on the type of connection that is possible.

    const auto & commands = getRegistrationCommands();
    for (const auto & cmd : commands) {
        ret = getRegistration(eReg, cmd);
        if (ret != SUCCESS) {
            break;
        }

        if (eReg == REGISTERED || eReg == ROAMING) {
            eRegistration = eReg;
            break;
        }
        eRegistration = (uRegPriority[eRegistration] > uRegPriority[eReg]) ? eReg : eRegistration;
    }

    return ret;
}

ICellularRadio::CODE CellularRadio::getCellularMode(CELLULAR_MODES &networks) {
    networks = CELLULAR_MODE_NA;
    std::string cmdResult = sendCommand("AT+COPS?");
    if (cmdResult.find(ICellularRadio::RSP_OK) == std::string::npos) {
        printError("%s| AT+COPS returned unexpected response: AT+COPS? [%s]", getName().c_str(), cmdResult.c_str());
        return FAILURE;
    }

    size_t cursor = 0;
    const std::vector<std::string> &reply = MTS::Text::split(MTS::Text::getLine(MTS::Text::trim(cmdResult), cursor, cursor), ',');
    uint8_t op;
    if (reply.size() < 4 || !MTS::Text::parse(op, reply[3])) {
        printError("%s| AT+COPS Error parsing reply [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str());
        return FAILURE;
    }
    if (op == 0) {
        networks = CELLULAR_MODE_2G;
    } else if (op >= 2 && op <= 6) {
        networks = CELLULAR_MODE_3G;
    } else if (op == 7) {
        networks = CELLULAR_MODE_4G;
    } else {
        printError("%s| AT+COPS unknown Radio Access Technology [AT+COPS?][%s]", getName().c_str(), cmdResult.c_str());
        return FAILURE;
    }
    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::convertRegistrationToString(REGISTRATION eRegistration, std::string& sRegistration) {

    ICellularRadio::CODE eCode = FAILURE;
    switch (eRegistration) {
        case NOT_REGISTERED: sRegistration = ICellularRadio::VALUE_NOT_REGISTERED; eCode = SUCCESS; break;
        case REGISTERED: sRegistration = ICellularRadio::VALUE_REGISTERED; eCode = SUCCESS; break;
        case SEARCHING: sRegistration = ICellularRadio::VALUE_SEARCHING; eCode = SUCCESS; break;
        case DENIED: sRegistration = ICellularRadio::VALUE_DENIED; eCode = SUCCESS; break;
        case UNKNOWN: sRegistration = ICellularRadio::VALUE_UNKNOWN; eCode = SUCCESS; break;
        case ROAMING: sRegistration = ICellularRadio::VALUE_ROAMING; eCode = SUCCESS; break;
    }
    return eCode;
}

ICellularRadio::CODE CellularRadio::convertCellModesToString(ICellularRadio::CELLULAR_MODES eCellModes, std::string& sCellModes) {
    std::string sResult;

    if (eCellModes & CELLULAR_MODE_2G) {
        sResult += "2g,";
    }
    if (eCellModes & CELLULAR_MODE_3G) {
        sResult += "3g,";
    }
    if (eCellModes & CELLULAR_MODE_4G) {
        sResult += "4g,";
    }
    if (eCellModes & CELLULAR_MODE_5G) {
        sResult += "5g,";
    }

    if (!sResult.empty()) {
        sResult.pop_back();  // remove trailing comma
    }

    sCellModes = sResult;
    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::unlockSimCard(const Json::Value& jArgs) {
    printTrace("%s| Unlock the SIM card using PIN code", m_sName.c_str());

    if(!jArgs["pin"].isString()) {
        return INVALID_ARGS;
    }

    std::string sCmd = "AT+CPIN=" + jArgs["pin"].asString();
    std::string sResult = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, 5000);

    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Failed to unlock the SIM card using command [%s]", m_sName.c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

ICellularRadio::CODE CellularRadio::validateMsl(const Json::Value&) {
    printTrace("%s| Validate MSL", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMsid(const Json::Value&) {
    printTrace("%s| Set MSID", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::getMipProfile(Json::Value&) {
    printTrace("%s| Get MIP Active Profile", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipActiveProfile(const Json::Value&) {
    printTrace("%s| Set MIP Active Profile", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipNai(const Json::Value&) {
    printTrace("%s| Set MIP NAI", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipHomeIp(const Json::Value&) {
    printTrace("%s| Set MIP Home IP", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipPrimaryHa(const Json::Value&) {
    printTrace("%s| Set MIP Primary HA", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipSecondaryHa(const Json::Value&) {
    printTrace("%s| Set MIP Secondary HA", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipMnAaaSpi(const Json::Value&) {
    printTrace("%s| Set MIP MN-AAA SPI", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipMnHaSpi(const Json::Value&) {
    printTrace("%s| Set MIP MN-HA SPI", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipRevTun(const Json::Value&) {
    printTrace("%s| Set MIP Rev Tun", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipMnAaaSs(const Json::Value&) {
    printTrace("%s| Set MIP MN-AAA SS", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setMipMnHaSs(const Json::Value&) {
    printTrace("%s| Set MIP MN-HA SS", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::updateDc(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Device Configuration", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::updatePrl(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Preferred Roaming List", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::updateFumo(const Json::Value&, UpdateCb&) {
    printTrace("%s| Update Firmware Update Management Object", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::resetHfa(const Json::Value&, UpdateCb&) {
    printTrace("%s| HFA Reset", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::activate(const Json::Value&, UpdateCb&) {
    printTrace("%s| Activation", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::setActiveFirmware(const Json::Value&) {
    printTrace("%s| Set Active Firmware Image Number: not applicable", m_sName.c_str());

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::getActiveFirmware(std::string& sFwId) {
    printTrace("%s| Get Active Firmware Image Number: not applicable", m_sName.c_str());
    sFwId = ICellularRadio::VALUE_NOT_SUPPORTED;

    return NOT_APPLICABLE;
}

ICellularRadio::CODE CellularRadio::sendBasicCommand(const std::string& sCmd, int32_t iTimeoutMillis, const char& ESC) {
    std::string response = sendCommand(sCmd, DEFAULT_BAIL_STRINGS, iTimeoutMillis, ESC);
    if (response.size() == 0) {
        return NO_RESPONSE;
    } else if (response.find(ICellularRadio::RSP_OK) != std::string::npos) {
        return SUCCESS;
    } else if (response.find(ICellularRadio::RSP_ERROR) != std::string::npos) {
        return ERROR;
    } else {
        return FAILURE;
    }
}

std::string CellularRadio::sendCommand(const std::string& sCmd, const std::vector<std::string>& vBail, int32_t timeoutMillis, const char& ESC) {
    return ICellularRadio::sendCommand(m_apIo, sCmd, vBail, timeoutMillis, ESC);
}

std::string CellularRadio::sendCommand(const std::string& sCmd, MTS::IO::CellularRadio::IsNeedMoreData& isNeedMoreData, int32_t timeoutMillis, const char& ESC) {
    return ICellularRadio::sendCommand(m_apIo, sCmd, isNeedMoreData, timeoutMillis, ESC);
}

bool CellularRadio::splitAndAssign(const std::string& sLine, const std::string& sKey, Json::Value& jParent, const std::string& sJsonKey,  Json::ValueType eType) {

    std::vector<std::string> vParts = MTS::Text::split(sLine, ":", 2);

    if(vParts.size() == 2 && vParts[0] == sKey) {
        if(eType == Json::ValueType::stringValue) {
            jParent[sJsonKey] = MTS::Text::trim(vParts[1]);
        } else if (eType == Json::ValueType::intValue || eType == Json::ValueType::uintValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else if(eType == Json::ValueType::realValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else if(eType == Json::ValueType::booleanValue) {
            //TODO:
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        } else {
            printWarning("%s| Unable to parse requested type from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
            return false;
        }
    } else {
        printWarning("%s| Unable to parse %s from line [%s]", getName().c_str(), sKey.c_str(), sLine.c_str());
        return false;
    }

    return true;
}

bool CellularRadio::getCarrierFromFirmware(const std::string& sFirmware, std::string& sCarrier) {
    // Assuming that this function is not supported by the modem until overriden.

    return false;
}

bool CellularRadio::getHardwareVersionFromFirmware(const std::string& sFirmware, std::string& sHardware) {
    // Assuming that this function is not supported by the modem until overriden.

    return false;
}

const char *CellularRadio::RadioBandMap::getLTEBand(const int32_t channel)
{
    for (unsigned int ii = 0; ii < NUM_LTE_BANDS; ii++)
    {
        if (EULTRAband[ii].low <= channel && EULTRAband[ii].high >= channel)
        {
            return EULTRAband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}

const char *CellularRadio::RadioBandMap::getCDMABand(const int channel)
{
    for (unsigned int ii = 0; ii < NUM_WCDMA_BANDS; ii++)
    {
        if (WCDMAband[ii].low <= channel && WCDMAband[ii].high >= channel)
        {
            return WCDMAband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}

const char *CellularRadio::RadioBandMap::getGSMBand(const int channel)
{
    for (unsigned int ii = 0; ii < NUM_GSM_BANDS; ii++)
    {
        if (GSMband[ii].low <= channel && GSMband[ii].high >= channel)
        {
            return GSMband[ii].name;
        }
    }
    return ICellularRadio::VALUE_UNKNOWN;
}

const char *CellularRadio::RadioBandMap::getRadioBandName()
{
    const char *band = ICellularRadio::VALUE_UNKNOWN;

    if (m_sRadioType == ICellularRadio::VALUE_TYPE_LTE)
    {
        band = getLTEBand(m_iChannel);
    }
    else if (m_sRadioType == ICellularRadio::VALUE_TYPE_CDMA)
    {
        band = getCDMABand(m_iChannel);
    }
    else if (m_sRadioType == ICellularRadio::VALUE_TYPE_GSM)
    {
        band = getGSMBand(m_iChannel);
    }

    return band;
}

const char *CellularRadio::RadioBandMap::getRadioBandName(const std::string &channel, const std::string &radioType)
{
    const char *band = ICellularRadio::VALUE_UNKNOWN;
    int32_t chan = strtol(channel.c_str(), NULL, 10);
    if (radioType == ICellularRadio::VALUE_TYPE_LTE)
    {
        band = getLTEBand(chan);
    }
    else if (radioType == ICellularRadio::VALUE_TYPE_CDMA)
    {
        band = getCDMABand(chan);
    }
    else if (radioType == ICellularRadio::VALUE_TYPE_GSM)
    {
        band = getGSMBand(chan);
    }

    return band;
}