/*
 * Copyright (c) 2013-2019, 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.
*/

#include <stdlib.h>

#include "config_parser.h"
#include "log_utils.h"

static NvMediaStatus GetParamIndex(ConfigParamsMap *paramsMap, char *paramName, unsigned int *index)
{
    int i = 0;

    while(paramsMap[i].paramName != NULL) {
        if (strcasecmp(paramsMap[i].paramName, paramName) == 0) {
            *index = i;
            return NVMEDIA_STATUS_OK;
        } else {
            i++;
        }
    }

    return NVMEDIA_STATUS_BAD_PARAMETER;
}

NvMediaStatus ConfigParser_GetSectionIndexByName(SectionMap *sectionsMap, char *sectionName, unsigned int *index)
{
    unsigned int i = 0;

    while(sectionsMap[i].secType != SECTION_NONE) {
        if(strcmp(sectionsMap[i].name, sectionName) == 0) {
            *index = i;
            return NVMEDIA_STATUS_OK;
        } else {
            i++;
        }
    }

    return NVMEDIA_STATUS_BAD_PARAMETER;
}

NvMediaStatus ConfigParser_GetSectionIndexByType(SectionMap *sectionsMap, SectionType sectionType, unsigned int *index)
{
    unsigned int i = 0;

    while(sectionsMap[i].secType != SECTION_NONE) {
        if(sectionsMap[i].secType == sectionType) {
            *index = i;
            return NVMEDIA_STATUS_OK;
        } else {
            i++;
        }
    }

    *index = i;
    return NVMEDIA_STATUS_OK;
}

static NvMediaStatus GetFileContent(char *filename, char **fileContentOut)
{
    FILE *file;
    char *fileCotent;
    long fileSize;

    file = fopen(filename, "r");
    if(file == NULL) {
        LOG_ERR("Parser_GetFileContent: Cannot open configuration file %s\n", filename);
        return NVMEDIA_STATUS_BAD_PARAMETER;
    }

    if (fseek(file, 0, SEEK_END) != 0) {
        LOG_ERR("Parser_GetFileContent: Cannot fseek in configuration file %s\n", filename);
        return NVMEDIA_STATUS_ERROR;
    }

    fileSize = ftell(file);
    if(fileSize < 0 || fileSize > 150000) {
        LOG_ERR("Parser_GetFileContent: Unreasonable Filesize %ld encountered for file %s\n", fileSize, filename);
        return NVMEDIA_STATUS_ERROR;
    }

    if(fseek (file, 0, SEEK_SET) != 0) {
        LOG_ERR("Parser_GetFileContent: Cannot fseek in configuration file %s\n", filename);
        return NVMEDIA_STATUS_ERROR;
    }

    fileCotent = malloc(fileSize + 1);
    if(fileCotent == NULL) {
        LOG_ERR("Parser_GetFileContent: Failed allocating buffer for file Content\n");
        return NVMEDIA_STATUS_OUT_OF_MEMORY;
    }

    fileSize = (long)fread(fileCotent, 1, fileSize, file);
    fileCotent[fileSize] = '\0';
    *fileContentOut = fileCotent;

    fclose(file);

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus ConfigParser_ParseFile(ConfigParamsMap *paramsMap, unsigned int numParams, SectionMap *sectionsMap, char *fileName)
{
    char *items[MAX_ITEMS_TO_PARSE] = {NULL};
    int intValue, itemsCount = 0, i = 0, sectionIndex = 0;
    double doubleValue;
    float floatValue;
    unsigned int currItemIndex, uintValue, sectionId = 0, currSectionId = 0, charValue, paramDefaultLength;
    unsigned short ushortValue;
    short shortValue;
    unsigned long long ullValue;
    NvMediaBool isInString = NVMEDIA_FALSE, isInItem = NVMEDIA_FALSE;
    char *buffer, *bufferEnd, *param, *pParamLength;
    char sectionName[100];
    char currDigit;
    char *configContentBuf = NULL;
    unsigned int numSetsInSection = 0;

    if(GetFileContent(fileName, &configContentBuf) != NVMEDIA_STATUS_OK) {
        LOG_ERR("ConfigParser_ParseFile: Failed reading file %s", fileName);
        return NVMEDIA_STATUS_ERROR;
    }

    buffer = configContentBuf;
    bufferEnd = &configContentBuf[strlen(configContentBuf)];

    // Stage one: Create items mapping in the content using "items" pointers array. For each parameter we have 3 items: param name, '=' char and the param value.
    while(buffer < bufferEnd) {
        if(itemsCount >= MAX_ITEMS_TO_PARSE) {
            LOG_WARN("ConfigParser_ParseFile: Number of items in configuration file exceeded the maximum allowed (%d). Only %d items will be parsed.\n",
                        MAX_ITEMS_TO_PARSE, MAX_ITEMS_TO_PARSE);
            itemsCount = MAX_ITEMS_TO_PARSE;
            break;
        }
        switch(*buffer) {
            // Carriage return
            case 13:
                ++buffer;
                break;
            case '#':
                *buffer = '\0';                           // Replace '#' with '\0' in case of comment immediately following integer or string
                while(*buffer != '\n' && buffer < bufferEnd) { // Skip till EOL or EOF
                    ++buffer;
                }
                isInString = NVMEDIA_FALSE;
                isInItem = NVMEDIA_FALSE;
                break;
            case '\n':
                isInItem = NVMEDIA_FALSE;
                isInString = NVMEDIA_FALSE;
                *buffer++='\0';
                break;
            case ' ':
            case '\t':                          // Skip whitespace, leave state unchanged
                if(isInString)
                    buffer++;
                else {                          // Terminate non-strings once whitespace is found
                    *buffer++ = '\0';
                    isInItem = NVMEDIA_FALSE;
                }
                break;
            case '"':                           // Begin/End of String
                *buffer++ = '\0';
                if(!isInString) {
                    items[itemsCount++] = buffer;
                    isInItem = ~isInItem;
                } else {
                    isInItem = NVMEDIA_FALSE;
                }
                isInString = ~isInString;           // Toggle
                break;
            case '[':
                *(buffer++) = '\0';
                items[itemsCount++] = buffer;
                while(*buffer != ' ' && *buffer != '\n' && buffer < bufferEnd) { // Skip till whitespace (after which is located the parsed section number) or EOL or EOF
                    sectionName[i++] = *(buffer++);
                }
                sectionName[i] = '\0';
                i = 0;
                while(*buffer == ' ') {
                    *(buffer++) = '\0';
                }
                items[itemsCount++] = buffer;
                while(*buffer != ']' && *buffer != '\n' && buffer < bufferEnd) { // Read the section number
                    currDigit = *buffer;
                    sectionIndex = sectionIndex * 10 + (currDigit - '0');
                    buffer++;
                }
                *(buffer++) = '\0';
                sectionIndex--;
                if(ConfigParser_GetSectionIndexByName(sectionsMap, sectionName, &sectionId) != NVMEDIA_STATUS_OK) {
                    LOG_ERR("ConfigParser_ParseFile: SectionName couldn't be found in section map: '%s'.\n", sectionName);
                }
                numSetsInSection++;
                sectionsMap[sectionId].lastSectionIndex = sectionIndex;
                sectionIndex = 0;
                isInString = NVMEDIA_FALSE;
                isInItem = NVMEDIA_FALSE;
                break;
            default:
                if(!isInItem) {
                    items[itemsCount++] = buffer;
                    isInItem = ~isInItem;
                }
                buffer++;
        }
    }

    itemsCount--;

    if(numSetsInSection > numParams) {
        LOG_ERR("%s: Not enough buffers allocated for parsing. Number of sets allocated: %d. Number of sets in config file: %d \n",
                __func__, numParams, numSetsInSection);
        if(configContentBuf) {
            free(configContentBuf);
        }
        return NVMEDIA_STATUS_ERROR;
    }

    // Stage 2: Go through the list of items and save their values in parameters map
    for(i = 0; i < itemsCount; i += 3) {
        if(ConfigParser_GetSectionIndexByName(sectionsMap, items[i], &currItemIndex) == NVMEDIA_STATUS_OK) {
            currSectionId = atoi(items[i + 1]);
            currSectionId--;
            LOG_DBG("ConfigParser_ParseFile: Parsing section %s index %d\n", items[i], currSectionId);
            i -= 1;
            continue;
        }

        if(GetParamIndex(paramsMap, items[i], &currItemIndex) != NVMEDIA_STATUS_OK) {
            LOG_WARN("ConfigParser_ParseFile: Parameter Name '%s' is not recognized. Dismissing this parameter.\n", items[i]);
            continue;
        }

        if(strcmp("=", items[i + 1])) {
            LOG_ERR("ConfigParser_ParseFile: '=' expected as the second token in each line. Error caught while parsing parameter '%s'.\n", items[i]);
            i -= 2;
            continue;
        }

        if(ConfigParser_GetSectionIndexByType(sectionsMap, paramsMap[currItemIndex].sectionType, &sectionId) != NVMEDIA_STATUS_OK) {
            LOG_ERR("ConfigParser_ParseFile: Section index couldn't be found in section map by type. Param Name: '%s'.\n", paramsMap[currItemIndex].paramName);
        }

        if(sectionsMap[sectionId].lastSectionIndex == 0) {
            // Param is not part of a collection or collection includes only one item
            currSectionId = 0;
        }

        param = (char *)paramsMap[currItemIndex].mappedLocation + currSectionId * sectionsMap[sectionId].sizeOfStruct;
        pParamLength = (char *)paramsMap[currItemIndex].stringLengthAddr + currSectionId * sectionsMap[sectionId].sizeOfStruct;
        paramDefaultLength = paramsMap[currItemIndex].stringLength;

        // Interpret the Value
        LOG_DBG("ConfigParser_ParseFile: Interpreting parameter %s\n", items[i]);
        switch(paramsMap[currItemIndex].type) {
            case TYPE_INT:
                if(sscanf(items[i + 2], "%d", &intValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected numerical value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(int *)(void *)param = intValue;
                break;
            case TYPE_UINT:
                if(sscanf(items[i + 2], "%u", &uintValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected numerical value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(unsigned int *)(void *)param = uintValue;
                break;
            case TYPE_UINT_HEX:
                if(sscanf(items[i + 2], "%x", &uintValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected unsigned char value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(unsigned int *)(void *)param = uintValue;
                break;
            case TYPE_CHAR_ARR:
                if(items[i + 2] == NULL)
                    memset(param, 0, (pParamLength != NULL && *pParamLength != 0) ? *pParamLength : paramDefaultLength);
                else {
                    strncpy(param, items[i + 2], paramsMap[currItemIndex].stringLength);
                    param[strlen(items[i + 2])] = '\0';
                }
                break;
            case TYPE_DOUBLE:
                if(sscanf(items[i + 2], "%lf", &doubleValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected double value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(double *)(void *)param = doubleValue;
                break;
            case TYPE_FLOAT:
                if(sscanf(items[i + 2], "%f", &floatValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected double value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(float *)(void *)param = floatValue;
                break;
            case TYPE_UCHAR:
                if(sscanf(items[i + 2], "%u", &charValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected unsigned char value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(unsigned char *)(void *)param = charValue;
                break;
            case TYPE_USHORT:
                if(sscanf(items[i + 2], "%hu", &ushortValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected unsigned short value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(unsigned short *)(void *)param = ushortValue;
                break;
            case TYPE_SHORT:
                if(sscanf(items[i + 2], "%hd", &shortValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected short value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(short *)(void *)param = shortValue;
                break;
            case TYPE_UCHAR_ARR:
                if(items[i + 2] == NULL)
                    memset(param, 0, (pParamLength != NULL && *pParamLength != 0) ? *pParamLength : paramDefaultLength);
                else {
                    strncpy(param, items[i + 2], paramsMap[currItemIndex].stringLength);
                    param[strlen(items[i + 2])] = '\0';
                }
                break;
            case TYPE_ULLONG:
                if(sscanf(items[i + 2], "%llu", &ullValue) != 1) {
                    LOG_ERR("ConfigParser_ParseFile: Expected numerical value for Parameter %s, found value '%s'\n", items[i], items[i + 2]);
                }
                *(unsigned long long *)(void *)param = ullValue;
                break;
            default:
                LOG_ERR("ConfigParser_ParseFile: Encountered unknown value type in the map\n");
        }
    }

    if (configContentBuf)
        free(configContentBuf);

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus ConfigParser_InitParamsMap(ConfigParamsMap *paramsMap)
{
    int i = 0;

    while(paramsMap[i].paramName != NULL) {
        if (paramsMap[i].mappedLocation == NULL) {
           i++;
           continue;
        }

        switch(paramsMap[i].type) {
            case TYPE_UINT:
            case TYPE_UINT_HEX:
                *(unsigned int *)(paramsMap[i].mappedLocation) = (unsigned int)paramsMap[i].defaultValue;
                break;
            case TYPE_INT:
                *(int *)(paramsMap[i].mappedLocation) = (int)paramsMap[i].defaultValue;
                break;
            case TYPE_DOUBLE:
                *(double *)(paramsMap[i].mappedLocation) = (double)paramsMap[i].defaultValue;
                break;
            case TYPE_FLOAT:
                *(float *)(paramsMap[i].mappedLocation) = (float)paramsMap[i].defaultValue;
                break;
            case TYPE_UCHAR:
                *(unsigned char *)(paramsMap[i].mappedLocation) = (NvMediaBool)paramsMap[i].defaultValue;
                break;
            case TYPE_USHORT:
                *(unsigned short *)(paramsMap[i].mappedLocation) = (unsigned short)paramsMap[i].defaultValue;
                break;
            case TYPE_SHORT:
                *(short *)(paramsMap[i].mappedLocation) = (short)paramsMap[i].defaultValue;
                break;
            case TYPE_ULLONG:
                *(unsigned long long *)(paramsMap[i].mappedLocation) = (unsigned long long)paramsMap[i].defaultValue;
                break;
            case TYPE_CHAR_ARR:
            case TYPE_UCHAR_ARR:
            default:
                break;
        }
        i++;
    }

    return NVMEDIA_STATUS_OK;
}

NvMediaStatus ConfigParser_ValidateParams(ConfigParamsMap *paramsMap, SectionMap *sectionsMap)
{
    NvMediaStatus status = NVMEDIA_STATUS_OK;
    unsigned int sectionId = 0, i = 0, j;
    char *param;

    while(paramsMap[i].paramName != NULL) {
        if(ConfigParser_GetSectionIndexByType(sectionsMap, paramsMap[i].sectionType, &sectionId) != NVMEDIA_STATUS_OK) {
            LOG_ERR("ConfigParser_ValidateParams: Section index couldn't be found in section map. Param Name: '%s'.\n", paramsMap[i].paramName);
        }

        for(j = 0; j <= sectionsMap[sectionId].lastSectionIndex; j++) {
            if(paramsMap[i].paramLimits == 1 || paramsMap[i].paramLimits == 2) {
                param = (char *)paramsMap[i].mappedLocation + j * sectionsMap[sectionId].sizeOfStruct;
                if (param == NULL) {
                    i++;
                    continue;
                }
                switch (paramsMap[i].type) {
                    case TYPE_UINT:
                    case TYPE_UINT_HEX:
                        if(*(unsigned int *)(void *)param < (unsigned int)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(unsigned int *)(void *)param > (unsigned int)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_DOUBLE:
                        if(*(double *)(void *)param < (double)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(double *)(void *)param > (double)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_FLOAT:
                        if(*(float *)(void *)param < (float)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(float *)(void *)param > (float)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_INT:
                        if(*(int *)(void *)param < (int)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(int *)(void *)param > (int)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_USHORT:
                        if(*(unsigned short *)(void *)param < (unsigned short)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(unsigned short *)(void *)param > (unsigned short)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_SHORT:
                        if(*(short *)(void *)param < (short)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(short *)(void *)param > (short)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    case TYPE_ULLONG:
                        if(*(unsigned long long *)(void *)param < (unsigned long long)paramsMap[i].minLimit ||
                            (paramsMap[i].paramLimits == 2 && *(unsigned long long *)(void *)param > (unsigned long long)paramsMap[i].maxLimit )) {
                            LOG_ERR("ConfigParser_ValidateParams: Error in input parameter %s\n", paramsMap[i].paramName);
                            LOG_ERR("Check configuration file for parameter limits\n");
                            status = NVMEDIA_STATUS_BAD_PARAMETER;
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        i++;
    }

    return status;
}

NvMediaStatus ConfigParser_DisplayParams(ConfigParamsMap *pParamsMap, SectionMap *pSectionsMap)
{
    unsigned int i = 0, j, sectionId = 0;
    char *param;

    while(pParamsMap[i].paramName != NULL) {
        if(ConfigParser_GetSectionIndexByType(pSectionsMap, pParamsMap[i].sectionType, &sectionId) != NVMEDIA_STATUS_OK) {
            LOG_ERR("ConfigParser_DisplayParams: Section index couldn't be found in section map by type. Param Name: '%s'.\n", pParamsMap[i].paramName);
        }

        for(j = 0; j <= pSectionsMap[sectionId].lastSectionIndex; j++) {
            param = (char *)pParamsMap[i].mappedLocation + j * pSectionsMap[sectionId].sizeOfStruct;
            if (param == NULL) {
                i++;
                continue;
            }

            switch(pParamsMap[i].type) {
                case TYPE_UINT:
                    LOG_MSG("(%d) %s = %u\n", j, pParamsMap[i].paramName, *(unsigned int *)(void *)param);
                    break;
                case TYPE_DOUBLE:
                    LOG_MSG("(%d) %s = %.2lf\n", j, pParamsMap[i].paramName, *(double *)(void *)param);
                    break;
                case TYPE_FLOAT:
                    LOG_MSG("(%d) %s = %.2f\n", j, pParamsMap[i].paramName, *(float *)(void *)param);
                    break;
                case TYPE_UCHAR:
                    LOG_MSG("(%d) %s = %d\n", j, pParamsMap[i].paramName, *(unsigned char *)(void *)param);
                    break;
                case TYPE_USHORT:
                    LOG_MSG("(%d) %s = %hu\n", j, pParamsMap[i].paramName, *(unsigned short *)(void *)param);
                    break;
                case TYPE_SHORT:
                    LOG_MSG("(%d) %s = %hd\n", j, pParamsMap[i].paramName, *(short *)(void *)param);
                    break;
                case TYPE_ULLONG:
                    LOG_MSG("(%d) %s = %llu\n", j, pParamsMap[i].paramName, *(unsigned long long *)(void *)param);
                    break;
                case TYPE_CHAR_ARR:
                    LOG_MSG("(%d) %s = ""%s""\n", j, pParamsMap[i].paramName, param);
                    break;
                case TYPE_UCHAR_ARR:
                    LOG_MSG("(%d) %s = ""%s""\n", j, pParamsMap[i].paramName, (unsigned char *)(void *)param);
                    break;
                case TYPE_INT:
                    LOG_MSG("(%d) %s = %d\n", j, pParamsMap[i].paramName, *(int *)(void *)param);
                    break;
                case TYPE_UINT_HEX:
                    LOG_MSG("(%d) %s = %x\n", j, pParamsMap[i].paramName, *(unsigned int *)(void *)param);
                    break;
                default:
                    // Do nothing
                    break;
            }
        }
        i++;
    }

    return NVMEDIA_STATUS_OK;
}
