/****************************************************************************/
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
// Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0/
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License 2.0 are satisfied: GNU General Public License, version 2
// or later which is available at
// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
/****************************************************************************/
/// @file    DataHandler.cpp
/// @author  Jakob Erdmann
/// @date    Jun 2021
///
// The XML-Handler for data elements loading
/****************************************************************************/
#include <config.h>

#include <utils/common/FileBucket.h>
#include <utils/common/MsgHandler.h>
#include <utils/common/StringUtils.h>
#include <utils/xml/XMLSubSys.h>

#include "DataHandler.h"


// ===========================================================================
// method definitions
// ===========================================================================

DataHandler::DataHandler(FileBucket* fileBucket) :
    CommonHandler(fileBucket),
    SUMOSAXHandler(fileBucket->getFilename()) {
}


DataHandler::~DataHandler() {}


bool
DataHandler::parse() {
    // run parser and return result
    return XMLSubSys::runParser(*this, getFileName());
}


void
DataHandler::parseSumoBaseObject(CommonXMLStructure::SumoBaseObject* obj) {
    // check if loading was aborted
    if (!myAbortLoading) {
        // switch tag
        switch (obj->getTag()) {
            // Stopping Places
            case SUMO_TAG_INTERVAL:
                if (buildDataInterval(obj,
                                      obj->getStringAttribute(SUMO_ATTR_ID),
                                      obj->getDoubleAttribute(SUMO_ATTR_BEGIN),
                                      obj->getDoubleAttribute(SUMO_ATTR_END))) {
                    obj->markAsCreated();
                }
                break;
            case SUMO_TAG_EDGE:
                if (buildEdgeData(obj,
                                  obj->getStringAttribute(SUMO_ATTR_ID),
                                  obj->getParameters())) {
                    obj->markAsCreated();
                }
                break;
            case SUMO_TAG_EDGEREL:
                if (buildEdgeRelationData(obj,
                                          obj->getStringAttribute(SUMO_ATTR_FROM),
                                          obj->getStringAttribute(SUMO_ATTR_TO),
                                          obj->getParameters())) {
                    obj->markAsCreated();
                }
                break;
            case SUMO_TAG_TAZREL:
                if (buildTAZRelationData(obj,
                                         obj->getStringAttribute(SUMO_ATTR_FROM),
                                         obj->getStringAttribute(SUMO_ATTR_TO),
                                         obj->getParameters())) {
                    obj->markAsCreated();
                }
                break;
            default:
                break;
        }
        // now iterate over childrens
        for (const auto& child : obj->getSumoBaseObjectChildren()) {
            // call this function recursively
            parseSumoBaseObject(child);
        }
    }
}


void
DataHandler::myStartElement(int element, const SUMOSAXAttributes& attrs) {
    // obtain tag
    const SumoXMLTag tag = (element == 0) ? SUMO_TAG_ROOTFILE : static_cast<SumoXMLTag>(element);
    // open SUMOBaseOBject
    myCommonXMLStructure.openSUMOBaseOBject();
    // check tag
    try {
        switch (tag) {
            // interval
            case SUMO_TAG_INTERVAL:
                parseInterval(attrs);
                break;
            // datas
            case SUMO_TAG_EDGE:
                parseEdgeData(attrs);
                break;
            case SUMO_TAG_EDGEREL:
                parseEdgeRelationData(attrs);
                break;
            case SUMO_TAG_TAZREL:
                parseTAZRelationData(attrs);
                break;
            case SUMO_TAG_PARAM:
                WRITE_WARNING(TL("Data elements cannot load attributes as params"));
                myCommonXMLStructure.abortSUMOBaseOBject();
                break;
            default:
                // tag cannot be parsed in routeHandler
                myCommonXMLStructure.abortSUMOBaseOBject();
                break;
        }
    } catch (InvalidArgument& e) {
        writeError(e.what());
    }
}


void
DataHandler::myEndElement(int element) {
    // obtain tag
    const SumoXMLTag tag = static_cast<SumoXMLTag>(element);
    // get last inserted object
    CommonXMLStructure::SumoBaseObject* obj = myCommonXMLStructure.getCurrentSumoBaseObject();
    // close SUMOBaseOBject
    myCommonXMLStructure.closeSUMOBaseOBject();
    if (obj) {
        // check tag
        switch (tag) {
            // only interval
            case SUMO_TAG_INTERVAL:
                // parse object and all their childrens
                parseSumoBaseObject(obj);
                // delete object (and all of their childrens)
                delete obj;
                break;
            default:
                break;
        }
    }
}


void
DataHandler::parseInterval(const SUMOSAXAttributes& attrs) {
    // declare Ok Flag
    bool parsedOk = true;
    // needed attributes
    const std::string id = attrs.get<std::string>(SUMO_ATTR_ID, "", parsedOk);
    const double begin = STEPS2TIME(attrs.getSUMOTimeReporting(SUMO_ATTR_BEGIN, "", parsedOk));
    const double end = STEPS2TIME(attrs.getSUMOTimeReporting(SUMO_ATTR_END, "", parsedOk));
    // continue if flag is ok
    if (parsedOk) {
        // set tag
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_INTERVAL);
        // add all attributes
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_ID, id);
        myCommonXMLStructure.getCurrentSumoBaseObject()->addDoubleAttribute(SUMO_ATTR_BEGIN, begin);
        myCommonXMLStructure.getCurrentSumoBaseObject()->addDoubleAttribute(SUMO_ATTR_END, end);
    } else {
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_ERROR);
    }
}


void
DataHandler::parseEdgeData(const SUMOSAXAttributes& attrs) {
    // declare Ok Flag
    bool parsedOk = true;
    // needed attributes
    const std::string id = attrs.get<std::string>(SUMO_ATTR_ID, "", parsedOk);
    // fill attributes
    getAttributes(attrs, {SUMO_ATTR_ID});
    // continue if flag is ok
    if (parsedOk) {
        // set tag
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_EDGE);
        // add all attributes
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_ID, id);
    } else {
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_ERROR);
    }
}


void
DataHandler::parseEdgeRelationData(const SUMOSAXAttributes& attrs) {
    // declare Ok Flag
    bool parsedOk = true;
    // needed attributes
    const std::string from = attrs.get<std::string>(SUMO_ATTR_FROM, "", parsedOk);
    const std::string to = attrs.get<std::string>(SUMO_ATTR_TO, "", parsedOk);
    // fill attributes
    getAttributes(attrs, {SUMO_ATTR_FROM, SUMO_ATTR_TO});
    // continue if flag is ok
    if (parsedOk) {
        // set tag
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_EDGEREL);
        // add all attributes
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_FROM, from);
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_TO, to);
    } else {
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_ERROR);
    }
}


void
DataHandler::parseTAZRelationData(const SUMOSAXAttributes& attrs) {
    // declare Ok Flag
    bool parsedOk = true;
    // needed attributes
    const std::string from = attrs.get<std::string>(SUMO_ATTR_FROM, "", parsedOk);
    const std::string to = attrs.get<std::string>(SUMO_ATTR_TO, "", parsedOk);
    // fill attributes
    getAttributes(attrs, {SUMO_ATTR_FROM, SUMO_ATTR_TO});
    // continue if flag is ok
    if (parsedOk) {
        // set tag
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_TAZREL);
        // add all attributes
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_FROM, from);
        myCommonXMLStructure.getCurrentSumoBaseObject()->addStringAttribute(SUMO_ATTR_TO, to);
    } else {
        myCommonXMLStructure.getCurrentSumoBaseObject()->setTag(SUMO_TAG_ERROR);
    }
}


void
DataHandler::getAttributes(const SUMOSAXAttributes& attrs, const std::vector<SumoXMLAttr> avoidAttributes) const {
    // transform avoidAttributes to strings
    std::vector<std::string> avoidAttributesStr;
    for (const SumoXMLAttr& avoidAttribute : avoidAttributes) {
        avoidAttributesStr.push_back(toString(avoidAttribute));
    }
    // iterate over attributes and fill parameters map
    for (const std::string& attribute : attrs.getAttributeNames()) {
        if (std::find(avoidAttributesStr.begin(), avoidAttributesStr.end(), attribute) == avoidAttributesStr.end()) {
            myCommonXMLStructure.getCurrentSumoBaseObject()->addParameter(attribute, attrs.getStringSecure(attribute, ""));
        }
    }
}


void
DataHandler::checkParent(const SumoXMLTag currentTag, const SumoXMLTag parentTag, bool& ok) {
    // check that parent SUMOBaseObject's tag is the parentTag
    if ((myCommonXMLStructure.getCurrentSumoBaseObject()->getParentSumoBaseObject() &&
            (myCommonXMLStructure.getCurrentSumoBaseObject()->getParentSumoBaseObject()->getTag() == parentTag)) == false) {
        writeError(toString(currentTag) + " must be defined within the definition of a " + toString(parentTag));
        ok = false;
    }
}

/****************************************************************************/
