//
// Created by magino on 6.6.19.
//

#ifndef S7CSVCOMPOSITEBUILDER_H
#define S7CSVCOMPOSITEBUILDER_H

#include <queue>
#include <memory>
#include <Management.h>
#include "csv.h"
#include "compositeBuilder/ICompositeBuilder.h"
#include "S7CsvRowModel.h"
#include "composite/INode.h"
#include "composite/S7/S7ObjectNode.h"
#include "composite/S7/S7VariableNode.h"
#include "composite/S7/S7MethodNode.h"
#include "compositeBuilder/SiemensDataType.h"

class S7CsvCompositeBuilder: public ICompositeBuilder {
private:
    std::queue<S7CsvRowModel> _rows;
    io::CSVReader<7> _in;
    int _actualLevel;

    float _minAddress = 0;
    float _maxAddress = 0;
    std::string _maxDataType = "";

    S7CsvRowModel& GetRow() { return _rows.front(); }

    void BuildLevel(INode& objectNode) {
        if(_rows.empty())
            return;

        S7CsvRowModel row = GetRow();

        if(row.level <= _actualLevel)
            return;
        switch (row.nodeType) {
            case UA_NODECLASS_OBJECT: {
                ++_actualLevel;

                INode* newObjectNode = new S7ObjectNode(comm, uaServer, row);
                objectNode.Add(newObjectNode);
                _rows.pop();

                do {
                    BuildLevel(*newObjectNode);
                } while (!_rows.empty() && _rows.front().level == _actualLevel + 1);

                --_actualLevel;
                break;
            }
            case UA_NODECLASS_METHOD: {
                ++_actualLevel;

                // 1. row defines method
                S7MethodNode* methodNode = new S7MethodNode(comm, uaServer, row, mngmnt);
                _rows.pop();

                // 2. row defines trigger bool
                methodNode->AddTrigger(_rows.front());
                _rows.pop();

                // 3. row single output argument
                methodNode->AddOutputArgument(_rows.front());
                _rows.pop();

                // 4. row .. and more -> input arguments
                S7CsvRowModel methodParam;
                while(!_rows.empty() && (methodParam = GetRow()).level == _actualLevel + 1) {
                    methodNode->AddInputArgument(methodParam);
                    _rows.pop();
                }

                objectNode.Add(methodNode);
                --_actualLevel;
                break;
            }
            case UA_NODECLASS_VARIABLE: {
                S7VariableNode* varNode = new S7VariableNode(comm, uaServer, row);
                objectNode.Add(varNode);
                _rows.pop();
                BuildLevel(objectNode);
                break;
            }
        }
    }

    float AddToMaxByType(std::string type) {
        if(type == "Bool" || type == "Byte" ||type == "SInt" || type == "USInt" || type == "Int" || type == "UInt" || type == "Word")
            return 2.0;
        else if(type == "DInt" || type == "UDInt" || type == "DWord" || type == "Real")
            return 4.0;
        else if(type == "LInt" || type == "ULInt" || type == "LWord" || type == "LReal")
            return 8.0;
        // TODO parse number between [] from string
        //if(type.compare(0, 6, "String") == 0)
        //    return type.substr(7,);
        return 0.0;
    }

public:
    S7CsvCompositeBuilder(const std::string fileName, ICommunication& comm, MyUaServer& server, Management& mngmnt): ICompositeBuilder(comm, server, mngmnt), _in(fileName), _actualLevel(0)  {
        _in.read_header(io::ignore_extra_column, "Name", "Data type", "Offset", "Accessible", "Writable", "Visible", "Comment");
        std::string name, dataType, comment, accessible, writable, visible; double offset;

        bool init = true;
        while(_in.read_row(name, dataType, offset, accessible, writable, visible, comment)) {
            auto type = getS7(dataType);
            if (type == SiemensDataType::LREAL || type == SiemensDataType::CHAR || type == SiemensDataType::ARRAY) {
                UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "%s datatype is not supported for mapping from s7 to opc ua! %s variable will not be added!!", dataType.c_str(), name.c_str());
                continue;
            }
            if(init) {
                _minAddress = offset;
                _maxAddress = offset;
                _maxDataType = dataType;
                init = false;
            }

            if(offset > _maxAddress) {
                _maxAddress = offset;
                _maxDataType = dataType;
            }

            if(offset < _minAddress)
                _minAddress = offset;

            _rows.emplace(name, getS7(dataType), getUa(dataType), offset, accessible, writable, visible, comment); //Move ctor of S7
        }

        _maxAddress += AddToMaxByType(_maxDataType);
    }
    ~S7CsvCompositeBuilder() override { }

    ICompositeBuilder& BuildRoot() {
        S7CsvRowModel& row = GetRow();
        rootNode = new S7ObjectNode(comm, uaServer, row);
        _rows.pop();
        return *this;
    }

    ICompositeBuilder& BuildTree() {
        do {
            BuildLevel(*rootNode);
        } while(!_rows.empty());
        return *this;
    }

    UA_DataType getUa(std::string type) {
        if(type == "Bool")
            return UA_TYPES[UA_TYPES_BOOLEAN];
        if(type == "Byte" || type == "USInt")
            return UA_TYPES[UA_TYPES_BYTE];
        if(type == "SInt")
            return UA_TYPES[UA_TYPES_SBYTE];
        if(type == "Int")
            return UA_TYPES[UA_TYPES_INT16];
        if(type == "UInt" || type == "Word")
            return UA_TYPES[UA_TYPES_UINT16];
        if(type == "DInt")
            return UA_TYPES[UA_TYPES_INT32];
        if(type == "UDInt" || type == "DWord")
            return UA_TYPES[UA_TYPES_UINT32];
        if(type == "LInt")
            return UA_TYPES[UA_TYPES_INT64];
        if(type == "ULInt" || type == "LWord")
            return UA_TYPES[UA_TYPES_UINT64];
        if(type == "Real")
            return UA_TYPES[UA_TYPES_FLOAT];
        if(type == "LReal")
            return UA_TYPES[UA_TYPES_DOUBLE];
        /*
        if(type == "Char")
            return UA_TYPES[UA_TYPES_STRING];
        */
        if(type.compare(0, 6, "String") == 0)
            return UA_TYPES[UA_TYPES_STRING];

        return UA_TYPES[UA_TYPES_EXTENSIONOBJECT];
    }

    SiemensDataType getS7(std::string type) {
        if(type == "Bool")
            return SiemensDataType::BOOL;
        if(type == "Byte" || type == "USInt")
            return SiemensDataType::BYTE;
        if(type == "SInt")
            return SiemensDataType::SINT;
        if(type == "Int")
            return SiemensDataType::INT;
        if(type == "UInt" || type == "Word")
            return SiemensDataType::UINT;
        if(type == "DInt")
            return SiemensDataType::DINT;
        if(type == "UDInt" || type == "DWord")
            return SiemensDataType::UDINT;
        if(type == "LInt")
            return SiemensDataType::LINT;
        if(type == "ULInt" || type == "LWord")
            return SiemensDataType::ULINT;
        if(type == "Real")
            return SiemensDataType::REAL;
        if(type == "LReal")
            return SiemensDataType::LREAL;
        if(type == "Char")
            return SiemensDataType::CHAR;
        if(type.compare(0, 6, "String") == 0)
            return SiemensDataType::STRING;
        if(type.compare(0, 5, "Array") == 0)
            return SiemensDataType::ARRAY;

        return SiemensDataType::UDT;
    }
};
#endif //S7CSVCOMPOSITEBUILDER_H