/*
 * Copyright (c) 2018-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

/* STL Headers */
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <getopt.h>
#include <vector>
#include <iomanip>

#include "nvmedia_idp.h" // NvMediaIDP Header to query display properties

#include "NvSIPLTrace.hpp" // NvSIPLTrace to set library trace level
#include "NvSIPLQuery.hpp" // NvSIPLQuery to display platform config

#ifndef CCMDPARSER_HPP
#define CCMDPARSER_HPP

using namespace std;
using namespace nvsipl;

class CCmdLineParser
{
 public:
    // Command line options
    string sConfigName = "";
    vector<uint32_t> vMasks;
    string sTestConfigFile = "";
    uint32_t uDisplay = -1;
    uint32_t uDispWinID = 0;
    uint32_t uDispWinDepth = 0;
    NvMediaRect oDispRect = {0, 0, 0, 0};
    string sPrefix = "";
    int32_t uRunDurationSec = -1;
    uint32_t verbosity = INvSIPLTrace::LevelError;
    vector<string> vInputRawFiles;
    bool bDisableRaw = true;
    bool bDisableISP0 = false;
    bool bDisableISP1 = false;
    bool bShowFPS = false;
    bool bShowMetadata = false;
    bool bEnableSlave = false;
    PluginType autoPlugin = NV_PLUGIN;
    bool bAutoRecovery = false;
    bool bNvSci = false;
    bool bEnableProfiling = false;
    uint32_t uNumSkipFrames = 0u;
    uint64_t uNumWriteFrames = -1u;
    string sNitoFolderPath = "";
    bool bShowEEPROM = false;

    bool bIspInputCropEnable = false;
    uint32_t uIspInputCropY = 0u;
    uint32_t uIspInputCropH = 0u;

    // Other members
    bool bRectSet = false;

    static void ShowConfigs(string sConfigFile="")
    {
        auto pQuery = INvSIPLQuery::GetInstance();
        if (pQuery == nullptr) {
            cout << "INvSIPLQuery::GetInstance() failed\n";
        }

        auto status = pQuery->ParseDatabase();
        if (status != NVSIPL_STATUS_OK) {
            LOG_ERR("INvSIPLQuery::ParseDatabase failed\n");
        }

        if (sConfigFile != "") {
            status = pQuery->ParseJsonFile(sConfigFile);
            if (status != NVSIPL_STATUS_OK) {
                LOG_ERR("Failed to parse test config file\n");
            }
            LOG_INFO("Available platform configurations\n");
        }
        for (auto &cfg : pQuery->GetPlatformCfgList()) {
            cout << "\t" << std::setw(35) << std::left << cfg->platformConfig << ":" << cfg->description << endl;
        }
    }

    static void ShowUsage(void)
    {
        int32_t numDisplays = 0;
        NvMediaIDPDeviceParams* outputs = NULL;
        auto nvmStatus = NvMediaIDPQuery(&numDisplays, NULL);
        if (nvmStatus != NVMEDIA_STATUS_OK) {
            cout << "NvMediaIDPQuery failed! nvmStatus: " << nvmStatus << endl;
        } else {
            // Get available displays.
            outputs = new NvMediaIDPDeviceParams[numDisplays];
            nvmStatus = NvMediaIDPQuery(&numDisplays, outputs);
            if (nvmStatus != NVMEDIA_STATUS_OK) {
                cout << "NvMediaIDPQuery failed! nvmStatus: " << nvmStatus << endl;
            }
        }

        cout << "Usage:\n";
        cout << "-h or --help                               :Prints this help\n";
        cout << "-c or --platform-config 'name'             :Platform configuration. Supported values\n";
        ShowConfigs();
        cout << "--link-enable-masks 'masks'                :Enable masks for links on each deserializer connected to CSI\n";
        cout << "                                           :masks is a list of masks for each deserializer.\n";
        cout << "                                           :Eg: '0x0000 0x1101 0x0000 0x0000' disables all but links 0, 2 and 3 on CSI-CD intrface\n";
        cout << "-d or --disp-id <ID>                       :Display ID.\n";
        cout << "                                           Number of available displays: " << numDisplays << endl;
        for (auto i = 0; i != numDisplays; i++) {
            cout << "                                           Display ID: " << outputs[i].displayId << endl;
            if (outputs[i].enabled) {
                cout << "                                       \t\tWidth: " << outputs[i].width << endl;
                cout << "                                       \t\tHeight: " << outputs[i].height << endl;
                cout << "                                       \t\tRefresh rate: " << outputs[i].refreshRate << endl;
            }
        }
        cout << "-w or --disp-win-id <ID>                   :Display window ID. Supported values 0 to 2\n";
        cout << "-z or --disp-win-depth <val>               :Display window depth. Supported values 0 to 255\n";
        cout << "-p or --disp-win-pos 'rect'                :Display position, where rect is x0, y0, width, height\n";
        cout << "-f or --filedump-prefix 'str'              :Dump RAW file with filename prefix 'str'  when RAW output is enabled.\n";
        cout << "-r or --runfor <seconds>                   :Exit application after n seconds\n";
        cout << "-v or --verbosity <level>                  :Set verbosity. Supported values (default: 1) \n";
        cout << "                                           : " << INvSIPLTrace::LevelNone << " (None)\n";
        cout << "                                           : " << INvSIPLTrace::LevelError << " (Errors)\n";
        cout << "                                           : " << INvSIPLTrace::LevelWarning << " (Warnings and above)\n";
        cout << "                                           : " << INvSIPLTrace::LevelInfo << " (Infos and above)\n";
        cout << "                                           : " << INvSIPLTrace::LevelDebug << " (Debug and above)";
        cout << endl;
        cout << "-t or --test-config-file <file>            :Set custom platform config json file\n";
        cout << "-l or --list-configs                       :List configs from file specified via -t or --test-config-file\n";
        cout << "-i or --input-raw-files <file1[,file2]...> :Set input RAW files for simulator mode testing.\n";
        cout << "                                           :Use comma separator for multiple files.\n";
        cout << "--enableRawOutput                          :Enable the Raw output\n";
        cout << "--disableISP0Output                        :Disable the ISP0 output\n";
        cout << "--disableISP1Output                        :Disable the ISP1 output\n";
        cout << "--enableSlave                              :Enable slave mode\n";
        cout << "--showfps                                  :Show FPS (frames per second) every 2 seconds\n";
        cout << "--showmetadata                             :Show Metadata when RAW output is enabled\n";
        cout << "--plugin <type>                            :Auto Control Plugin. Supported types (default: If nito available 0 else 1)\n";
        cout << "                                           : " << NV_PLUGIN << " Nvidia AE/AWB Plugin\n";
        cout << "                                           : " << CUSTOM_PLUGIN0 << " Custom Plugin\n";
        cout << "--autorecovery                             :Recover deserializer link failure automatically\n";
        cout << "--nvsci                                    :Use NvSci for communication and synchronization\n";
        cout << "--profile                                  :Enable profiling of capture KPIs\n";
        cout << "--skipFrames <val>                         :Number of frames to skip before writing to file\n";
        cout << "--writeFrames <val>                        :Number of frames to write to file\n";
        cout << "--nito <folder>                            :Path to folder containing NITO files\n";
        cout << "--icrop 'y+h'                              :Specifies the cropping at the input in the format 'y+h'\n";
        cout << "--showEEPROM                               :Show EEPROM data\n";

        if (outputs != NULL) {
            delete [] outputs;
        }
        return;
    }

    int Parse(int argc, char* argv[])
    {
        const char* const short_options = "hc:m:d:w:z:p:f:nr:v:i:t:lR01SsMP:aCkK:W:N:O:e";
        const struct option long_options[] =
        {
            { "help",                 no_argument,       0, 'h' },
            { "platform-config",      required_argument, 0, 'c' },
            { "link-enable-masks",    required_argument, 0, 'm' },
            { "disp-id",              required_argument, 0, 'd' },
            { "disp-win-id",          required_argument, 0, 'w' },
            { "disp-win-depth",       required_argument, 0, 'z' },
            { "disp-win-pos",         required_argument, 0, 'p' },
            { "filedump-prefix",      required_argument, 0, 'f' },
            { "enable-notification",  no_argument,       0, 'n' },
            { "runfor",               required_argument, 0, 'r' },
            { "verbosity",            required_argument, 0, 'v' },
            { "input-raw-files",      required_argument, 0, 'i' },
            { "test-config-file",     required_argument, 0, 't' },
            { "list-configs",         no_argument,       0, 'l' },
            { "enableRawOutput",      no_argument,       0, 'R' },
            { "disableISP0Output",    no_argument,       0, '0' },
            { "disableISP1Output",    no_argument,       0, '1' },
            { "enableSlave",          no_argument,       0, 'S' },
            { "showfps",              no_argument,       0, 's' },
            { "showmetadata",         no_argument,       0, 'M' },
            { "plugin",               required_argument, 0, 'P' },
            { "autorecovery",         no_argument,       0, 'a' },
            { "nvsci",                no_argument,       0, 'C' },
            { "profile",              no_argument,       0, 'k' },
            { "skipFrames",           required_argument, 0, 'K' },
            { "writeFrames",          required_argument, 0, 'W' },
            { "nito",                 required_argument, 0, 'N' },
            { "icrop",                required_argument, 0, 'O' },
            { "showEEPROM",           no_argument,       0, 'e' },
            { 0,                      0,                 0,  0 }
        };

        int index = 0;
        auto bShowHelp = false;
        auto bShowConfigs = false;
        char *rawFile;

        while (1) {
            const auto getopt_ret = getopt_long(argc, argv, short_options , &long_options[0], &index);
            if (getopt_ret == -1) {
                // Done parsing all arguments.
                break;
            }

            switch (getopt_ret) {
            default: /* Unrecognized option */
            case '?': /* Unrecognized option */
                cout << "Invalid or Unrecognized command line option. Specify -h or --help for options\n";
                break;
            case 'h': /* -h or --help */
                bShowHelp = true;
                break;
            case 'c':
                sConfigName = string(optarg);
                break;
            case 'm':
            {
                char* token = std::strtok(optarg, " ");
                while(token != NULL) {
                    vMasks.push_back(stoi(token, nullptr, 16));
                    token = std::strtok(NULL, " ");
                }
            }
                break;
            case 'd':
                uDisplay = atoi(optarg);
                break;
            case 'w':
                uDispWinID = atoi(optarg);
                break;
            case 'z':
                uDispWinDepth = atoi(optarg);
                break;
            case 'p':
                int32_t x, y, w, h;
                sscanf(optarg, "%d %d %d %d", &x, &y, &w, &h);
                oDispRect.x0 = x;
                oDispRect.y0 = y;
                oDispRect.x1 = oDispRect.x0 + w;
                oDispRect.y1 = oDispRect.y0 + h;
                bRectSet = true;
                break;
            case 'f':
                sPrefix = string(optarg);
                break;
            case 'r':
                uRunDurationSec = atoi(optarg);
                break;
            case 'v':
                verbosity = atoi(optarg);
                break;
            case 'i':
                rawFile = strtok(optarg, ",");
                while (rawFile != NULL) {
                    vInputRawFiles.push_back(string(rawFile));
                    rawFile = strtok (NULL, ",");
                }
                break;
            case 't':
                sTestConfigFile = string(optarg);
                break;
            case 'l':
                bShowConfigs = true;
                break;
            case 'R':
                bDisableRaw = false;
                break;
            case '0':
                bDisableISP0 = true;
                break;
            case '1':
                bDisableISP1 = true;
                break;
            case 'S':
                bEnableSlave = true;
                break;
            case 's':
                bShowFPS = true;
                break;
            case 'M':
                bShowMetadata = true;
                break;
            case 'P':
                autoPlugin = (PluginType) atoi(optarg);
                break;
            case 'a':
                bAutoRecovery = true;
                break;
            case 'C':
                bNvSci = true;
                break;
            case 'k':
                bEnableProfiling = true;
                break;
            case 'K':
                uNumSkipFrames = atoi(optarg);
                break;
            case 'W':
                uNumWriteFrames = atoi(optarg);
                break;
            case 'N':
                sNitoFolderPath = string(optarg);
                break;
            case 'O':
                sscanf(optarg, "%d+%d", &uIspInputCropY, &uIspInputCropH);
                bIspInputCropEnable = true;
                break;
            case 'e':
                bShowEEPROM = true;
                break;
            }
        }

        if (bShowHelp) {
            ShowUsage();
            return -1;
        }

        if (bShowConfigs) {
            // User just wants to list available configs
            ShowConfigs(sTestConfigFile);
            return -1;
        }

        // Check for bad arguments
        if (sConfigName == "") {
            cout << "No platform configuration specified.\n";
            return -1;
        }

        if (bDisableRaw && bDisableISP0 && bDisableISP1) {
            cout << "At-least one output must be enabled\n";
            return -1;
        }

        if ((sPrefix == "") && (uNumSkipFrames != 0u)) {
            cout << "skipFrames is only applicable when file dump is enabled\n";
            return -1;
        }

        if ((sPrefix == "") && (uNumWriteFrames != -1u)) {
            cout << "writeFrames is only applicable when file dump is enabled\n";
            return -1;
        }

        return 0;
    }

    void PrintArgs() const
    {
        if (sConfigName != "") {
            cout << "Platform configuration name: " << sConfigName << endl;
        }
        if (sTestConfigFile != "") {
            cout << "Platform configuration file: " << sTestConfigFile << endl;
        }
        if (uDisplay != -1u) {
            cout << "Display ID:                  " << uDisplay << endl;
            cout << "Display window ID:           " << uDispWinID << endl;
            cout << "Display window Depth:        " << uDispWinDepth << endl;
            uint32_t x = oDispRect.x0, y = oDispRect.y0;
            uint32_t w = oDispRect.x1 - x, h = oDispRect.y1 - y;
            cout << "Display window position:     " << x << " " << y << " " << w << " " << h << endl;
        }
        if (sPrefix != "") {
            cout << "File dump prefix:            " << sPrefix << endl;
        }
        if (uRunDurationSec != -1) {
            cout << "Running for " << uRunDurationSec << " seconds\n";
        }
        cout << "Verbosity level: " << verbosity << endl;
        if (!vInputRawFiles.empty()) {
            cout << "Input raw file(s):           ";
            for (auto s : vInputRawFiles) {
                cout << s << " ";
            }
            cout << endl;
        }
        if (bDisableRaw) {
            cout << "Raw output: disabled" << endl;
        } else {
            cout << "Raw output: enabled" << endl;
        }

        if (bDisableISP0) {
            cout << "ISP0 output: disabled" << endl;
        } else {
            cout << "ISP0 output: enabled" << endl;
        }

        if (bDisableISP1) {
            cout << "ISP1 output: disabled" << endl;
        } else {
            cout << "ISP1 output: enabled" << endl;
        }

        if (bShowFPS) {
            cout << "Enabled FPS logging" << endl;
        } else {
            cout << "Disabled FPS logging" << endl;
        }

        if (bShowMetadata) {
            cout << "Enabled Metadata logging" << endl;
        } else {
            cout << "Disabled Metadata logging" << endl;
        }

        if (bEnableSlave) {
            cout << "Enabled Slave mode" << endl;
        } else {
            cout << "Disabled Slave mode" << endl;
        }

        if (bAutoRecovery) {
            cout << "Enabled automatic recovery" << endl;
        } else {
            cout << "Disabled automatic recovery" << endl;
        }

        if (bNvSci) {
            cout << "Enabled NvSci" << endl;
        } else {
            cout << "Disabled NvSci" << endl;
        }

        if (bEnableProfiling) {
            cout << "Enabled profiling" << endl;
        } else {
            cout << "Disabled profiling" << endl;
        }

        if (uNumSkipFrames != 0u) {
            cout << "Number of frames to skip: " << uNumSkipFrames << endl;
        }

        if (uNumWriteFrames != -1u) {
            cout << "Number of frames to write: " << uNumWriteFrames << endl;
        }

        if (sNitoFolderPath != "") {
            cout << "NITO folder path: " << sNitoFolderPath << endl;
        }
    }
};

#endif //CCMDPARSER_HPP
