/******************************************************************************
*  Filename:       sblAppEx.cpp
*  Revised:        $Date$
*  Revision:       $Revision$
*
*  Description:    Serial Bootloader Library application example.
*                  This example enumerates all COM devices and lets you
*                  select which port to connect to. The example assumes the
*                  connected device is a CC2538, CC2650 or CC2652 and programs 
*                  a blinky onto the device. After programming the blinky, 
*                  bootloader mode may be forced by 
*                  - holding SELECT button on 06EB (for CC2538 and CC26x0 EMKs), or
*                  - holding BTN-1 on the device LaunchPad (for CC26x2 LPs)
*                  when resetting the chip.
*  
*  Copyright (C) 2013 - 2024 Texas Instruments Incorporated - http://www.ti.com/
*
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*    Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
*    Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
*    Neither the name of Texas Instruments Incorporated nor the names of
*    its contributors may be used to endorse or promote products derived
*    from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
*  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
*  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************/
#include <sbllib.h>
#include <ComPortElement.h>

#include <vector>
#include <iostream>
#include <fstream>

//
// Including functions for millisecond precision timing.
// Taken from http://stackoverflow.com/questions/2494356/how-to-use-gettimeofday-or-something-equivalent-with-visual-studio-c-2008
//
#include <time.h>
#include <windows.h>
const __int64 DELTA_EPOCH_IN_MICROSECS= 11644473600000000;

using namespace std;

struct timeval2 {
    __int32    tv_sec;          /* seconds */
    __int32    tv_usec;         /* microseconds */
};

int gettimeofday(struct timeval2 *tv)
{
    FILETIME ft;
    __int64 tmpres = 0;
    TIME_ZONE_INFORMATION tz_winapi;  
    int rez = 0;

    ZeroMemory(&ft,sizeof(ft));
    ZeroMemory(&tz_winapi,sizeof(tz_winapi));

    GetSystemTimeAsFileTime(&ft);

    tmpres = ft.dwHighDateTime;
    tmpres <<= 32;
    tmpres |= ft.dwLowDateTime;

    /*converting file time to UNIX epoch*/
    tmpres /= 10;  /*convert into microseconds*/
    tmpres -= DELTA_EPOCH_IN_MICROSECS; 
    tv->tv_sec = (__int32)(tmpres*0.000001);
    tv->tv_usec =(tmpres%1000000);

    return 0;
}

double diff_ms(timeval2 t1, timeval2 t2)
{
    return ((((t1.tv_sec - t2.tv_sec) * 1000000) + (t1.tv_usec - t2.tv_usec)) + 500)/1000;
}


// Calculate crc32 checksum the way CC2538 and CC2650 does it.
int calcCrcLikeChip(const unsigned char *pData, unsigned long ulByteCount)
{
    unsigned long d, ind;
    unsigned long acc = 0xFFFFFFFF;
    const unsigned long ulCrcRand32Lut[] =
    {
        0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 
        0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C, 
        0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C, 
        0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C
    };

    while ( ulByteCount-- )
    {
        d = *pData++;
        ind = (acc & 0x0F) ^ (d & 0x0F);
        acc = (acc >> 4) ^ ulCrcRand32Lut[ind];
        ind = (acc & 0x0F) ^ (d >> 4);
        acc = (acc >> 4) ^ ulCrcRand32Lut[ind];
    }

    return (acc ^ 0xFFFFFFFF);
}

/// Application status function (used as SBL status callback)
void appStatus(char *pcText, bool bError)
{
    if(bError)
    {
        cerr << pcText;
    }
    else
    {
        cout << pcText;
    }
}


/// Application progress function (used as SBL progress callback)
static void appProgress(uint32_t progress)
{
    fprintf(stdout, "\r%d%% ", progress);
    fflush(stdout);
}

// Time variables for calculating execution time.
static timeval2 tBefore, tAfter;

// Start millisecond timer
static void getTime(void)
{
    gettimeofday(&tBefore);
}

// Print time since getTime()
static void printTimeDelta(void)
{
    gettimeofday(&tAfter);
    printf("(%.2fms)\n", diff_ms(tAfter, tBefore));
}

std::string first_numberstring(std::string const& str)
{
    std::string const digits = "0123456789";
    std::size_t const n = str.find_first_of(digits);
    if (n != std::string::npos)
    {
        std::size_t const m = str.find_first_not_of(digits, n);
        return str.substr(n, m != std::string::npos ? m - n : m);
    }
    return std::string();
}

static void printComPorts(ComPortElement *pElements, int32_t &nElem, std::string tabs)
{
    // Enumerate COM ports
    SblDevice::enumerate(pElements, nElem);
    for (int32_t i = 0; i < nElem; i++)
    {
        cout << tabs;
        printf("| %s\n", pElements[i].description);
    }
    if (nElem == 0)
    {
        cout << tabs;
        cout << "| No COM ports detected.\n";
    }
}

static inline void printSupportedDevices(std::string tabs)
{
    cout << tabs; cout << "|IDx\t| Device\n";
    cout << tabs; cout << "----------------------------------\n";
    cout << tabs; cout << "|  0\t| CC2538\n";
    cout << tabs; cout << "|  1\t| CC13x0/CC26x0\n";
    cout << tabs; cout << "|  2\t| CC2640R2\n";
    cout << tabs; cout << "|  3\t| CC13x2/CC26x2\n";
    cout << tabs; cout << "|  4\t| CC13x2PSIP/CC26x2PSIP\n";
    cout << tabs; cout << "|  5\t| CC13x2x7/CC26x2x7\n";
    cout << tabs; cout << "|  6\t| CC2652RB\n";
    cout << tabs; cout << "|  7\t| CC13x1x3/CC26x1x3\n";
    cout << tabs; cout << "|  8\t| CC13x4/CC26x4\n";
}

static void printHelp(void) {
    ComPortElement pElements[10];       // An array for the COM ports that will be found by enumeration.
    int32_t nElem = 10;                 // Sets the number of COM ports to list by SBL.


    cout << "\nusage: sblAppEx.exe [-help] COM_PORT DEVICE_ID BINARY [BINARY_CCFG]  \n\n";

    cout << "\t\"Flashes a custom binary file using the ROM Bootloader.\"  \n\n";

    cout << "arguments:\n";
    cout << "\t-help, help \t prints this user help guide and exits\n";
    cout << "\tCOM_PORT \t (int) the COM port which the device is connected to, ex: '12' for the COM12 port\n";
    cout << "\tDEVICE_ID \t (int [0-8]) the IDx of the connected device, ex: '2' for the CC2640R2 device \n\t\t\t (see list of supported devices under 'extra info' below)\n";
    cout << "\tBINARY \t\t (string) the name of the binary file to be loaded, ex: 'blinky_example.bin'\n";
    cout << "\tBINARY_CCFG \t (string) the name of the CCFG binary file to be loaded, ex: 'blinky_example_onlyccfg.bin'\n\t\t\t (Only required for CC13x4/CC26x4 devices, where the CCFG is located outside of the main flash)\n\n";

    cout << "extra info:\n";
    cout << "\tThe following devices are currently connected to the host:\n\n";

    printComPorts(pElements, nElem, "\t\t");

    cout << "\n\n\tThe following devices are supported:\n\n";
    printSupportedDevices("\t\t");

    cout << "\n\n";
}


// Defines
#define DEVICE_CC2538                0x2538
#define DEVICE_CC26X0                0x2650
#define DEVICE_CC2640R2              0x2640
#define DEVICE_CC26X2                0x2652
#define DEVICE_CC26X1X3              0x2651
#define DEVICE_CC26X4                0x2674
#define CC2538_FLASH_BASE            0x00200000
#define CC26XX_FLASH_BASE            0x00000000
#define CC26X4_CCFG_BASE             0x50000000

#define DEVICE_SELECT_CC2538         0
#define DEVICE_SELECT_CC26X0         1
#define DEVICE_SELECT_CC2640R2       2
#define DEVICE_SELECT_CC26X2         3
#define DEVICE_SELECT_CC26X2PSIP     4
#define DEVICE_SELECT_CC26X2X7       5
#define DEVICE_SELECT_CC2652RB       6
#define DEVICE_SELECT_CC26X1X3       7
#define DEVICE_SELECT_CC26X4         8


// Application main function
int main(int argc, char** argv)
{
    //
    // START: Program Configuration
    //
    /* UART baud rate. Default: 230400 */
    uint32_t baudRate = 230400;

    //
    // END: Program configuration
    //

    uint32_t deviceType = DEVICE_CC26X2;
    SblDevice *pDevice = NULL;          // Pointer to SblDevice object
    ComPortElement pElements[10];       // An array for the COM ports that will be found by enumeration.
    int32_t nElem = 10;                 // Sets the number of COM ports to list by SBL.
    std::string comPort;                // COM portnumber
    int32_t devStatus = -1;             // Hold SBL status codes
    std::string fileName;               // File name to program
    std::string fileName_ccfg;          // File name of CCFG to program (only used for CC13x4/CC26x4)
    uint32_t byteCount = 0;             // File size in bytes
    uint32_t fileCrc, devCrc;           // Variables to save CRC checksum
    uint32_t devFlashBase;              // Flash start address
    static std::vector<char> pvWrite(1);// Vector to application firmware in.
    static std::ifstream file;          // File stream
    uint32_t device;                    // Selected device type
    bool bEnableXosc = true;
    bool dualBinaryLoaded = false;      // Used to load the CCFG binary
    bool BL_EN = true;                  // Bootloader enabled/disabled in new binary


    //
    // Set callback functions
    //
    SblDevice::setCallBackStatusFunction(&appStatus);
    SblDevice::setCallBackProgressFunction(&appProgress);


    //
    // Check number of input arguments
    //

    if (argc <= 1)
    {
        // No arguments, continue to request input arguments
    }
    else if (argc < 4)
    {
        // Too few arguments
        std::string str1 = "help";
        std::string str2 = "-help";
        if (argv[1] == str1 || argv[1] == str2)
        {
            printHelp();
        }
        else
        {
            cout << "Too few arguments. Use '-help' to bring up the help guide.\n\n";
        }
        
        goto exit;
    }
    else if (argc == 4 || argc == 5)
    {   

        std::string str = first_numberstring(argv[1]);
        if (str == std::string()) {
            std::cout << "Invalid COM_PORT argument. Use '-help' to bring up the help guide.\n\n";
            goto error;
        }
        comPort = "COM";
        comPort.append(str);

        try {
            device = std::stoi(argv[2]);
        }
        catch (const std::invalid_argument& e) {
            std::cout << "Invalid DEVICE_ID argument. Use '-help' to bring up the help guide.\n\n";
            goto error;
        }
        
        fileName = argv[3];
        

        if (device == DEVICE_SELECT_CC2538)
        {
            deviceType = DEVICE_CC2538;
            devFlashBase = CC2538_FLASH_BASE;
        }
        else if (device == DEVICE_SELECT_CC26X0)
        {
            deviceType = DEVICE_CC26X0;
            devFlashBase = CC26XX_FLASH_BASE;
        }
        else if (device == DEVICE_SELECT_CC2640R2)
        {
            deviceType = DEVICE_CC2640R2;
            devFlashBase = CC26XX_FLASH_BASE;
        }
        else if (device == DEVICE_SELECT_CC26X2 || device == DEVICE_SELECT_CC26X2PSIP 
                || device == DEVICE_SELECT_CC26X2X7 || device == DEVICE_SELECT_CC2652RB)
        {
            deviceType = DEVICE_CC26X2;
            devFlashBase = CC26XX_FLASH_BASE;
        }
        else if (device == DEVICE_SELECT_CC26X1X3)
        {
            deviceType = DEVICE_CC26X1X3;
            devFlashBase = CC26XX_FLASH_BASE;
        }
        else if (device == DEVICE_SELECT_CC26X4)
        {
            deviceType = DEVICE_CC26X4;
            devFlashBase = CC26XX_FLASH_BASE;
        }
        else
        {
            goto error;
        }

        if (deviceType == DEVICE_CC26X4)
        {
            if (argc == 5)
            {
                fileName_ccfg = argv[4];
            }
            else
            {
                cout << "\nNo CCFG binary file was provided. The CCFG will not be updated!\n\n";
            }
        }
        else
        {
            if (argc == 5)
            {
                cout << "Too many input arguments. Use '-help' to bring up the help guide.\n\n";
                goto exit;
            }
        }

        //
        // All arguments received, continue to flashing
        //
        goto flashing;
    }
    else
    {
        // Too many arguments, go to error
        cout << "Too many input arguments. Use '-help' to bring up the help guide.\n\n";
        goto error;
    }


    cout << "+-------------------------------------------------------------\n";
    cout << "| Serial Bootloader Library Example Application \n";
    cout << "+-------------------------------------------------------------\n\n";

    //
    // Enumerate and list COM ports
    //
    cout << "+-------------------------------------------------------------\n";
    cout << "| COM ports:\n";
    cout << "+-------------------------------------------------------------\n";
    cout << "| Description\n";
    printComPorts(pElements, nElem, "");
    cout << "+-------------------------------------------------------------\n\n";

    //
    // Wait for user to select COM port
    //
    cout << "Select COM port: ";
    cin >> comPort;
    comPort = first_numberstring(comPort);
    comPort.insert(0, "COM");

    //
    // Select device
    //
    cout << "+-------------------------------------------------------------\n";
    cout << "| Supported devices:\n";
    cout << "+-------------------------------------------------------------\n";
    printSupportedDevices("");
    cout << "\nSelect target device: ";
    cin >> device;
    if (device > NUM_SUPPORTED_DEVICES)
    {
        cout << "Index out of bounds.\n";
        goto error;
    }

    //
    // Set variables based on user input
    //
    if (device == DEVICE_SELECT_CC2538)
    {
        deviceType = DEVICE_CC2538;
        fileName = "blinky_backdoor_select_btn2538.bin";
        devFlashBase = CC2538_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X0)
    {
        deviceType = DEVICE_CC26X0;
        fileName = "blinky_backdoor_select_btn26x0.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC2640R2)
    {
        deviceType = DEVICE_CC2640R2;
        fileName = "blinky_backdoor_select_btn2640r2.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X2)
    {
        deviceType = DEVICE_CC26X2;
        fileName = "blinky_backdoor_select_btn26x2.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X2PSIP)
    {
        deviceType = DEVICE_CC26X2;
        fileName = "blinky_backdoor_select_btn26x2PSIP.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X2X7)
    {
        deviceType = DEVICE_CC26X2;
        fileName = "blinky_backdoor_select_btn26x2x7.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC2652RB)
    {
        deviceType = DEVICE_CC26X2;
        fileName = "blinky_backdoor_select_btn2652RB.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X1X3)
    {
        deviceType = DEVICE_CC26X1X3;
        fileName = "blinky_backdoor_select_btn26x1x3.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else if (device == DEVICE_SELECT_CC26X4)
    {
        deviceType = DEVICE_CC26X4;
        fileName = "blinky_backdoor_select_btn26x4_noccfg.bin";
        fileName_ccfg = "blinky_backdoor_select_btn26x4_onlyccfg.bin";
        devFlashBase = CC26XX_FLASH_BASE;
    }
    else
    {
        goto error;
    }

    //
    // Select custom binary?
    //
    cout << "+-------------------------------------------------------------\n";
    cout << "| Select Binary File:\n";
    cout << "+-------------------------------------------------------------\n";
    cout << "|IDx\t| Binary File\n";
    cout << "|  0\t| Blinky Example\n";
    cout << "|  1\t| Use Custom Binary (enter binary file name in next step)\n";
    cout << "+-------------------------------------------------------------\n\n";
    uint32_t binOpt;
    cout << "Select Binary Option: ";
    cin >> binOpt;
    if (binOpt >= 2)
    {
        cout << "Index out of bounds.\n";
        goto error;
    }
    else if (binOpt == 1)
    {

        if (deviceType == DEVICE_CC26X4)
        {
            cout << "Application binary file name (without CCFG): ";
            cin >> fileName;

            cout << "CCFG binary file name (only CCFG) (type '-skip' to skip flashing the CCFG): ";
            std::string ans;
            cin >> ans;
            if (ans == "-skip")
            {
                fileName_ccfg = "";
            }
            else
            {
                fileName_ccfg = ans;
            }
        }
        else
        {
            cout << "Binary file name: ";
            cin >> fileName;
        }
    }
    else
    {
        //do nothing
    }

    //
    // Should SBL try to enable XOSC? (Not possible for CC26xx)
    // Automatically enable if argument parsing is used
    //
    if(deviceType == 0x2538)
    {
        char answer[64];
        cout << "Enable device CC2538 XOSC? (Y/N): ";
        cin >> answer;
        bEnableXosc = (answer[0] == 'Y' || answer[0] == 'y') ? true : false;
    }

    goto flashing;


flashing:

    file = ifstream();

    //
    // If we are loding the second binary, there is no need to create and connect a new SBLDevice.
    //
    if (dualBinaryLoaded == false)
    {

        //
        // Create SBL object
        //
        pDevice = SblDevice::Create(deviceType);
        if (pDevice == NULL)
        {
            cout << "No SBL device object.\n";
            goto error;
        }


        //
        // Connect to device
        //
        cout << "\nConnecting (" << comPort << " @ " << baudRate << " baud) ...\n";
        getTime();
        if (pDevice->connect(comPort, baudRate, bEnableXosc) != SBL_SUCCESS)
        {
            goto error;
        }
        printTimeDelta();

    }
    
    //
    // Read file
    //
    file.open(fileName.c_str(), std::ios::binary);
    if(file.is_open())
    {
        //
        // Get file size:
        //
        file.seekg(0, std::ios::end);
        byteCount = (uint32_t)file.tellg();
        file.seekg(0, std::ios::beg);

        //
        // Read data
        //
        pvWrite.resize(byteCount);
        file.read((char*) &pvWrite[0], byteCount);
    }
    else   
    {
        cout << "Unable to open file " << fileName.c_str();
        goto error;
    }

    //
    // Calculate file CRC checksum
    //
    fileCrc = calcCrcLikeChip((unsigned char *)&pvWrite[0], byteCount);

    //
    // Calculate file CRC checksum
    //
    fileCrc = calcCrcLikeChip((unsigned char*)&pvWrite[0], byteCount);

    //
    // Check if bootloader is enabled in new CCFG binary
    //
    uint32_t ui32BlCfgAddr = pDevice->getBootloaderEnableAddress();
    uint32_t ui32BlCfgDataIdx = ui32BlCfgAddr - devFlashBase;

    if (ui32BlCfgDataIdx <= byteCount)
    {
        if (((pvWrite[ui32BlCfgDataIdx]) & 0xFF) != SBL_CC2650_BL_CONFIG_ENABLED_BM)
        {
            cout << "\nWARNING! Bootloader is disabled in the new CCFG.\nContinuing will make it impossible to flash the device using this application.\n";
            cout << "Do you wish to continue?(yes/no)\n\nAnswer: ";
            std::string answ;
            cin >> answ;
            if (answ != "yes")
            {
                cout << "Exits program...\n";
                goto exit;
            }
            cout << "Continues to flashing.\n";
            BL_EN = false;
        }
    }

    //
    // Erasing as much flash needed to program firmware.
    //
    cout << "Erasing flash ...\n";
    getTime();
    if(pDevice->eraseFlashRange(devFlashBase, byteCount) != SBL_SUCCESS)
    {
        goto error;
    }
    printTimeDelta();

    //
    // Writing file to device flash memory.
    //
    cout << "Writing flash ...\n";
    getTime();
    if(pDevice->writeFlashRange(devFlashBase, byteCount, &pvWrite[0]) != SBL_SUCCESS)
    {
        goto error;
    }
    printTimeDelta();


    if (BL_EN == false)
    {
        // Skip CRC check since bootloader is disabled and will not respond.
        goto exit;
    }

    //
    // Calculate CRC checksum of flashed content.
    //
    cout << "Calculating CRC on device ...\n";
    getTime();
    if(pDevice->calculateCrc32(devFlashBase, byteCount, &devCrc) != SBL_SUCCESS)
    {
        goto error;
    }
    printTimeDelta();

    //
    // Compare CRC checksums
    //
    cout << "Comparing CRC ...\n";
    if(fileCrc == devCrc) printf("OK\n");
    else printf("Mismatch!\n");


    //
    // Check if device is CC26x4, in that case proceed to flash CCFG as well.
    //
    if (deviceType == DEVICE_CC26X4 && dualBinaryLoaded == false && fileName_ccfg != "")
    {
        cout << "Proceed to flash CCFG.\n";
        dualBinaryLoaded = true;
        devFlashBase = CC26X4_CCFG_BASE;
        fileName = fileName_ccfg;
        goto flashing;
    }

    cout << "Resetting device ...\n";
    if(pDevice->reset() != SBL_SUCCESS)
    {
        goto error;
    }
    cout << "OK\n";

    //
    // If we got here, all succeeded. Jump to exit.
    //
    goto exit;

error:
    cout << "\n\nAn error occurred: " << pDevice->getLastStatus();
exit:
    devStatus = 0;
    if(pDevice != NULL) {
        devStatus = pDevice->getLastStatus();
    }
    
    return devStatus;
}
