#include "containers.h"

#include <util/stream/output.h>
#include <util/stream/file.h>
#include <util/folder/dirut.h>
#include <util/string/split.h>
#include <util/generic/singleton.h>

namespace NRtyInfraTests {

    TScriptData::TScriptData() {

        AllowedTypes.insert("RTYSERVER");
        AllowedTypes.insert("RTYEMULATOR");
        AllowedTypes.insert("DISPATCHER");
        AllowedTypes.insert("SPROXY");
        AllowedTypes.insert("IPROXY");
        AllowedTypes.insert("SERVICE");
        AllowedTypes.insert("CONFIG");
        AllowedTypes.insert("PATH");

        AllowedTypes.insert("SYSTEM");
        AllowedTypes.insert("ACTION");
        AllowedTypes.insert("QUERY");
        AllowedTypes.insert("CONTR");
        AllowedTypes.insert("GET");
// obsolete
        AllowedTypes.insert("CONTROL");
        AllowedTypes.insert("SEARCH");
        AllowedTypes.insert("INDEX");
        AllowedTypes.insert("DELETE");
    }

    void TScriptData::ReadScript(const char* scriptPath) {
        VERIFY_AND_THROW(NFs::Exists(scriptPath), "script path '" << scriptPath << "' is not exist");
        Cout << "reading file '" << scriptPath << "'" << Endl;
        TIFStream fileInput(scriptPath);
        TString currentLine;
        size_t counter = 1;
        while (fileInput.ReadLine(currentLine)) {
            TVector<TString> tokens;
            Cout << " line " << counter << ": ";
            TokenizeLine(currentLine, tokens);

            if (tokens.size() == 0) {
                Cout << "empty line" << Endl;
            } else if (tokens.size() == 1 && tokens[0] == MAIN) {
                ProcessMain(fileInput, currentLine, counter);
            } else if (tokens.size() == 2) {
                if (tokens[0] == INCLUDE) {
                    Cout << "start reading include '" << tokens[1] << "'" << Endl;
                    ReadScript(tokens[1].data());
                } else {
                    ProcessObject(fileInput, currentLine, counter);
                }
            } else {
                ythrow yexception() << "something unsupported: " << currentLine << Endl;
            }
            ++counter;
        }
        Cout << "reading file '" << scriptPath << "' finished" << Endl;
    }

    void TScriptData::TokenizeLine(TString& line, TVector<TString>& tokens) {
        Split(line, SPACE, tokens);
    }

    void TScriptData::ProcessMain(TIFStream& fileInput, TString& currentLine, size_t& counter) {
        Cout << "start reading main: " << Endl;
        ++counter;
        while (fileInput.ReadLine(currentLine) && !currentLine.empty()) {
            Cout << " line " << counter << ": " << currentLine << Endl;
            TVector<TString> tokens;
            TokenizeLine(currentLine, tokens);
            VERIFY_AND_THROW(tokens.size() < 3, "wrong line in main: " << currentLine);
            if (tokens.size() == 1) {
                tokens.push_back(Default<TString>());
            }
            MainSequence.push_back(std::pair<TString, TString>(tokens[0], tokens[1]));
            ++counter;
        }
        Cout << " line " << counter << ": finished reading main" << Endl;
    }

    void TScriptData::ProcessObject(TIFStream& fileInput, TString& currentLine, size_t& counter) {
        TVector<TString> tokens;
        TokenizeLine(currentLine, tokens);
        Cout << "start reading object '" << tokens[0] << "'" << Endl;
        TAttrs& object = ProcessParent(tokens[1], tokens[0]);
        ++counter;
        while (fileInput.ReadLine(currentLine) && !(currentLine.empty() || currentLine.find_first_not_of(' ') == TString::npos)) {
            Cout << " line " << counter << ": ";
            TokenizeLine(currentLine, tokens);
            VERIFY_AND_THROW(tokens.size() > 1, "wrong line in object: " << currentLine);
            object[tokens[0]] = currentLine.substr(currentLine.find(tokens[1]));
            Cout << " attribute '" << tokens[0] << "', value: '" << object[tokens[0]] << "'\n";
            ++counter;
        }
        Cout << " line " << counter << ": finished reading object " << currentLine << Endl;
    }

    TAttrs& TScriptData::ProcessParent(const TString& parentName, const TString& objectName) {
        TAttrs *objectPtr;
        THashMap<TString, TAttrs>::iterator parentPtr = Objects.find(parentName);
        if (parentPtr != Objects.end()) {
            objectPtr = &(Objects[objectName] = parentPtr->second);
        } else if (AllowedTypes.find(parentName) != AllowedTypes.end()) {
            objectPtr = &(Objects[objectName]);
            (*objectPtr)["Type"] = parentName;
        } else {
            ythrow yexception() << "unsupported type or undefined parent :" << parentName << Endl;
        }
        return *objectPtr;
    }


    const TScriptData::TMain& TScriptData::GetMainSequence() const {
        return MainSequence;
    }

    const TScriptData::TObjects& TScriptData::GetObjects() const {
        return Objects;
    }

    const TString& TScriptData::GetValue(const TAttrs& currentObject, const TString& attrName) const {
        TAttrs::const_iterator namePtr = currentObject.find(attrName);
        VERIFY_AND_THROW(currentObject.end() != namePtr, attrName << " is not set");
        return namePtr->second;
    }

    const TAttrs& TScriptData::GetObject(const TString& name) const {
        TObjects::const_iterator objectPtr = Objects.find(name);
        VERIFY_AND_THROW(Objects.end() != objectPtr, "object " << name << " is not found");
        return objectPtr->second;
    }

    const TAttrs& TScriptData::GetObjectByAttr(const TAttrs& currentObject, const TString& attrName) const {
        return GetObject(GetValue(currentObject, attrName));
    }

    const TString TScriptData::SPACE(" ");
    const TString TScriptData::MAIN("MAIN");
    const TString TScriptData::INCLUDE("INCLUDE");
}
