/*
 *  config_file_parser.c
 *
 *  Copyright 2014-2024 Michael Zillgith
 *
 *  This file is part of libIEC61850.
 *
 *  libIEC61850 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  libIEC61850 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with libIEC61850.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  See COPYING file for the complete license text.
 */

#include "iec61850_server.h"
#include "iec61850_dynamic_model.h"
#include "iec61850_config_file_parser.h"

#include "libiec61850_platform_includes.h"
#include "stack_config.h"

#include <ctype.h>

#define READ_BUFFER_MAX_SIZE 1024

static int
readLine(FileHandle fileHandle, uint8_t* buffer, int maxSize)
{
    int bytesRead = 0;
    int bufPos = 0;

    int fileReadResult = 1;

    /* eat up leading cr or lf */
    while (fileReadResult > 0)
    {
        if (bytesRead == maxSize)
            break;

        fileReadResult = FileSystem_readFile(fileHandle, buffer + bufPos, 1);

        if (fileReadResult == 1)
        {
            if (!((buffer[bufPos] == '\n') || (buffer[bufPos] == '\r'))) {
                bufPos++;
                bytesRead++;
                break;
            }
        }
    }

    if (fileReadResult > 0)
    {
        while (fileReadResult > 0)
        {
            if (bytesRead == maxSize)
                break;

            fileReadResult = FileSystem_readFile(fileHandle, buffer + bufPos, 1);

            if (fileReadResult == 1)
            {
                if ((buffer[bufPos] == '\n') || (buffer[bufPos] == '\r'))
                    break;
                else {
                    bufPos++;
                    bytesRead++;
                }
            }
        }
    }

    return bytesRead;
}

static void
terminateString(char* string, char ch)
{
    int index = 0;

    while (string[index] != 0)
    {
        if (string[index] == ch)
        {
            string[index] = 0;
            break;
        }

        index++;
    }
}

IedModel*
ConfigFileParser_createModelFromConfigFileEx(const char* filename)
{
    FileHandle configFile = FileSystem_openFile((char*)filename, false);

    if (configFile == NULL)
    {
        if (DEBUG_IED_SERVER)
            printf("IED_SERVER: Error opening config file!\n");
        return NULL;
    }

    IedModel* model = ConfigFileParser_createModelFromConfigFile(configFile);

    FileSystem_closeFile(configFile);

    return model;
}

static bool
setValue(char* lineBuffer, DataAttribute* dataAttribute)
{
    char* valueIndicator = strchr((char*) lineBuffer, '=');

    if (valueIndicator)
    {
        switch (dataAttribute->type) {
        case IEC61850_UNICODE_STRING_255:
            {
                char* stringStart = valueIndicator + 2;
                terminateString(stringStart, '"');
                dataAttribute->mmsValue = MmsValue_newMmsString(stringStart);
            }
            break;

        case IEC61850_VISIBLE_STRING_255:
        case IEC61850_VISIBLE_STRING_129:
        case IEC61850_VISIBLE_STRING_65:
        case IEC61850_VISIBLE_STRING_64:
        case IEC61850_VISIBLE_STRING_32:
        case IEC61850_CURRENCY:
            {
                char* stringStart = valueIndicator + 2;
                terminateString(stringStart, '"');
                dataAttribute->mmsValue = MmsValue_newVisibleString(stringStart);
            }
            break;

        case IEC61850_INT8:
        case IEC61850_INT16:
        case IEC61850_INT32:
        case IEC61850_INT64:
        case IEC61850_INT128:
        case IEC61850_ENUMERATED:
            {
                int32_t intValue;
                if (sscanf(valueIndicator + 1, "%i", &intValue) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newIntegerFromInt32(intValue);
            }
            break;

        case IEC61850_INT8U:
        case IEC61850_INT16U:
        case IEC61850_INT24U:
        case IEC61850_INT32U:
            {
                uint32_t uintValue;
                if (sscanf(valueIndicator + 1, "%u", &uintValue) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newUnsignedFromUint32(uintValue);
            }
            break;

        case IEC61850_FLOAT32:
            {
                float floatValue;
                if (sscanf(valueIndicator + 1, "%f", &floatValue) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newFloat(floatValue);
            }
            break;

        case IEC61850_FLOAT64:
            {
                double doubleValue;
                if (sscanf(valueIndicator + 1, "%lf", &doubleValue) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newDouble(doubleValue);
            }
            break;

        case IEC61850_BOOLEAN:
            {
                int boolean;
                if (sscanf(valueIndicator + 1, "%i", &boolean) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newBoolean((bool) boolean);
            }
            break;

        case IEC61850_OPTFLDS:
            {
                int value;
                if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newBitString(-10);
                MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
            }
            break;

        case IEC61850_TRGOPS:
            {
                int value;
                if (sscanf(valueIndicator + 1, "%i", &value) != 1) goto exit_error;
                dataAttribute->mmsValue = MmsValue_newBitString(-6);
                MmsValue_setBitStringFromIntegerBigEndian(dataAttribute->mmsValue, value);
            }
            break;

        default:
            break;

        }
    }

    return true;

exit_error:
    return false;
}

IedModel*
ConfigFileParser_createModelFromConfigFile(FileHandle fileHandle)
{
    uint8_t* lineBuffer = (uint8_t*)GLOBAL_MALLOC(READ_BUFFER_MAX_SIZE);

    if (lineBuffer == NULL)
        return NULL;

    int bytesRead = 1;

    bool stateInModel = false;
    int indendation = 0;
    bool inArray = false;
    bool inArrayElement = false;

    IedModel* model = NULL;
    LogicalDevice* currentLD = NULL;
    LogicalNode* currentLN = NULL;
    ModelNode* currentModelNode = NULL;
    ModelNode* currentArrayNode = NULL;
    DataSet* currentDataSet = NULL;
    GSEControlBlock* currentGoCB = NULL;
    SVControlBlock* currentSMVCB = NULL;

    char nameString[130];
    char nameString2[130];
    char nameString3[130];

    int currentLine = 0;

    while (bytesRead > 0)
    {
        bytesRead = readLine(fileHandle, lineBuffer, READ_BUFFER_MAX_SIZE);

        currentLine++;

        if (bytesRead > 0)
        {
            lineBuffer[bytesRead] = 0;

            /* trim trailing spaces */
            while (bytesRead > 1)
            {
                bytesRead--;

                if (isspace(lineBuffer[bytesRead]))
                {
                    lineBuffer[bytesRead] = 0;
                }
                else
                {
                    break;
                }
            }

            if (stateInModel)
            {
                if (StringUtils_startsWith((char*)lineBuffer, "}"))
                {
                    if (indendation == 1)
                    {
                        stateInModel = false;
                        indendation = 0;
                    }
                    else if (indendation == 2)
                    {
                        indendation = 1;
                    }
                    else if (indendation == 3)
                    {
                        indendation = 2;
                    }
                    else if (indendation == 4)
                    {
                        indendation = 3;
                    }
                    else if (indendation > 4)
                    {
                        if (inArrayElement && currentModelNode->parent == currentArrayNode)
                        {
                            inArrayElement = false;
                        }
                        else
                        {
                            indendation--;
                        }

                        if (inArray && currentModelNode == currentArrayNode)
                        {
                            inArray = false;
                        }

                        currentModelNode = currentModelNode->parent;
                    }
                }
                else if (indendation == 1)
                {
                    if (StringUtils_startsWith((char*)lineBuffer, "LD"))
                    {
                        indendation = 2;

                        char ldName[65];
                        ldName[0] = 0;

                        if (sscanf((char*)lineBuffer, "LD(%129s %64s)", nameString, ldName) < 1)
                            goto exit_error;

                        terminateString(nameString, ')');

                        if (ldName[0] != 0)
                        {
                            terminateString(ldName, ')');

                            currentLD = LogicalDevice_createEx(nameString, model, ldName);
                        }
                        else
                        {
                            currentLD = LogicalDevice_create(nameString, model);
                        }
                    }
                    else
                        goto exit_error;
                }
                else if (indendation == 2)
                {
                    if (StringUtils_startsWith((char*) lineBuffer, "LN"))
                    {
                        indendation = 3;

                        if (sscanf((char*) lineBuffer, "LN(%129s)", nameString) < 1)
                            goto exit_error;

                        terminateString(nameString, ')');

                        currentLN = LogicalNode_create(nameString, currentLD);
                    }
                    else
                        goto exit_error;
                }
                else if (indendation == 3)
                {
                    if (StringUtils_startsWith((char*)lineBuffer, "DO"))
                    {
                        indendation = 4;

                        int arrayElements = 0;

                        if (sscanf((char*)lineBuffer, "DO(%129s %i)", nameString, &arrayElements) != 2)
                        {
                            goto exit_error;
                        }

                        currentModelNode =
                            (ModelNode*)DataObject_create(nameString, (ModelNode*)currentLN, arrayElements);
                    }
                    else if (StringUtils_startsWith((char*)lineBuffer, "DS"))
                    {
                        indendation = 4;

                        if (sscanf((char*)lineBuffer, "DS(%129s)", nameString) != 1)
                        {
                            goto exit_error;
                        }

                        terminateString(nameString, ')');

                        currentDataSet = DataSet_create(nameString, currentLN);
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "RC"))
                    {
                        int isBuffered;
                        uint32_t confRef;
                        int trgOps;
                        int options;
                        uint32_t bufTm;
                        uint32_t intgPd;

                        int matchedItems = sscanf((char*) lineBuffer, "RC(%129s %129s %i %129s %u %i %i %u %u)",
                                nameString, nameString2, &isBuffered, nameString3, &confRef,
                                &trgOps, &options, &bufTm, &intgPd);

                        if (matchedItems < 9) goto exit_error;

                        char* rptId = NULL;

                        if (strcmp(nameString2, "-") != 0)
                            rptId = nameString2;

                        char* dataSetName = NULL;

                        if (strcmp(nameString3, "-") != 0)
                            dataSetName = nameString3;

                        ReportControlBlock_create(nameString, currentLN, rptId,
                                (bool) isBuffered, dataSetName, confRef, trgOps, options, bufTm, intgPd);
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "LC"))
                    {
                        uint32_t trgOps;
                        uint32_t intgPd;
                        int logEna;
                        int withReasonCode;

                        int matchedItems = sscanf((char*) lineBuffer, "LC(%129s %129s %129s %u %u %i %i)",
                                nameString, nameString2, nameString3, &trgOps, &intgPd, &logEna, &withReasonCode);

                        if (matchedItems < 7) goto exit_error;

                        char* dataSet = NULL;
                        if (strcmp(nameString2, "-") != 0)
                            dataSet = nameString2;

                        char* logRef = NULL;
                        if (strcmp(nameString3, "-") != 0)
                            logRef = nameString3;

                        LogControlBlock_create(nameString, currentLN, dataSet, logRef, trgOps, intgPd, logEna, withReasonCode);
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "LOG"))
                    {
                        int matchedItems = sscanf((char*) lineBuffer, "LOG(%129s)", nameString);

                        if (matchedItems < 1) goto exit_error;

                        /* remove trailing ')' character */
                        strtok(nameString, ")");

                        Log_create(nameString, currentLN);
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "GC"))
                    {
                        uint32_t confRef;
                        int fixedOffs;
                        int minTime = -1;
                        int maxTime = -1;

                        int matchedItems = sscanf((char*) lineBuffer, "GC(%129s %129s %129s %u %i %i %i)",
                                nameString, nameString2, nameString3, &confRef, &fixedOffs, &minTime, &maxTime);

                        if (matchedItems < 5) goto exit_error;

                        currentGoCB = GSEControlBlock_create(nameString, currentLN, nameString2,
                                    nameString3, confRef, fixedOffs, minTime, maxTime);

                        if (StringUtils_endsWith((char*) lineBuffer, "{"))
                        {
                            indendation = 4;
                        }
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "SMVC"))
                    {
                        uint32_t confRev;
                        int smpMod;
                        int smpRate;
                        int optFlds;
                        int isUnicast;

                        int matchedItems = sscanf((char*) lineBuffer, "SMVC(%129s %129s %129s %u %i %i %i %i)",
                                nameString, nameString2, nameString3, &confRev, &smpMod, &smpRate, &optFlds, &isUnicast);

                        if (matchedItems < 5) goto exit_error;

                        currentSMVCB = SVControlBlock_create(nameString, currentLN, nameString2, nameString3, confRev, smpMod, smpRate, optFlds, (bool) isUnicast);

                        if (StringUtils_endsWith((char*) lineBuffer, "{"))
                        {
                            indendation = 4;
                        }
                    }
#if (CONFIG_IEC61850_SETTING_GROUPS == 1)
                    else if (StringUtils_startsWith((char*) lineBuffer, "SG"))
                    {
                        if (strcmp(currentLN->name, "LLN0") != 0)
                        {
                            if (DEBUG_IED_SERVER)
                                printf("IED_SERVER: Setting group control is not defined in LLN0\n");

                            goto exit_error;
                        }

                        int actSG;
                        int numOfSGs;

                        int matchedItems = sscanf((char*) lineBuffer, "SG(%i %i)", &actSG, &numOfSGs);

                        if (matchedItems < 2)
                            goto exit_error;

                        SettingGroupControlBlock_create(currentLN, actSG, numOfSGs);
                    }
#endif /* (CONFIG_IEC61850_SETTING_GROUPS == 1) */
                    else
                    {
                        if (DEBUG_IED_SERVER)
                            printf("IED_SERVER: Unknown identifier (%s)\n", lineBuffer);

                        goto exit_error;
                    }
                }
                else if (indendation > 3)
                {
                    if (StringUtils_startsWith((char*) lineBuffer, "DO"))
                    {
                        indendation++;

                        int arrayElements = 0;

                        int matchedItems = sscanf((char*) lineBuffer, "DO(%129s %i)", nameString, &arrayElements);

                        if (matchedItems != 2) goto exit_error;

                        currentModelNode = (ModelNode*) DataObject_create(nameString, currentModelNode, arrayElements);

                        if (arrayElements > 0)
                        {
                            inArray = true;
                            currentArrayNode = currentModelNode;
                        }
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "["))
                    {
                        if (inArray == false)
                        {
                            goto exit_error;
                        }

                        int arrayIndex;

                        if (sscanf((char*)lineBuffer, "[%i]", &arrayIndex) != 1)
                        {
                            goto exit_error;
                        }

                        if (arrayIndex < 0)
                        {
                            goto exit_error;
                        }

                        if (currentArrayNode->modelType == DataAttributeModelType)
                        {
                            if (StringUtils_endsWith((char*)lineBuffer, ";"))
                            {
                                /* array of basic data attribute */
                                ModelNode* arrayElementNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);

                                if (arrayElementNode)
                                {
                                    setValue((char*)lineBuffer, (DataAttribute*)arrayElementNode);
                                }
                                else
                                {
                                    goto exit_error;
                                }
                            }
                            else if (StringUtils_endsWith((char*)lineBuffer, "{"))
                            {
                                /* array of constructed data attribtute */
                                currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);

                                if (currentModelNode)
                                {
                                    inArrayElement = true;
                                }
                                else
                                {
                                    goto exit_error;
                                }
                            }
                        }
                        else if (currentArrayNode->modelType == DataObjectModelType)
                        {
                            if (StringUtils_endsWith((char*)lineBuffer, "{"))
                            {
                                /* array of constructed data attribtute */
                                currentModelNode = ModelNode_getChildWithIdx(currentArrayNode, arrayIndex);

                                if (currentModelNode)
                                {
                                    inArrayElement = true;
                                }
                                else
                                {
                                    goto exit_error;
                                }
                            }
                            else
                            {
                                if (DEBUG_IED_SERVER)
                                    printf("Unexpected character at end of line: %s\n", lineBuffer);
                                goto exit_error;
                            }
                        }
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "DA"))
                    {
                        int arrayElements = 0;

                        int attributeType = 0;
                        int functionalConstraint = 0;
                        int triggerOptions = 0;
                        uint32_t sAddr = 0;

                        if (sscanf((char*)lineBuffer, "DA(%129s %i %i %i %i %u)", nameString, &arrayElements, &attributeType, &functionalConstraint, &triggerOptions, &sAddr) != 6) {
                            goto exit_error;
                        }

                        DataAttribute* dataAttribute = DataAttribute_create(nameString, currentModelNode,
                                (DataAttributeType) attributeType, (FunctionalConstraint) functionalConstraint, triggerOptions, arrayElements, sAddr);

                        if (arrayElements > 0)
                        {
                            inArray = true;
                            currentArrayNode = (ModelNode*)dataAttribute;
                        }

                        setValue((char*)lineBuffer, dataAttribute);

                        int lineLength = (int) strlen((char*) lineBuffer);

                        if (lineBuffer[lineLength - 1] == '{')
                        {
                            indendation++;
                            currentModelNode = (ModelNode*) dataAttribute;
                        }
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "DE"))
                    {
                        char* start = strchr((char*) lineBuffer, '(');

                        if (start)
                        {
                            start++;

                            StringUtils_copyStringMax(nameString, 130, start);

                            terminateString(nameString, ')');

                            int indexVal = -1;
                            char* componentVal = NULL;

                            /* check for index */
                            char* sep = strchr(nameString, ' ');

                            if (sep)
                            {
                                char* indexStr = sep + 1;
                                *sep = 0;

                                /* check for component */
                                sep = strchr(indexStr, ' ');

                                if (sep) {
                                    componentVal = sep + 1;
                                    *sep = 0;
                                }

                                indexVal = atoi(indexStr);
                            }

                            DataSetEntry_create(currentDataSet, nameString, indexVal, componentVal);
                        }
                    }
                    else if (StringUtils_startsWith((char*) lineBuffer, "PA"))
                    {
                        uint32_t vlanPrio;
                        uint32_t vlanId;
                        uint32_t appId;

                        int matchedItems =
                            sscanf((char*)lineBuffer, "PA(%u %u %u %129s)", &vlanPrio, &vlanId, &appId, nameString);

                        if ((matchedItems != 4) || ((currentGoCB == NULL) && (currentSMVCB == NULL)))
                            goto exit_error;

                        terminateString(nameString, ')');

                        if (strlen(nameString) != 12)
                            goto exit_error;

                        if (StringUtils_createBufferFromHexString(nameString, (uint8_t*)nameString2) != 6)
                            goto exit_error;

                        PhyComAddress* dstAddress = PhyComAddress_create((uint8_t)vlanPrio, (uint16_t)vlanId,
                                                                         (uint16_t)appId, (uint8_t*)nameString2);

                        if (currentGoCB)
                        {
                            GSEControlBlock_addPhyComAddress(currentGoCB, dstAddress);
                        }

                        if (currentSMVCB)
                        {
                            SVControlBlock_addPhyComAddress(currentSMVCB, dstAddress);
                        }
                    }
                    else
                        goto exit_error;
                }
            }
            else
            {
                if (StringUtils_startsWith((char*) lineBuffer, "MODEL{"))
                {
                    model = IedModel_create("");
                    stateInModel = true;
                    indendation = 1;
                }
                else if (StringUtils_startsWith((char*) lineBuffer, "MODEL("))
                {
                    if (sscanf((char*)lineBuffer, "MODEL(%129s)", nameString) != 1)
                        goto exit_error;

                    terminateString(nameString, ')');
                    model = IedModel_create(nameString);
                    stateInModel = true;
                    indendation = 1;
                }
                else
                    goto exit_error;
            }
        }
    }

    GLOBAL_FREEMEM(lineBuffer);

    return model;

exit_error:

    GLOBAL_FREEMEM(lineBuffer);

    if (DEBUG_IED_SERVER)
        printf("IED_SERVER: error parsing line %i (indentation level = %i)\n", currentLine, indendation);

    IedModel_destroy(model);

    return NULL;
}
