/*************
 *
 * Filename:    fwDld_main.c
 *
 * Purpose:     Command line based firmware download application
 *
 * Copyright: © 2013 Sierra Wireless Inc., all rights reserved
 *
 **************/
#define _GNU_SOURCE

#include "fwDld_main.h"
#include "fwDld_9x00.h"
#include "fwDld_G3K.h"
#include "fwDld_9x15.h"

/****************************************************************
*                       DEFINES
****************************************************************/
#define DEV_NODE_SZ        256
#define DEV_KEY_SZ         16

/****************************************************************
*                       DATA STRUCTURE
****************************************************************/
//#define FWDLDDBG
/* Device information structure */
typedef struct device_info_param{
  CHAR deviceNode[DEV_NODE_SZ];
  CHAR deviceKey[DEV_KEY_SZ];
}device_info_t;

/****************************************************************
 *                    GLOBAL DATA
 ****************************************************************/

/* path to sdk binary */
static char *sdkbinpath = NULL;

/* device connectivity */
static device_info_t devices[1] = { { {'\0'}, {'\0'} } };
static device_info_t *pdev = &devices[0];
static BOOL bootupdated = FALSE;

/* Firmware download variables */
static BOOL sdkPathSet = FALSE;
static char* imgPath   = NULL;
static char* devType   = NULL;
static BYTE  g_modem_index = 0;

static BYTE devmode = DCS_DEVICE_MODE_DISCONNECTED;

/* Command line options to firmware download tool */
const char * const short_options = "s:hd:p:m:";

/* Command line long options for firmware download tool */
const struct option long_options[] = {
    {"sdkpath",  1, NULL, 's'},      /* SDK path */
    {"help",     0, NULL, 'h'},      /* Provides terse help to users */
    {"device",   1, NULL, 'd'},      /* Running device */
    {"path",     1, NULL, 'p'},      /* Specify the pathname */
    {"modem",    1, NULL, 'm'},      /* modem index */
    {NULL,       0, NULL,  0 }       /* End of list */
};

static struct fwDld_options userOptData;

/* macros*/


/****************************************************************
*                       FUNCTIONS
****************************************************************/

/*
 * Name:     StartSDK
 *
 * Purpose:  It starts the SDK by setting the SDK path, enumerates the device
 *           and connects to the SDK.
 *
 * Return:   SUCCESS on successfully starting SDK, else error code
 *
 * Notes:    none
 */
ULONG StartSDK(BYTE modem_index)
{
    ULONG rc = 0;
    BYTE  devicesSize = 1;

    /* Set SDK image path */
    if( eQCWWAN_ERR_NONE != (rc = SetSDKImagePath(sdkbinpath)) )
    {
        return rc;
    }

    /* Establish APP<->SDK IPC */
    if( eQCWWAN_ERR_NONE != (rc = SLQSStart(modem_index,NULL)) )
    {
        if( eQCWWAN_ERR_NONE != SLQSKillSDKProcess() )
        {
            return rc;
        }
        else
        {
            if( eQCWWAN_ERR_NONE != (rc = SLQSStart(modem_index,NULL)) )
            {
                return rc;
            }
        }
    }

    rc = SLQSGetDeviceMode ((BYTE *)&devmode);

    /* Can enumerate and connect only if device is in Application mode */
    if ( devmode == DCS_DEVICE_MODE_READY )
    {
        /* Enumerate the device */
        while( QCWWAN2kEnumerateDevices( &devicesSize, (BYTE *)(pdev) ) != 0 )
        {
            printf ("\nUnable to find device..\n");
            sleep(1);
        }

#ifdef FWDLDDBG
    fprintf( stderr,  "#devices: %d\ndeviceNode: %s\ndeviceKey: %s\n",
             devicesSize,
             pdev->deviceNode,
             pdev->deviceKey );
#endif
        bootupdated = FALSE;
        /* Connect to the SDK */
        rc = QCWWANConnect( pdev->deviceNode,
                            pdev->deviceKey );
    }
    else if ( devmode == DCS_DEVICE_MODE_BOOT_READY )
    {
        bootupdated = TRUE;
    }

    return rc;
}

/*
 * Name:     PrintUsage
 *
 * Purpose:  prints the right usage options for using this application.
 *
 * Return:   none
 *
 * Notes:    none
 */
void PrintUsage()
{
    printf( "\r\n" );
    printf( "App usage: \r\n\r\n" );
    printf( "  <appName> -s <sdk_path> -d [9x00/9x15/g3k] -p [pathname] -m [modem_index] -h \n\n" );
    printf( "  -s  --sdkpath \n ");
    printf( "        Specifies the path to the slqssdk. This is a mandatory parameter.\n\n");
    printf( "  -m  --modem \n ");
    printf( "        Specifies the modem index. This is a optional parameter.\n\n");
    printf( "        Default to zero if not specified, that is first modem\n\n");
    printf( "  -d  --device [9x00/9x15/g3k] \n" );
    printf( "        Specifies the device type. Must be lower cases. This option is optional.\n" );
    printf( "        If this option is omitted, 9x15 is the default setting.\n" );
    printf( "          - 9x00: This is a 9x00 based device.\n" );
    printf( "          - 9x15: This is a 9x15 based device.\n" );
    printf( "          - g3k: This is a Gobi device.\n\n" );
    printf( "  -p  --path [folder to firmware images]\n" );
    printf( "        This specifies the folder location of the firmware images. This option is mandatory. \n" );
    printf( "        Usage of this parameter depends on type of device.\n" );
    printf( "          - 9x00: Specify the path containing the image.\n" );
    printf( "          - 9x15: Specify the path containing a combined image or separate images.\n" );
    printf( "          - g3k: Should be in format <file path>/x , x should be a number,\n" );
    printf( "                 and this folder should contain both AMSS and UQCN.\n" );
    printf( "  -h  --help  \n" );
    printf( "        This option prints the usage instructions.\n\n" );
}


/*
 * Name:     parseSwitches
 *
 * Purpose:  Parse the various command line switches .
 *
 * Return:   none
 *
 * Notes:    none
 */
void parseSwitches(
    int                  argc,
    char                 **argv,
    struct fwDld_options *userData)
{
    UNUSEDPARAM( userData );

    int next_option;

    /* Parse the command line before doing anything else */
    do
    {
        /* Read the next option until there are no more */
        next_option = getopt_long( argc, argv,
                                   short_options,
                                   long_options, NULL );

        switch( next_option )
        {
            case 's':
                /* Get the SDK path */
                sdkbinpath = optarg;
                sdkPathSet = TRUE;
                break;

            case 'h':
                /* Print usage information */
                PrintUsage();
                exit( 0 );
                break;

            case 'd':
                /* caller specifies a device type*/
                devType = optarg;
                break;

            case 'p':
                /* caller specifies a path to the SDK executable */
                imgPath = optarg;
                break;

            case '?':
                /* Caller has used an invalid option */
                printf("\nInvalid option\n" );

                /* Print the usage info and exit */
                PrintUsage();
                exit(1);
                break;

            case 'm':
                /* caller specifies a modem index*/
                g_modem_index = atoi(optarg);
                break;

            case -1:
                /* Done with options list */
                break;

            default:
                exit(1);
                break;
        }
    }
    while( next_option != -1 );
}

/*
 * Name:     InitParams
 *
 * Purpose:  Initialize the parameters after validation.
 *
 * Return:   none
 *
 * Notes:    none
 */
void InitParams()
{
    ULONG len;

    /* First check for Device type  */
    if( NULL != devType )
    {
        len = strlen(devType);

        /* Check if device length is too long */
        if ( len > MAX_DEV_LEN )
        {
            fprintf( stderr, "\nError: Invalid Device Type\n" );
            exit(0);
        }

        /* Initialize device type */
        if( 0 == strcmp( devType,"9x00" ) )
        {
            userOptData.devType = (ULONG) eDEV_MDM9x00;
        }
        else if( 0 == strcmp( devType, "9x15" ) )
        {
            userOptData.devType = (ULONG) eDEV_MDM9x15;
        }
        else if( 0 == strcmp(devType,"9x30") )
        {
            userOptData.devType = (ULONG) eDEV_MDM9x30;
        }
        else if( 0 == strcmp( devType, "g3k" ) )
        {
            userOptData.devType = (ULONG) eDEV_GOBI3K;
        }
        else
        {
            /* Invalid device type specified*/
            fprintf( stderr, "\nError: Invalid Device Type\n" );
            exit(0);
        }
    }
    else
    {
        fprintf( stderr, "INFO: No Device Type specified. Using default( 9x15 )\n" );
        userOptData.devType = (ULONG) eDEV_MDM9x15;
    }

    /* Next check for path  */
    if( NULL != imgPath )
    {
        len = strlen(imgPath);

        /* Check if path length is too long */
        if ( len > MAX_PATH_LEN )
        {
            fprintf( stderr, "\nError: Length of Path too long (> 255) \n" );
            exit(0);
        }

        /* Copy Image Path */
        strncpy(userOptData.path, imgPath, len);
    }
    else
    {
        /* File path is mandatory */
        fprintf( stderr, "\nError: Path is a mandatory parameter \n" );
        exit(0);
    }

    fprintf( stderr, "INFO: Path: %s\n", userOptData.path );
    fprintf( stderr, "INFO: Device Type( 0,1,2->9x00,9x15,9x30,G3K ): %lu\n", userOptData.devType );

    /* TBD: Add validation of physical path and files */
}

/*
 * Name:     ExecDownload
 *
 * Purpose:  Register call back and invoke download.
 *
 * Return:   none
 *
 * Notes:    none
 */
void ExecDownload()
{
    BYTE  prevdevicemode;
    ULONG rc;

    switch( userOptData.devType )
    {
        case eDEV_MDM9x00:
            FwDloader_9x00( userOptData.path );
            break;
        case eDEV_MDM9x15:
            FwDloader_9x15( userOptData.path );
            break;
        case eDEV_GOBI3K:
            FwDloader_G3K( userOptData.path );
            break;
        default:
            /* We should never have reached here */
            exit(0);
            break;
    }
    prevdevicemode = devmode;
    rc = SLQSGetDeviceMode ((BYTE *)&devmode);

    if(eQCWWAN_ERR_NONE != rc)
    {
        fprintf( stderr, "ERROR: SLQSGetDeviceMode Failure Code: %lu\n", rc );
        return ;
    }
    /* If device was originally in boot mode, and is now in app mode,
     * re-connect the device to the SDK so that QMI services are properly set up.
     */
    if ( prevdevicemode == DCS_DEVICE_MODE_BOOT_READY &&
         devmode == DCS_DEVICE_MODE_READY)
    {
        /* Start the SDk */
        rc = StartSDK(g_modem_index);
        if( eQCWWAN_ERR_NONE != rc )
        {
            /* Display the failure reason */
            fprintf( stderr, "ERROR: Failed to start SDK: Exiting App\n"\
                             "Failure Code: %lu\n", rc );

            /* Failed to start SDK, exit the application */
            exit( EXIT_FAILURE );
         }
    }

    /* Display device status */
    if( DCS_DEVICE_MODE_READY == devmode )
    {
        fprintf( stderr, "\nINFO: Device successfully recovered to Application mode \n" );
    }
    else if( DCS_DEVICE_MODE_BOOT_READY == devmode )
    {
        fprintf( stderr, "\nERROR: Could not recover device, current State: Boot Mode \n" );
    }
    else
    {
        /* The below shouldn't happen. But just in case. */
        fprintf( stderr, "\nERROR: Could not recover device, current State: Disconnected \n" );
    }
}

/*
 * Name:     main
 *
 * Purpose:  Entry point of the application
 *
 * Return:   EXIT_SUCCESS, EXIT_FAILURE on unexpected error
 *
 * Notes:    none
 */
int main( int argc, char *argv[])
{
    /* Initialize the data structure before parsing switches */
    userOptData.devType = (ULONG)eDEV_INVALID;
    memset( (void*)&userOptData.path, 0, MAX_PATH_LEN);
    devmode = DCS_DEVICE_MODE_DISCONNECTED;

    /* Parse the command line switches  */
    parseSwitches( argc, argv, &userOptData );

    if( FALSE == sdkPathSet )
    {
        fprintf( stderr, "\nError: Mandatory SDK path not set \n" );
        /* Print usage information */
        PrintUsage();
        exit( 0 );
    }

    /* Start the SDK */
    StartSDK(g_modem_index);
    fprintf( stderr, "\nINFO: Running with device in %s mode\n",
             DCS_DEVICE_MODE_DISCONNECTED == devmode
             ?  "disconnected"
             :  DCS_DEVICE_MODE_READY == devmode
                ? "application"
                : "boot and hold" );

    /* Validate the parameters and formalize them for usage */
    InitParams();

    /* Execute firmware Download */
    ExecDownload();

    /* Finished, now exit */
    fprintf( stderr, "Exiting Application!!!\n");
    QCWWANDisconnect();
    exit(EXIT_SUCCESS);
}
