/*
 * Copyright (C) 2015 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/>.
 *
 */

/*! 
 \file MTS_IO_CdmaRadio.cpp
 \brief A brief description 
 \date Nov 19, 2014
 \author sgodinez

 A more elaborate description
*/

#include <mts/MTS_IO_CdmaRadio.h>
#include <mts/MTS_Text.h>
#include <mts/MTS_Logger.h>
#include <mts/MTS_Thread.h>

using namespace MTS::IO;

CdmaRadio::CdmaRadio(const std::string& sName, const std::string& sPort)
: TelitRadio(sName, sPort)
{

}

CdmaRadio::~CdmaRadio() {

}

ICellularRadio::CODE CdmaRadio::getImei(std::string& sImei) {
    printTrace("%s| Get IMEI", getName().c_str());
    return getMeid(sImei);
}

ICellularRadio::CODE CdmaRadio::getMeid(std::string& sMeid) {
    printTrace("%s| Get MEID", getName().c_str());
    sMeid = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT#MEIDESN?");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get MEID from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    size_t start = sResult.find("#MEIDESN:") + sizeof("#MEIDESN:");
    size_t stop = sResult.find(",");

    if(stop != std::string::npos && stop > start) {
        sMeid = sResult.substr(start, stop - start);
    } else {
        return FAILURE;
    }

    return SUCCESS;
}

ICellularRadio::CODE CdmaRadio::getMsid(std::string& sMsid) {
    printTrace("%s| Get MSID", getName().c_str());
    sMsid = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT$MSID?");
    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get MSID from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    size_t start = sResult.find("$MSID:") + sizeof("$MSID:");
    sMsid =  MTS::Text::trim(sResult.substr(start, end-start));
    if(sMsid.size() == 0) {
        printWarning("%s| MSID is empty", getName().c_str());
    }

    return SUCCESS;
}

ICellularRadio::CODE CdmaRadio::getCarrier(std::string& sCarrier) {
    if(m_sCarrier != "") {
        sCarrier = m_sCarrier;
        return SUCCESS;
    }

    std::string sFirmware;
    ICellularRadio::CODE code = getFirmware(sFirmware);
    if(code != SUCCESS) {
        return code;
    }

    if(!getCarrierFromFirmware(sFirmware, sCarrier)) {
        return FAILURE;
    }

    m_sCarrier = sCarrier;

    return SUCCESS;
}

ICellularRadio::CODE CdmaRadio::getNetwork(std::string& sNetwork) {
    return getCarrier(sNetwork);
}

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

ICellularRadio::CODE CdmaRadio::getIccid(std::string& sIccid) {
    printTrace("%s| Get ICCID", getName().c_str());
    sIccid = ICellularRadio::VALUE_NOT_SUPPORTED;
    return NOT_APPLICABLE;
}

ICellularRadio::CODE CdmaRadio::getLac(std::string& sLac) {
    printTrace("%s| Get LAC", getName().c_str());
    sLac = ICellularRadio::VALUE_NOT_SUPPORTED;
    return NOT_APPLICABLE;
}

ICellularRadio::CODE CdmaRadio::getService(std::string& sService) {
    printTrace("%s| Get Service", getName().c_str());
    sService = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+SERVICE?");
    std::string sResult = 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("+SERVICE:");
    if(start != std::string::npos) {
        start += sizeof("+SERVICE:");
        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 = "No Service"; break;
            case 1: sService = "1xRTT"; break;
            case 2: sService = "EVDO"; break; //Release 0
            case 3: sService = "EVDO"; break; //Release A
            case 4: sService = "GPRS"; break;
            default: sService = ICellularRadio::VALUE_UNKNOWN; break;
        }

        printDebug("%s| Service ID: [%d][%s]", getName().c_str(), iService, sService.c_str());
    }
    return SUCCESS;
}

ICellularRadio::CODE CdmaRadio::getHardware(std::string& sHardware) {
    printTrace("%s| Get Hardware", getName().c_str());
    sHardware = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT#HWREV");
    std::string sResult = sendCommand(sCmd);
    size_t pos = sResult.find(ICellularRadio::RSP_OK);
    if (pos == std::string::npos) {
        printWarning("%s| Unable to get hardware from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

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

    return SUCCESS;
}

ICellularRadio::CODE CdmaRadio::getMdn(std::string& sMdn) {
    printTrace("%s| Get MDN", getName().c_str());
    sMdn = ICellularRadio::VALUE_NOT_SUPPORTED;
    std::string sCmd("AT+CNUM");
    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to get MDN from radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    size_t start = sResult.find("CNUM:");
    std::vector<std::string> vParts = MTS::Text::split(sResult.substr(start + sizeof("CNUM:"), end), ",");
    if(vParts.size() == 3) {
        sMdn = vParts[1];
        if(sMdn.size() == 0) {
            printWarning("%s| MDN is empty.  Device may need to be activated", getName().c_str(), sCmd.c_str());
        }
    } else {
        printWarning("%s| Unable to parse MDN from radio using command [%s][%s]", getName().c_str(), sCmd.c_str(), sResult.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier != "Aeris" && sCarrier != "Sprint") {
        return NOT_APPLICABLE;
    }

    std::string sMdn;
    if(getMdn(sMdn) != SUCCESS) {
        return FAILURE;
    }

    Json::Value jMdn(Json::objectValue);
    jMdn["mdn"] = sMdn;
    jMdn["msl"] = jArgs["msl"];

    return setMdn(jMdn);
}

ICellularRadio::CODE CdmaRadio::setMdn(const Json::Value& jArgs) {
    printTrace("%s| Set MDN", getName().c_str());

    if(!jArgs["mdn"].isString()) {
        printError("%s| Arguments missing \"mdn\"", getName().c_str());
        return INVALID_ARGS;
    }

    std::string sCarrier;
    getCarrier(sCarrier);

    std::string sCmd("AT$MDN=");
    if(sCarrier == "Sprint") {

        if(!jArgs["msl"].isString()) {
            printError("%s| Arguments missing \"msl\"", getName().c_str());
            return INVALID_ARGS;
        }
        sCmd += jArgs["msl"].asString() + ",";
    } else if(sCarrier == "Aeris") {
        //AT$MDN=<Last Six Digits of HEX MEID>,<MDN>
        sCmd += getMeidLastSix() + ",";
    }

    //Strip spaces and hyphens
    std::string sMdn = jArgs["mdn"].asString();
    sMdn = MTS::Text::strip(sMdn, '-');
    sMdn = MTS::Text::strip(sMdn, '(');
    sMdn = MTS::Text::strip(sMdn, ')');
    sMdn = MTS::Text::strip(sMdn, ' ');


    sCmd += sMdn;

    std::string sResult = sendCommand(sCmd);
    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;
}

ICellularRadio::CODE CdmaRadio::setMsid(const Json::Value& jArgs) {
    printTrace("%s| Set MSID", getName().c_str());
    if(!jArgs["msid"].isString()) {
        printError("%s| Arguments missing \"msid\"", getName().c_str());
        return INVALID_ARGS;
    }

    std::string sCarrier;
    getCarrier(sCarrier);

    std::string sCmd("AT$MSID=");
    if(sCarrier == "Sprint") {
        if(!jArgs["msl"].isString()) {
            printError("%s| Arguments missing \"msl\"", getName().c_str());
            return INVALID_ARGS;
        }
        sCmd += jArgs["msl"].asString() + ",";
    } else if(sCarrier == "Aeris") {
        //AT$MSID=<Last Six Digits of HEX MEID>,<MSID>
        sCmd += getMeidLastSix() + ",";
    }


    sCmd += jArgs["msid"].asString();

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MSID for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

    initMipProfile(jMipProfile);

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier == "Aeris" || sCarrier == "Sprint") {
        std::string sCmd("AT$QCMIPGETP");
        std::string sResult = MTS::Text::trim(sendCommand(sCmd));
        if (sResult.find(ICellularRadio::RSP_OK) == std::string::npos) {
            printWarning("%s| Unable to get active MIP profile for radio using command [%s]", getName().c_str(), sCmd.c_str());
            return FAILURE;
        }

        std::vector<std::string> vLine = MTS::Text::split(sResult, "\r\n");

        //ACTIVE MIP PROFILE
        std::string sLine = vLine[0];
        std::vector<std::string> vParts = MTS::Text::split(sLine, ':', 2);
        if(vParts.size() == 2 && vParts[0] == "Profile") {
            if(vParts[1].find("Enabled") != std::string::npos) {
                jMipProfile[ICellularRadio::KEY_MIP_ENABLED] = true;
            } else {
                jMipProfile[ICellularRadio::KEY_MIP_ENABLED] = false;
            }
            int32_t id;
            sscanf(vParts[1].c_str(), "%d", &id);
            jMipProfile[ICellularRadio::KEY_MIP_ID] = id;

        } else {
            printWarning("%s| Unable to parse active MIP profile from line [%s]", getName().c_str(), sLine.c_str());
        }


        splitAndAssign(vLine[1], "NAI", jMipProfile, ICellularRadio::KEY_MIP_NAI);
        splitAndAssign(vLine[2], "Home Addr", jMipProfile, ICellularRadio::KEY_MIP_HOMEADDRESS);
        splitAndAssign(vLine[3], "Primary HA", jMipProfile, ICellularRadio::KEY_MIP_PRIMARYHA);
        splitAndAssign(vLine[4], "Secondary HA", jMipProfile, ICellularRadio::KEY_MIP_SECONDARYHA);
        splitAndAssign(vLine[5], "MN-AAA SPI", jMipProfile, ICellularRadio::KEY_MIP_MNAAASPI);
        splitAndAssign(vLine[6], "MN-HA SPI", jMipProfile, ICellularRadio::KEY_MIP_MNHASPI);

        //Reverse Tunneling
        sLine = vLine[7];
        vParts = MTS::Text::split(sLine, ':', 2);
        if(vParts.size() == 2 && vParts[0] == "Rev Tun") {
            if(vParts[1] == "1") {
                jMipProfile[ICellularRadio::KEY_MIP_REVTUN] = true;
            } else {
                jMipProfile[ICellularRadio::KEY_MIP_REVTUN] = false;
            }
        } else {
            printWarning("%s| Unable to parse Reverse Tunneling from line [%s]", getName().c_str(), sLine.c_str());
        }

        //MN-AAA SS
        sLine = vLine[8];
        vParts = MTS::Text::split(sLine, ':', 2);
        if(vParts.size() == 2 && vParts[0] == "MN-AAA SS") {
            if(vParts[1] == "Set") {
                jMipProfile[ICellularRadio::KEY_MIP_MNAAASS] = true;
            } else {
                jMipProfile[ICellularRadio::KEY_MIP_MNAAASS] = false;
            }
        } else {
            printWarning("%s| Unable to parse MN-AAA SS from line [%s]", getName().c_str(), sLine.c_str());
        }

        //MN-HA SS
        sLine = vLine[9];
        vParts = MTS::Text::split(sLine, ':', 2);
        if(vParts.size() == 2 && vParts[0] == "MN-HA SS") {
            if(vParts[1] == "Set") {
                jMipProfile[ICellularRadio::KEY_MIP_MNHASS] = true;
            } else {
                jMipProfile[ICellularRadio::KEY_MIP_MNHASS] = false;
            }
        } else {
            printWarning("%s| Unable to parse MN-HA SS from line [%s]", getName().c_str(), sLine.c_str());
        }
    }


    return SUCCESS;
}


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

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

    std::string sCmd("AT$QCMIPP=");
    sCmd += jArgs["activeProfile"].asString();

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set Active profile for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPNAI=");
    sCmd += jArgs["nai"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set NAI for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPHA=");
    sCmd += jArgs["homeIp"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set Home IP profile for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPPHA=");
    sCmd += jArgs["primaryHa"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set Primary HA for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPSHA=");
    sCmd += jArgs["secondaryHa"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set Secondary HA for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPMASPI=");
    sCmd += jArgs["mnAaaSpi"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MN-AAA SPI for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPMHSPI=");
    sCmd += jArgs["mnHaSpi"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MN-HA SPI for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPRT=");
    sCmd += jArgs["revTun"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set Rev Tun for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPMASS=");
    sCmd += jArgs["mnAaaSs"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MN-AAA SS for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

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

    std::string sCmd("AT$QCMIPMHSS=");
    sCmd += jArgs["mnHaSs"].asString() + ",1";

    std::string sResult = sendCommand(sCmd);
    size_t end = sResult.find(ICellularRadio::RSP_OK);
    if (end == std::string::npos) {
        printWarning("%s| Unable to set MN-HA SS for radio using command [%s]", getName().c_str(), sCmd.c_str());
        return FAILURE;
    }

    return SUCCESS;
}

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

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier != "Sprint") {
        return NOT_APPLICABLE;
    }

    ICellularRadio::CODE result = FAILURE;
    std::size_t pos = 0;
    std::size_t end = 0;

    IsNeedMoreData isNeedMoreData = [&stepCb, &result, &pos, &end,  this](const std::string& iterationData, const std::string& allData)->bool {

        if(iterationData.empty()) {
            //No new data
            return true;
        }

        end = allData.find(ICellularRadio::NL, pos);
        printTrace("%s| Update DC Callback Started", this->getName().c_str());

        while(end != std::string::npos) {
            size_t next;
            std::string sLine = MTS::Text::getLine(allData, pos, next);

            printTrace("%s| Line: [%s]", this->getName().c_str(), sLine.c_str());

            if(sLine == "#906") {
                // DC DM session started
                if(stepCb) {
                    stepCb(Json::Value("DC Info: DM session started"));
                }
            } else if(sLine == "#918") {
                // DC Done, success
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("DC Done: success"));
                }
                return false;
            } else if(sLine == "#924") {
                // DC Done, no profile
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("DC Done: no profile"));
                }
                return false;
            } else if(sLine == "#911") {
                // DC Error: credential error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("DC Error: credential error"));
                }
                return false;
            } else if(sLine == "#912") {
                // DC Error: unreachable server
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("DC Error: unreachable server"));
                }
                return false;
            } else if(sLine == "#913") {
                // DC Error: network error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("DC Error: network error"));
                }
                return false;
            } else if(sLine == "#915") {
                // DC Error: update fails with other reasons
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("DC Error: update fails with other reasons"));
                }
                return false;
            }else if(sLine == ICellularRadio::RSP_ERROR) {
                result = ERROR;
                return false;
            }

            //Set cursors for next iteration
            if(next != std::string::npos) {
                pos = next;
            }
            end = allData.find(ICellularRadio::NL, pos);
        }

        printTrace("%s| Update DC Callback Finished", this->getName().c_str());
        return true;
    };

    sendCommand("AT+OMADM=2", isNeedMoreData, 5 * 60000);
    return result;
}

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

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier != "Sprint") {
        return NOT_APPLICABLE;
    }

    ICellularRadio::CODE result = FAILURE;
    std::size_t pos = 0;
    std::size_t end = 0;

    IsNeedMoreData isNeedMoreData = [&stepCb, &result, &pos, &end,  this](const std::string& iterationData, const std::string& allData)->bool {

        if(iterationData.empty()) {
            //No new data
            return true;
        }

        end = allData.find(ICellularRadio::NL, pos);
        printTrace("%s| Update PRL Callback Started", this->getName().c_str());

        while(end != std::string::npos) {
            size_t next;
            std::string sLine = MTS::Text::getLine(allData, pos, next);

            printTrace("%s| Line: [%s]", this->getName().c_str(), sLine.c_str());

            if(sLine == "#905") {
                // PRL DM session started
                if(stepCb) {
                    stepCb(Json::Value("PRL Info: DM session started"));
                }
            } else if(sLine == "#909") {
                // PRL Done, success
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("PRL Done: success"));
                }
                return false;
            } else if(sLine == "#910") {
                // PRL Done, no PRL update
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("PRL Done: no PRL update"));
                }
                return false;
            } else if(sLine == "#911") {
                // PRL Error: credential error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("PRL Error: credential error"));
                }
                return false;
            } else if(sLine == "#912") {
                // PRL Error: unreachable server
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("PRL Error: unreachable server"));
                }
                return false;
            } else if(sLine == "#913") {
                // PRL Error: network error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("PRL Error: network error"));
                }
                return false;
            } else if(sLine == "#915") {
                // PRL Error: update fails with other reasons
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("PRL Error: update fails with other reasons"));
                }
                return false;
            } else if(sLine == ICellularRadio::RSP_ERROR) {
                result = ERROR;
                return false;
            }

            //Set cursors for next iteration
            if(next != std::string::npos) {
                pos = next;
            }
            end = allData.find(ICellularRadio::NL, pos);
        }

        printTrace("%s| Update PRL Callback Finished", this->getName().c_str());
        return true;
    };

    sendCommand("AT+PRL=2", isNeedMoreData, 5 * 60000);
    return result;
}

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

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier != "Sprint") {
        return NOT_APPLICABLE;
    }

    ICellularRadio::CODE result = FAILURE;
    std::size_t pos = 0;
    std::size_t end = 0;

    IsNeedMoreData isNeedMoreData = [&stepCb, &result, &pos, &end,  this](const std::string& iterationData, const std::string& allData)->bool {

        if(iterationData.empty()) {
            //No new data
            return true;
        }

        end = allData.find(ICellularRadio::NL, pos);
        printTrace("%s| Update FUMO Callback Started", this->getName().c_str());

        while(end != std::string::npos) {
            size_t next;
            std::string sLine = MTS::Text::getLine(allData, pos, next);

            printTrace("%s| Line: [%s]", this->getName().c_str(), sLine.c_str());

            if(sLine == "#907") {
                // FUMO DM session started
                if(stepCb) {
                    stepCb(Json::Value("FUMO Info: DM session started"));
                }
            } else if(sLine == "#916") {
                // FUMO Done, no firmware update
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Done: no firmware update"));
                }
                return false;
            } else if(sLine == "#911") {
                // FUMO Error: credential error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: credential error"));
                }
                return false;
            } else if(sLine == "#912") {
                // FUMO Error: unreachable server
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: unreachable server"));
                }
                return false;
            } else if(sLine == "#913") {
                // FUMO Error: network error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: network error"));
                }
                return false;
            } else if(sLine == "#915") {
                // FUMO Error: update fails with other reasons
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: update fails with other reasons"));
                }
                return false;
            } else if(sLine == "#919") {
                // FUMO Firmware downloaded successfully
                if(stepCb) {
                    stepCb(Json::Value("FUMO Info: firmware downloaded successfully"));
                }
            } else if(sLine == "#920:") {
                // FUMO Firmware download progress (percent)
                if(stepCb) {
                    stepCb(Json::Value(std::string("FUMO Info: firmware download progress ") + allData.substr(pos + 5, end + 5 - pos)));
                }
            } else if(sLine == "#921:") {
                // FUMO Firmware size get from the OMA-DM server (byte)
                if(stepCb) {
                    stepCb(Json::Value(std::string("FUMO Info: firmware size get from the OMA-DM server ") + allData.substr(pos + 5, end + 5 - pos) + " byte"));
                }
            } else if(sLine == "#921") { /// Info: check after check "#921:"
                // FUMO Firmware download start
                if(stepCb) {
                    stepCb(Json::Value("FUMO Info: firmware downloaded successfully"));
                }
            } else if(sLine == "#929: 200") {
                // FUMO Done, success
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Done: update success"));
                }
                return false;
            } else if(sLine == "#929: 402") {
                // FUMO Error, firmware corrupted, CRC error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: firmware corrupted, CRC error"));
                }
                return false;
            } else if(sLine == "#929: 403") {
                // FUMO Error, firmware package mismatch
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: firmware package mismatch"));
                }
                return false;
            } else if(sLine == "#929: 404") {
                // FUMO Error, firmware signature failed
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: firmware signature failed"));
                }
                return false;
            } else if(sLine == "#929: 406") {
                // FUMO Error, firmware update authentication failed
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: firmware update authentication failed"));
                }
                return false;
            } else if(sLine == "#929: 410") {
                // FUMO Error, firmware update general error
                result = ERROR;
                if(stepCb) {
                    stepCb(Json::Value("FUMO Error: firmware update general error"));
                }
                return false;
            } else if(sLine == "#930") {
                // FUMO Reporting of Firmware Update result to server
                if(stepCb) {
                    stepCb(Json::Value("FUMO Info: reporting of firmware update result to server"));
                }
            } else if(sLine == ICellularRadio::RSP_ERROR) {
                result = ERROR;
                return false;
            }

            //Set cursors for next iteration
            if(next != std::string::npos) {
                pos = next;
            }
            end = allData.find(ICellularRadio::NL, pos);
        }

        printTrace("%s| Update FUMO Callback Finished", this->getName().c_str());
        return true;
    };

    sendCommand("AT+FUMO=2", isNeedMoreData, 30 * 60000);
    return result;
}

ICellularRadio::CODE CdmaRadio::resetHfa(const Json::Value& jArgs, UpdateCb& stepCb) {
    printTrace("%s| HFA Reset (after device reboot HFA will occur)", getName().c_str());

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier != "Sprint") {
        return NOT_APPLICABLE;
    }

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

    ICellularRadio::CODE result = FAILURE;
    std::size_t pos = 0;
    std::size_t end = 0;

    IsNeedMoreData isNeedMoreData = [&stepCb, &result, &pos, &end,  this](const std::string& iterationData, const std::string& allData)->bool {
        /* Expected Events:
        *  #900 - DM Client ready
        *  #904 - HFA Started
        *  #914 - HFA Done - HFA Success
        *  #905 - PRL - Session started
        *  #909 - PRL - Done - PRL success
        *  #907 - FUMO - Firmware DM session started or started again until no more updates are available
        *  #916 - FUMO - Firmware Done, No firmware update
        */


        if(iterationData.empty()) {
            //No new data
            return true;
        }

        end = allData.find(ICellularRadio::NL, pos);
        printTrace("%s| HFA Reset Callback Started", this->getName().c_str());

        while(end != std::string::npos) {
            size_t next;
            std::string sLine = MTS::Text::getLine(allData, pos, next);

            printTrace("%s| Line: [%s]", this->getName().c_str(), sLine.c_str());

            if(sLine == "#900") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: DM Client Ready"));
                }
            } else if(sLine == "#904") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: HFA - Session Started"));
                }
            } else if(sLine == "#914") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: HFA - Session Completed Successfully"));
                }
            } else if(sLine == "#905") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: PRL - Session Started"));
                }
            } else if(sLine == "#909") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: PRL - Session Completed Successfully"));
                }
            } else if(sLine == "#907") {
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: Firmware DM - Session Started"));
                }
            } else if(sLine == "#916") {
                // RTN Done, success
                result = SUCCESS;
                if(stepCb) {
                    stepCb(Json::Value("RTN Done: Firmware DM - Session Completed Successfully"));
                }
                return false;
            } else if(sLine == ICellularRadio::RSP_ERROR) {
                result = ERROR;
                return false;
            } else if(sLine == ICellularRadio::RSP_OK) {
                //The Device will reboot now, so reset the connection
                if(stepCb) {
                    stepCb(Json::Value("RTN Info: Resetting Radio"));
                }

                //TODO: Investigate a better way of to handle
                //      recapturing the radio port after reset

                //Sleep for a few seconds while resetting
                MTS::Thread::sleep(5000);

                //Try to reconnect to radio
                if(!this->resetConnection(10000)) {
                    printError("%s| Unable to obtain radio after reset", this->getName().c_str());
                    if(stepCb) {
                        stepCb(Json::Value("RTN Error: Unable to obtain radio after reset"));
                    }
                    result = ERROR;
                    return false;
                }
            }

            //Set cursors for next iteration
            if(next != std::string::npos) {
                pos = next;
            }
            end = allData.find(ICellularRadio::NL, pos);
        }

        printTrace("%s| HFA Reset Callback Finished", this->getName().c_str());
        return true;
    };

    std::string sCmd("AT#SPRTN=\"");
    sCmd += jArgs["msl"].asString() + "\"";
    sendCommand(sCmd, isNeedMoreData, 5 * 60000);
    return result;
}

ICellularRadio::CODE CdmaRadio::activate(const Json::Value& jArgs, UpdateCb& stepCb) {
    printTrace("%s| Activation", getName().c_str());

    std::string sActivationCmd;
    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier == ICellularRadio::VALUE_CARRIER_AERIS) {

        if(!jArgs["msid"].isString()) {
            printError("%s| Arguments missing \"msid\"", getName().c_str());
            return INVALID_ARGS;
        }

        if(!jArgs["mdn"].isString()) {
            printError("%s| Arguments missing \"mdn\"", getName().c_str());
            return INVALID_ARGS;
        }

        std::string sMsid = jArgs["msid"].asString();
        std::string sMdn = jArgs["mdn"].asString();
        if(sMdn.size() != 10) {
            printError("%s| MDN not valid.  Expected length of 10: [%s][%d]", getName().c_str(), sMdn.c_str(), sMdn.size());
            return INVALID_ARGS;
        }

        if(sMsid.size() != 10) {
            printError("%s| MIN not valid.  Expected length of 10: [%s][%d]", getName().c_str(), sMsid.c_str(), sMsid.size());
            return INVALID_ARGS;
        }

        //Set MDN & MSID
        ICellularRadio::CODE code;
        code = setMdn(jArgs);
        if(code != SUCCESS) {
            return code;
        }

        code = setMsid(jArgs);
        if(code != SUCCESS) {
            return code;
        }

        resetRadio(10000);

        return SUCCESS;

    } else if(sCarrier == ICellularRadio::VALUE_CARRIER_VERIZON) {
        sActivationCmd = "AT+CDV*22899\r";
    } else if(sCarrier == ICellularRadio::VALUE_CARRIER_USCELLULAR) {
        sActivationCmd = "AT+CDV*228\r";
    } else {
        return NOT_APPLICABLE;
    }

    ICellularRadio::CODE result = FAILURE;
    std::size_t pos = 0;
    std::size_t end = 0;

    IsNeedMoreData isNeedMoreData = [&stepCb, &result, &pos, &end,  this](const std::string& iterationData, const std::string& allData)->bool {
        //Verizon Activation
        /* Expected Events:
        *  OK
        *  #OTASP: 0   //Start OTASP
        *  #OTASP: 1   //Start OTASP Commit
        *  #OTASP: 2   //End OTASP
        *
        *  ERRORS:
        *  NO CARRIER  //Account Plan Not Active
        *  #OTASP: 5   //Activation Failed
        */


        if(iterationData.empty()) {
            //No new data
            return true;
        }

        end = allData.find(ICellularRadio::NL, pos);
        printTrace("%s| Activation Reset Callback Started", this->getName().c_str());

        while(end != std::string::npos) {
            size_t next;
            std::string sLine = MTS::Text::getLine(allData, pos, next);

            printTrace("%s| Line: [%s]", this->getName().c_str(), sLine.c_str());

            if(sLine == ICellularRadio::RSP_OK) {
                if(stepCb) {
                    stepCb(Json::Value("Activation Info: Over-the-Air Programming in Process"));
                }
            } else if(sLine == "#OTASP: 0") {
                if(stepCb) {
                    stepCb(Json::Value("Activation Info: Saving Radio Configurations"));
                }
            } else if(sLine == "#OTASP: 1") {
                if(stepCb) {
                    stepCb(Json::Value("Activation Info: Restarting Radio"));
                }
            } else if(sLine == "#OTASP: 2") {
                if(stepCb) {
                    stepCb(Json::Value("Activation Info: Activation Completed Successfully"));
                }
                //Sleep for a few seconds while radio resets
                MTS::Thread::sleep(5000);
                this->resetConnection(10000);
                result = SUCCESS;
                return false;
            } else if(sLine == "#OTASP: 5") {
                if(stepCb) {
                    stepCb(Json::Value("Activation Error: Activation Failed"));
                }
                return false;
            } else if(sLine == "NO CARRIER") {
                if(stepCb) {
                    stepCb(Json::Value("Activation Error: Account Plan May Not Be Active"));
                }
                result = ERROR;
                return false;
            } else if(sLine == ICellularRadio::RSP_ERROR) {
                result = ERROR;
                return false;
            }

            //Set cursors for next iteration
            if(next != std::string::npos) {
                pos = next;
            }
            end = allData.find(ICellularRadio::NL, pos);
        }

        printTrace("%s| Activation Callback Finished", this->getName().c_str());
        return true;
    };

    sendCommand(sActivationCmd, isNeedMoreData, 5 * 60000);
    return result;
}

ICellularRadio::CODE CdmaRadio::getNetworkStatus(Json::Value& jData) {
    printTrace("%s| Get Network Status", getName().c_str());

    getCommonNetworkStats(jData);

    std::string sCarrier;
    getCarrier(sCarrier);

    if(sCarrier == ICellularRadio::VALUE_CARRIER_SPRINT) {
        Json::Value& jDebug = jData["debug"];
        std::string sCmd("AT$DEBUG?");
        std::string sResult = MTS::Text::trim(sendCommand(sCmd));
        if (sResult.find(ICellularRadio::RSP_OK) != std::string::npos) {
            std::vector<std::string> vCategories = MTS::Text::split(sResult, "\r\n\r\n");
            for(size_t i = 0 ; i < vCategories.size(); i++) {
                std::vector<std::string> vLine = MTS::Text::split(vCategories[i], "\r\n");
                if(vLine[0] == "1x Engineering") {
                    Json::Value& j1x = jDebug["1xrtt"];
                    splitAndAssign(vLine[1], "State", j1x, "state");
                    splitAndAssign(vLine[3], "Channel", j1x, "channel");
                    splitAndAssign(vLine[4], "Band Class", j1x, "bandClass");
                    splitAndAssign(vLine[5], "SID", j1x, "sid");
                    splitAndAssign(vLine[6], "NID", j1x, "nid");
                    splitAndAssign(vLine[7], "Base ID", j1x, "baseId");
                    splitAndAssign(vLine[8], "PN", j1x, "pn");
                    splitAndAssign(vLine[9], "P_rev", j1x, "pRev");
                    splitAndAssign(vLine[10], "Lat", j1x, "lat");
                    splitAndAssign(vLine[11], "Lon", j1x, "lon");
                    splitAndAssign(vLine[12], "Rx Pwr", j1x, "rxPwr");
                    splitAndAssign(vLine[13], "Rx Ec/Io", j1x, "rxEcIo");
                    splitAndAssign(vLine[14], "Rx FER", j1x, "rxFer");
                    splitAndAssign(vLine[15], "Tx Pwr", j1x, "txPwr");
                    splitAndAssign(vLine[16], "Active Set", j1x, "activeSet");
                    splitAndAssign(vLine[17], "Neighbor Set", j1x, "neighborSet");
                } else if(vLine[0] == "EVDO Engineering") {
                    Json::Value& jEvdo = jDebug["evdo"];
                    splitAndAssign(vLine[1], "State", jEvdo, "state");
                    splitAndAssign(vLine[2], "Mac Index", jEvdo, "macIndex");
                    splitAndAssign(vLine[3], "Channel", jEvdo, "channel");
                    splitAndAssign(vLine[4], "Color Code", jEvdo, "colorCode");
                    splitAndAssign(vLine[5], "Sector ID", jEvdo, "sectorId");
                    splitAndAssign(vLine[6], "PN", jEvdo, "pn");
                    splitAndAssign(vLine[7], "Rx Pwr", jEvdo, "rxPwr");
                    splitAndAssign(vLine[8], "Rx PER", jEvdo, "rxPer");
                    splitAndAssign(vLine[9], "Pilot Energy", jEvdo, "pilotEnergy");
                    splitAndAssign(vLine[10], "DRC", jEvdo, "drc");
                    splitAndAssign(vLine[11], "SINR", jEvdo, "sinr");
                    splitAndAssign(vLine[12], "AN-AAA", jEvdo, "anAaa");
                    splitAndAssign(vLine[13], "IP Address", jEvdo, "ipAddress");
                } else if(vLine[0] == "Configuration") {
                    Json::Value& jConf = jDebug["configuration"];
                    splitAndAssign(vLine[1], "Technology", jConf, "technology");
                    splitAndAssign(vLine[2], "1x Diversity", jConf, "1xDiversity");
                    splitAndAssign(vLine[3], "EVDO Diversity", jConf, "evdoDiversity");
                    splitAndAssign(vLine[4], "QLIC", jConf, "qlic");
                    splitAndAssign(vLine[5], "PRL", jConf, "prl");
                    splitAndAssign(vLine[6], "Chipset", jConf, "chipset");
                    splitAndAssign(vLine[7], "AMSS version", jConf, "amssVersion");
                    splitAndAssign(vLine[8], "Device version", jConf, "deviceVersion");
                    splitAndAssign(vLine[9], "Hardware version", jConf, "hardwareVersion");
                    splitAndAssign(vLine[10], "Browser", jConf, "browser");
                    splitAndAssign(vLine[11], "Multimedia Version", jConf, "multimediaVersion");
                }
            }

        } else {
            printDebug("%s| SPRINT 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;
}

std::string CdmaRadio::getMeidLastSix() {
    std::string sMeid;
    if(getMeid(sMeid) != SUCCESS || sMeid.size() != 14) {
        return "";
    }
    return sMeid.substr(8);
}