/* Copyright (c) 2017-2020, NVIDIA CORPORATION.  All rights reserved. All
 * information contained herein is proprietary and confidential to NVIDIA
 * Corporation.  Any use, reproduction, or disclosure without the written
 * permission of NVIDIA Corporation is prohibited.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "cmdline.h"
#include "log_utils.h"
#include "misc_utils.h"

void PrintUsage()
{
    LOG_MSG("nvmedia_imageofst is opticalFlow/stereoDisparity estimation test program\n");
    LOG_MSG("Usage: nvmedia_imageofst [options]\n");
    LOG_MSG("Options:\n");
    LOG_MSG("-h                   Prints usage\n");
    LOG_MSG("-version             Get NvMediaIOFST Major/Minor version\n");
    LOG_MSG("-res [WxH]           Input file resolution (eg. 800x480)\n");
    LOG_MSG("-f   [input file]    Input file name (must be YV12 or IYUV format)\n");
    LOG_MSG("-if  [input format]  Input file format\n");
    LOG_MSG("                     0: YV12 (default)\n");
    LOG_MSG("                     1: IYUV\n");
    LOG_MSG("-o   [output file]   Output file name, use xxxx_%02d.xxx/xxxx_%05d.xxx for single frame output\n");
    LOG_MSG("-mvschk [input file] Input file name\n");
    LOG_MSG("-frames [n]          Number of frames used for opticalflow estimation. Default: all frames\n");
    LOG_MSG("-etype  [value]      motion estimation type\n");
    LOG_MSG("                     0: opticalFlow estimation high perf mode (default)\n");
    LOG_MSG("                     1: stereoDisparity estimation high perf mode\n");
    LOG_MSG("                     2: opticalFlow estimation high quality mode\n");
    LOG_MSG("                     3: stereoDisparity estimation high quality mode\n");
    LOG_MSG("                     4: opticalFlow estimation ultra high perf mode\n");
    LOG_MSG("                     5: stereoDisparity estimation ultra high perf mode\n");
    LOG_MSG("-crcgen [crcs.txt]   Generate CRC values\n");
    LOG_MSG("-crcchk [crcs.txt]   Check CRC values\n");
    LOG_MSG("-v   [level]         Logging Level = 0(Errors), 1(Warnings), 2(Info), 3(Debug)\n");
    LOG_MSG("-ehints [hintsFileBaseName]  Enable External Hints\n");
    LOG_MSG("-esegmap             Enable Segment Map\n");
    LOG_MSG("-bit_depth [value]   Bit Depth of Input YUV. Default is 8 bit\n");
    LOG_MSG("-cf_idc [value]      Chroma format IDC of Input YUV. Default is 1\n");
    LOG_MSG("                     0: 400\n");
    LOG_MSG("                     1: 420 (default)\n");
    LOG_MSG("                     2: 422\n");
    LOG_MSG("                     3: 444\n");
    LOG_MSG("-forward_ref         Enable Forward Reference\n");
    LOG_MSG("-hwid                Use NvEnc A (0) or B(1)\n");
    LOG_MSG("-enable_profiling    Enable profiling\n");
    LOG_MSG("-ehintsPerMB [value] Numnber of external hintsPerMB. Default: 6\n");
    LOG_MSG("-pt [num_milliseconds]  Perf test, encode time per frame should not exceed num_milliseconds,\n");
    LOG_MSG("                        Where num_milliseconds should be target time derived for given quality setting and nvenc clock\n");
    LOG_MSG("-neg                 Enable Negetive Testing can be provide with following extended arguments\n");
    LOG_MSG("                     hwid_create         Force differenet HW id for Create frame\n");
    LOG_MSG("                     hwid_process        Force differenet HW id for Process frame\n");
    LOG_MSG("                     invalidDestroy      Passes invalid private handle to Destroy\n");
    LOG_MSG("                     nodevice            Avoid NVMediaDevice creation before IOFSTCreate\n");
    LOG_MSG("                     maxinputBuffering   Force unsupported max input buffering test\n");
    LOG_MSG("                     safetyExtHint       Force external hint for safety build\n");
    LOG_MSG("                     ipIsNull            Force input Image as Null\n");
    LOG_MSG("                     refIsNull           Force Reference Image as Null\n");
    LOG_MSG("                     opIsNull            Force output Image as Null\n");
    LOG_MSG("                     opSurfType          Force Unsupported Output surface type \n");
    LOG_MSG("                     opSurfDataType      Force Unsupported Output surface data type \n");
    LOG_MSG("                     opSurfBitDepth      Force Unsupported Output surface bit depth \n");
    LOG_MSG("                     opCmpOrder          followed by Output surface Component order RG/ALPHA for \n");
    LOG_MSG("                     opLayout            followed by Output surface layout BL/PL - BL(Block linear), PL(Pitch linear)\n");
    LOG_MSG("                     layout              followed by Input Surface Layout BL/PL - BL(Block linear), PL(Pitch linear)\n");
    LOG_MSG("                     memType             followed by Input Surface memType SP/PL - SP(semi planar), PL(planar) surface\n");
    LOG_MSG("                     resRef              followed by Reference frame resolution (eg. 800x480)\n");
    LOG_MSG("                     bit_depthRef        followed by Bit Depth of Input Referece Frame of YUV\n");
    LOG_MSG("                     cf_idcRef           followed by Chroma format IDC of Input Ref Frame of YUV\n");
    LOG_MSG("                     layoutRef           followed by Input Referece Surface Layout BL/PL - BL(Block linear), PL(Pitch linear)\n");
    LOG_MSG("                     memTypeRef          followed by Input Referece Surface memType SP/PL - SP(semi planar), PL(planar) surface\n");
    LOG_MSG("                     initParam           Force initParam as Null\n");
    LOG_MSG("                     regCheck            Performs all negative tests on IOFST register and unregister APIs\n");
    LOG_MSG("                     negIgnoreRegFailure Ignore failure in Image Registration and proceed to processing\n");
    LOG_MSG("                     negProcessHandle    Force ofst Handle to Process API as Null\n");
    LOG_MSG("                     opProcessSurfType   Force invalid surface Type Buffer to Process API\n");
}

int ParseArgs(int argc, char *argv[], TestArgs *args)
{
    NvMediaBool bLastArg = NVMEDIA_FALSE;
    NvMediaBool bDataAvailable = NVMEDIA_FALSE;
    NvMediaBool bHasInputFileName = NVMEDIA_FALSE;

    //init crcoption
    args->crcoption.crcGenMode = NVMEDIA_FALSE;
    args->crcoption.crcCheckMode = NVMEDIA_FALSE;
    args->enableExtHints = NVMEDIA_FALSE;
    args->enableSegmentMap = NVMEDIA_FALSE;
    args->forward_ref = NVMEDIA_FALSE;
    args->bit_depth = 8;
    args->chroma_format_idc = 1;

#if NV_IS_SAFETY
    /* OFST is supported only on NVENC 1 in safety build.
       Setting default value as 1 */
    args->instanceId = 1;
#else
    args->instanceId = 0;
#endif

    args->version = NVMEDIA_FALSE;
    args->instanceId = 1;
    args->enable_profiling = 0;
    args->hintsPerMB = 6;
    args->outputFilename = NULL;
    args->negTest = NVMEDIA_FALSE;
    args->ipIsNull = NVMEDIA_FALSE;
    args->refIsNull =  NVMEDIA_FALSE;
    args->opIsNull =  NVMEDIA_FALSE;
    args->opCmpOrder = NULL;
    args->opLayout = NULL;
    args->widthRef = 0;
    args->heightRef = 0;
    args->layout = NULL;
    args->memType = NULL;
    args->bit_depthRef = 0;
    args->chroma_format_idcRef = 0;
    args->layoutRef = NULL;
    args->memTypeRef = NULL;
    args->negCreateHWID =  NVMEDIA_FALSE;
    args->negProcessHWID =  NVMEDIA_FALSE;
    args->negNVMEDIADevice =  NVMEDIA_FALSE;
    args->negMaxBuffering =  NVMEDIA_FALSE;
    args->negExtHint =  NVMEDIA_FALSE;
    args->negOutSurfType =  NVMEDIA_FALSE;
    args->negOutSurfDataType =  NVMEDIA_FALSE;
    args->negOutSurfBitDepth =  NVMEDIA_FALSE;
    args->negDestroy = NVMEDIA_FALSE;
    args->negInitParam = NVMEDIA_FALSE;
    args->negRegisterCheck = NVMEDIA_FALSE;
    args->negProcessHandle = NVMEDIA_FALSE;
    args->negProcessOSType = NVMEDIA_FALSE;
    args->negIgnoreRegFailure = NVMEDIA_FALSE;

    int i;
    for (i = 1; i < argc; i++) {
        // check if this is the last argument
        bLastArg = ((argc - i) == 1);

        // check if there is data available to be parsed following the option
        bDataAvailable = (!bLastArg) && !(argv[i+1][0] == '-');

        if (argv[i][0] == '-') {
            if (strcmp(&argv[i][1], "res") == 0) {
                if (!bDataAvailable || sscanf(argv[++i], "%dx%d", &args->width, &args->height) != 2) {
                    LOG_ERR("ERR: -res must be followed by resolution (e.g. 800x400)\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "f") == 0) {
                if (bDataAvailable) {
                    args->inputFilename = argv[++i];
                    bHasInputFileName = NVMEDIA_TRUE;
                } else {
                    LOG_ERR("ERR: -f must be followed by input file name.\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "o") == 0) {
                if (bDataAvailable) {
                    args->outputFilename = argv[++i];
                } else {
                    LOG_ERR("ERR: -o must be followed by output file name.\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "mvschk") == 0) {
                if (bDataAvailable) {
                    args->mvsFilename = argv[++i];
                    args->mvsRefCheck = NVMEDIA_TRUE;
                } else {
                    LOG_ERR("ERR: -mvschk must be followed by input file name.\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "h") == 0) {
                return -1;
            }
            else if (strcmp(&argv[i][1], "v") == 0) {
                if (bDataAvailable) {
                    args->logLevel = atoi(argv[++i]);
                    if (args->logLevel < LEVEL_ERR || args->logLevel > LEVEL_DBG) {
                        LOG_ERR("ParseArgs: Invalid logging level chosen (%d). ", args->logLevel);
                        LOG_ERR("           Setting logging level to LEVEL_ERR (0)\n");
                    }
                } else {
                    args->logLevel = LEVEL_DBG; // Max logging level
                }
                SetLogLevel(args->logLevel);
            }
            else if (strcmp(&argv[i][1], "frames") == 0) {
                if (!bDataAvailable || !sscanf(argv[++i], "%d", &args->numFrames)) {
                    LOG_ERR("ERR: -frames must be followed by frame count.\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "if") == 0) {
                if (!bDataAvailable || !sscanf(argv[++i], "%d", &args->inputFormat)) {
                    LOG_ERR("ERR: -if must be followed by input format.\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "etype") == 0) {
                if (!bDataAvailable || !sscanf(argv[++i], "%d", &args->estimationType)) {
                    LOG_ERR("ERR: -etype must be followed by motion estimation type.\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "crcchk") == 0) {
                // crc check
                if(bDataAvailable) {
                    args->crcoption.crcCheckMode = NVMEDIA_TRUE;
                    args->crcoption.crcFilename = argv[++i];
                } else {
                    LOG_ERR("ParseArgs: -crcchk must be followed by crc file name\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "crcgen") == 0) {
                // crc generate
                if(bDataAvailable) {
                    args->crcoption.crcGenMode = NVMEDIA_TRUE;
                    args->crcoption.crcFilename = argv[++i];
                } else {
                    LOG_ERR("ParseArgs: -crcgen must be followed by crc file name\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "ehints") == 0) {
                // external hints
                if(bDataAvailable) {
                    args->enableExtHints = NVMEDIA_TRUE;
                    args->extHintsFileBaseName = argv[++i];
                } else {
                    LOG_ERR("ParseArgs: -ehints must be followed by hints file name\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "ehintsPerMB") == 0) {
                // external hintsPerMB
                if(bDataAvailable) {
                    args->hintsPerMB = atoi(argv[++i]);
                    if (args->hintsPerMB > 6) {
                        LOG_ERR("ParseArgs: Invalid number of hintsPerMB (%d). ", args->hintsPerMB);
                        return -1;
                    }
                } else {
                    LOG_ERR("ParseArgs: -ehintsPerMB must be followed by number of hintsPerMB\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "esegmap") == 0) {
                args->enableSegmentMap = NVMEDIA_TRUE;
            }
            else if (strcmp(&argv[i][1], "bit_depth") == 0) {
                if (bDataAvailable) {
                    args->bit_depth = atoi(argv[++i]);
                } else {
                    LOG_ERR("ParseArgs: -bit_depth must be followed by value\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "cf_idc") == 0) {
                if (bDataAvailable) {
                    args->chroma_format_idc = atoi(argv[++i]);
                } else {
                    LOG_ERR("ParseArgs: -cf_idc must be followed by value\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "forward_ref") == 0) {
                args->forward_ref = NVMEDIA_TRUE;
            }
            else if (strcmp(&argv[i][1], "hwid") == 0) {
                if (bDataAvailable) {
                    args->instanceId = atoi(argv[++i]);
                } else {
                    LOG_ERR("ParseArgs: -hwid must be followed by value\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "enable_profiling") == 0) {
                args->enable_profiling = NVMEDIA_TRUE;
            }
            else if (strcmp(&argv[i][1], "pt") == 0) {  // perf test
                if (bDataAvailable) {
                    args->perfTest = NVMEDIA_TRUE;
                    args->avgTimePerFrame  = atof(argv[++i]);
                    if (args->avgTimePerFrame < 0) {
                        LOG_ERR("%s: Clock value must be greater than zero (%f)\n", __func__, args->avgTimePerFrame);
                        return -1;
                    }
                } else {
                    LOG_ERR("ParseArgs: -pt must be followed by time in milli-seconds\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "version") == 0)
            {
                args->version = NVMEDIA_TRUE;
            }
            else if(strcmp(&argv[i][1], "neg") == 0)
            {
                args->negTest = NVMEDIA_TRUE;
                i++;
                while( i<argc && argv[i][0] != '-')
                {
                    if(strcmp(&argv[i][0], "hwid_create") == 0){
                        args->negCreateHWID = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "hwid_process") == 0){
                        args->negProcessHWID = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "regCheck") == 0){
                        args->negRegisterCheck = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "ignoreRegFailure") == 0){
                        args->negIgnoreRegFailure = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "invalidDestroy") == 0){
                        args->negDestroy = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "negProcessHandle") == 0){
                        args->negProcessHandle = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "nodevice") == 0){
                        args->negNVMEDIADevice = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "initParam") == 0){
                        args->negInitParam = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "maxinputBuffering") == 0){
                        args->negMaxBuffering = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "safetyExtHint") == 0){
                        args->negExtHint = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "ipIsNull") == 0){
                        args->ipIsNull = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "refIsNull") == 0){
                        args->refIsNull = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "opIsNull") == 0){
                        args->opIsNull = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "opSurfType") == 0){
                        args->negOutSurfType = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "opProcessSurfType") == 0){
                        args->negProcessOSType = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "opSurfDataType") == 0){
                        args->negOutSurfDataType = NVMEDIA_TRUE;
                    }
                    else if(strcmp(&argv[i][0], "opSurfBitDepth") == 0){
                        args->negOutSurfBitDepth = NVMEDIA_TRUE;
                    }
                    else if (strcmp(&argv[i][0], "layout") == 0) {
                        if (bDataAvailable) {
                            args->layout = argv[++i];
                            if(strcasecmp(args->layout , "BL") == 0 || strcasecmp(args->layout , "PL") == 0);
                            else {
                                LOG_ERR("ERR: -layout must be followed by layout as BL/PL.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -layout must be followed by layout as BL/PL.\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "memType") == 0) {
                        if (bDataAvailable) {
                            args->memType = argv[++i];
                            if(strcasecmp(args->memType , "SP") == 0 || strcasecmp(args->memType , "PL") == 0);
                            else {
                                LOG_ERR("ERR: -memType must be followed by memType as SP/PL.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -memType must be followed by memType as SP/PL.\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "opCmpOrder") == 0) {
                        if (bDataAvailable) {
                            args->opCmpOrder = argv[++i];
                            if(strcasecmp(args->opCmpOrder , "RG") == 0|| strcasecmp(args->opCmpOrder , "ALPHA") == 0);
                            else {
                                LOG_ERR("ERR: -opCmpOrder must be followed by layout as RG/ALPHA.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -opCmpOrder must be followed by layout as RG/ALPHA.\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "opLayout") == 0) {
                        if (bDataAvailable) {
                            args->opLayout = argv[++i];
                            if(strcasecmp(args->opLayout , "BL") == 0 || strcasecmp(args->opLayout , "PL") == 0);
                            else {
                                LOG_ERR("ERR: -opLayout must be followed by layout as BL/PL.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -opLayout must be followed by layout as BL/PL.\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "resRef") == 0) {
                        if (!bDataAvailable || sscanf(argv[++i], "%dx%d", &args->widthRef, &args->heightRef) != 2) {
                            LOG_ERR("ERR: -resRef must be followed by resolution (e.g. 800x400)\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "bit_depthRef") == 0) {
                        if (bDataAvailable) {
                            args->bit_depthRef = atoi(argv[++i]);
                        } else {
                            LOG_ERR("ParseArgs: -bit_depthRef must be followed by value\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "cf_idcRef") == 0) {
                        if (bDataAvailable) {
                            args->chroma_format_idcRef = atoi(argv[++i]);
                        } else {
                            LOG_ERR("ParseArgs: -cf_idcRef must be followed by value\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "layoutRef") == 0) {
                        if (bDataAvailable) {
                            args->layoutRef = argv[++i];
                            if(strcasecmp(args->layoutRef , "BL") == 0 || strcasecmp(args->layoutRef , "PL") == 0);
                            else {
                                LOG_ERR("ERR: -layoutRef must be followed by layout as BL/PL.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -layoutRef must be followed by layout as BL/PL.\n");
                            return -1;
                        }
                    }
                    else if (strcmp(&argv[i][0], "memTypeRef") == 0) {
                        if (bDataAvailable) {
                            args->memTypeRef = argv[++i];
                            if(strcasecmp(args->memTypeRef , "SP") == 0 || strcasecmp(args->memTypeRef , "PL") == 0);
                            else {
                                LOG_ERR("ERR: -memType must be followed by memType as SP/PL.\n");
                                return -1;
                            }
                        }
                        else {
                            LOG_ERR("ERR: -memType must be followed by memType as SP/PL.\n");
                            return -1;
                        }
                    }
                    else
                    {
                        LOG_ERR("ERR: option %s is not supported and ignored.\n", &argv[i][0]);
                    }
                    i++;
                }
                i--;
            }
            else
            {
                LOG_ERR("ERR: option %s is not supported and ignored.\n", &argv[i][1]);
            }
        }
    }

    if(!args->width || !args->height) {
        LOG_ERR("ERR: Input resolution must be set\n");
        return -1;
    }

    if(!bHasInputFileName) {
        LOG_ERR("ERR: No input file name was given\n");
        return -1;
    }
    if(!args->negTest)
    {
        if (args->bit_depth < 8 || args->bit_depth > 16) {
            LOG_ERR("ParseArgs: Invalid Input YUV bit depth (%d). ", args->bit_depth);
            return -1;
        }
        if (args->chroma_format_idc < 0 || args->chroma_format_idc > 3) {
            LOG_ERR("ParseArgs: Invalid chroma_format_idc (%d). ", args->chroma_format_idc);
            return -1;
        }
        if (args->instanceId > 1) {
            LOG_ERR("ParseArgs: Invalid instanceId (%d). ", args->instanceId);
            return -1;
        }
        if(args->estimationType < 0 || args->estimationType > 5) {
            LOG_ERR("ParseArgs: Invalid estimationType (%d). ", args->estimationType);
            return -1;
        }
    }
    return 0;
}
