/*
 * Copyright (c) 2016-2019, 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 "display_utils.h"
#include "log_utils.h"
#include "misc_utils.h"

void PrintUsage()
{
    NvMediaIDPDeviceParams videoOutputs[MAX_OUTPUT_DEVICES];
    NvMediaStatus rt;
    int32_t outputDevicesNum, i;

    LOG_MSG("Usage: nvmimg_play [options]\n");
    LOG_MSG("Options:\n");
    LOG_MSG("-h                Prints usage\n");
    LOG_MSG("-c  [codec type]  Numeric codec type  = 1(H264) 2(H265) 3(VP9)\n");
    LOG_MSG("-c  [codec type]  Text codec type  = h264, h265, vp9\n");
    LOG_MSG("-f  [input file]  Input file name\n");
    LOG_MSG("-r  [value]       Frame rate\n");
    LOG_MSG("-n  [frames]      Number of frames to decode\n");
    LOG_MSG("-l  [loops]       Number of loops of playback\n");
    LOG_MSG("                  -1 for infinite loops of playback (default: 1)\n");
    LOG_MSG("-s  [output file] Output YUV File name to save\n");
    LOG_MSG("-crc       [gen/chk][CRC file]  Enable CRC checks (chk) or generate CRC file (gen)\n");
    LOG_MSG("-crcpath   [new path]           New Path for crc picture\n");
    LOG_MSG("-cropcrc                        CRC will be calculated on actual resolution\n");
    LOG_MSG("-w  [id]          Window ID. Default: 1\n");
    LOG_MSG("-p  [position]    Window position. Default: full screen size\n");
    LOG_MSG("-z  [depth]       Window depth. Default: 1\n");
    LOG_MSG("-d  [id]          Display ID\n");

    rt = GetAvailableIDPDisplayDevices(&outputDevicesNum, &videoOutputs[0]);
    if(rt != NVMEDIA_STATUS_OK) {
        LOG_ERR("PrintUsage: Failed retrieving available video output devices\n");
        return;
    }

    LOG_MSG("                  Available display devices: %d\n", outputDevicesNum);
    for(i = 0; i < outputDevicesNum; i++) {
        LOG_MSG("                        Display ID: %d\n", videoOutputs[i].displayId);
    }
    LOG_MSG("-t                Show decode timing info (not to be combined with -prof)\n");
    LOG_MSG("-prof             Enable per frame profiling at decoder \n");
    LOG_MSG("-id [instance id] Decoder instance Id. 0(Instance 0), 1(Instance 1)\n");
    LOG_MSG("-v  [level]       Logging Level = 0(Errors), 1(Warnings), 2(Info), 3(Debug)\n");
}

int ParseArgs(int argc, char **argv, TestArgs *args)
{
    bool bLastArg = false;
    bool bDataAvailable = false;
    bool bHasCodecType = false;
    bool bHasFileName = false;
    int i, x = 0, y = 0, w = 0, h = 0;
    FILE *file;

    /* app defaults */
    args->loop = 1;
    args->depth = 1;
    args->windowId = 1;
    args->instanceId = 0;
    args->checkCRC = NVMEDIA_FALSE;
    args->generateCRC = NVMEDIA_FALSE;
    args->cropCRC = NVMEDIA_FALSE;
    args->crcFilePath = '\0';
    args->decProfiling = NVMEDIA_FALSE;

    SetLogLevel(LEVEL_ERR); // Default logging level

    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], "h") == 0) {
                PrintUsage();
                return 1;
            }
            else if (strcmp(&argv[i][1], "c") == 0) {
                if (bDataAvailable) {
                    struct {
                        char *name;
                        NvMediaVideoCodec codec;
                    } codecs[] = {
                        { "h264", NVMEDIA_VIDEO_CODEC_H264 },
                        { "h265", NVMEDIA_VIDEO_CODEC_HEVC },
                        { "vp9", NVMEDIA_VIDEO_CODEC_VP9 },
                        { NULL, NVMEDIA_VIDEO_CODEC_H264 }
                    };
                    char *arg = argv[++i];
                    if (*arg == '1' || *arg == '2' || *arg == '3') {
                        args->eCodec = codecs[atoi(arg) - 1].codec;
                        bHasCodecType = true;
                    } else {
                        int j;
                        for(j = 0; codecs[j].name; j++) {
                            if (!strcasecmp(arg, codecs[j].name)) {
                                args->eCodec = codecs[j].codec;
                                bHasCodecType = true;
                                break;
                            }
                        }
                        if (!bHasCodecType) {
                            LOG_ERR("ParseArgs: -c must be followed by codec type\n");
                            return -1;
                        }
                    }
                } else {
                    LOG_ERR("ParseArgs: -c must be followed by codec type\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "f") == 0) {
                if (bDataAvailable) {
                    args->filename = argv[++i];
                    bHasFileName = true;
                    struct stat st;
                    file = fopen(args->filename, "rb");
                    if (!file) {
                        LOG_ERR("ParseArgs: failed to open stream %s\n", args->filename);
                        return -1;
                    }

                    if (stat(args->filename, &st) == -1) {
                        fclose(file);
                        LOG_ERR("ParseArgs: failed to stat stream %s\n", args->filename);
                        return -1;
                    }
                    args->fileSize = st.st_size;
                    fclose(file);
                } else {
                    LOG_ERR("ParseArgs: -f must be followed by file name\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "d") == 0) {
                if (bDataAvailable) {
                    args->displayEnabled = true;
                    if((sscanf(argv[++i], "%u", &args->displayId) != 1)) {
                        LOG_ERR("ParseArgs: Bad display id: %s\n", argv[i]);
                        return -1;
                    }

                    LOG_DBG("ParseArgs: Chosen display: (%d) \n", args->displayId);
                } else {
                    LOG_ERR("ParseArgs: -d must be followed by display id\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "w") == 0) {
                if (bDataAvailable) {
                    char *arg = argv[++i];
                    args->windowId = atoi(arg);
                } else {
                    LOG_ERR("ParseArgs: -w must be followed by window id\n");
                    return -1;
                }
                if (args->windowId > 2) {
                    LOG_ERR("ParseArgs: Bad window ID: %d. Valid values are [0-2]. ", args->windowId);
                    LOG_ERR("           Using default window ID 0\n");
                    args->windowId = 0;
                }
            }
            else if (strcmp(&argv[i][1], "p") == 0) {
                if (bDataAvailable) {
                    if((sscanf(argv[++i], "%u:%u:%u:%u", &x, &y, &w, &h) != 4)) {
                        LOG_ERR("ParseArgs: Bad resolution: %s\n", argv[i]);
                        return -1;
                    }
                    args->position.x0 = x;
                    args->position.y0 = y;
                    args->position.x1 = x + w;
                    args->position.y1 = y + h;
                    args->positionSpecifiedFlag = true;
                } else {
                    LOG_ERR("ParseArgs: -p must be followed by window position x0:x1:width:height\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "z") == 0) {
                if (bDataAvailable) {
                    char *arg = argv[++i];
                    args->depth = atoi(arg);
                } else {
                    LOG_ERR("ParseArgs: -z must be followed by depth value\n");
                    return -1;
                }
                if (args->depth > 255) {
                    LOG_ERR("ParseArgs: Bad depth value: %d. Valid values are [0-255]. ", args->depth);
                    LOG_ERR("           Using default depth value: 1\n");
                    args->depth = 1;
                }
            }
            else if (strcmp(&argv[i][1], "v") == 0) {
                if (bDataAvailable) {
                    char *arg = argv[++i];
                    args->logLevel = atoi(arg);
                    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((enum LogLevel)args->logLevel);
            }
            else if (strcmp(&argv[i][1], "r") == 0) {
                if (bDataAvailable) {
                    float framerate;
                    if (sscanf(argv[++i], "%f", &framerate)) {
                        args->frameTimeUSec = 1000000.0 / framerate;
                    } else {
                        LOG_ERR("ParseArgs: Invalid frame rate encountered (%s)\n", argv[i]);
                    }
                } else {
                    LOG_ERR("ParseArgs: -r must be followed by frame rate\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "n") == 0) {
                if (bDataAvailable) {
                    int decodeCount;
                    if (sscanf(argv[++i], "%d", &decodeCount)) {
                        args->numFramesToDecode = decodeCount;
                    } else {
                        LOG_ERR("ParseArgs: -n must be followed by decode frame count\n");
                    }
                } else {
                    LOG_ERR("ParseArgs: -n must be followed by frame count\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "s") == 0) {
                if (bDataAvailable) {
                    args->OutputYUVFilename = argv[++i];
                } else {
                    LOG_ERR("ParseArgs: -s must be followed by file name\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "cropcrc") == 0) {
                args->cropCRC = NVMEDIA_TRUE;
            }
            else if(strcmp(&argv[i][1], "crc") == 0) {
                if(bDataAvailable) {
                    ++i;
                    if (!strcasecmp(argv[i], "chk"))
                        args->checkCRC = NVMEDIA_TRUE;
                    else if (!strcasecmp(argv[i], "gen"))
                        args->generateCRC = NVMEDIA_TRUE;
                    else {
                        LOG_ERR("ParseArgs: -crc must be followed by gen/chk. "
                                "Instead %s was encountered.\n",
                            &argv[i][0]);
                        return -1;
                    }
                } else {
                    LOG_ERR("ParseArgs: -crc must be followed by gen/chk.\n");
                    return -1;
                }
            }
            else if(strcmp(&argv[i][1], "crcpath") == 0) {
                if(bDataAvailable) {
                    args->crcFilePath = argv[++i];
                } else {
                    printf("Error: -crcpath must be followed by crc file path.\n");
                    return 0;
                }
            }
            else if(strcmp(&argv[i][1], "prof") == 0) {
                args->decProfiling = NVMEDIA_TRUE;
            }
            else if (strcmp(&argv[i][1], "l") == 0) {
                if (argv[i+1]) {
                    int loop;
                    if (sscanf(argv[++i], "%d", &loop) && loop >= -1 && loop != 0) {
                        args->loop = loop;
                    } else {
                        LOG_ERR("ParseArgs: Invalid loop count encountered (%s)\n", argv[i]);
                    }
                } else {
                    LOG_ERR("ParseArgs: -l must be followed by loop count\n");
                    return -1;
                }
            }
            else if (strcmp(&argv[i][1], "id") == 0) {
                if (bDataAvailable) {
                    char *arg = argv[++i];
                    args->instanceId = atoi(arg);
                } else {
                    LOG_ERR("ParseArgs: -i must be followed by instance id\n");
                    return -1;
                }
                if (args->instanceId >= 2) {
                    LOG_ERR("ParseArgs: Bad instance ID: %d. Valid values are [0-1]. ", args->instanceId);
                    LOG_ERR("           Using default instance ID 0\n");
                    args->instanceId = 0;
                }
            }
            else if (strcmp(&argv[i][1], "t") == 0) {
                args->showDecodeTimimg = true;
            }
            else {
                LOG_ERR("ParseArgs: option %c is not supported anymore\n", argv[i][1]);
                return -1;
            }
        }
    }

    if (!bHasCodecType || !bHasFileName) {
        return -1;
    }

    return 0;
}
