/*
 * Distributed under the OSI-approved Apache License, Version 2.0.  See
 * accompanying file Copyright.txt for details.
 *
 * adiosXML.cpp
 *
 *  Created on: May 17, 2017
 *      Author: William F Godoy godoywf@ornl.gov
 */

#include "adiosXML.h"

/// \cond EXCLUDE_FROM_DOXYGEN
#include <stdexcept> //std::invalid_argument
/// \endcond

#include "adios2/ADIOSMPI.h"
#include "adios2/ADIOSTypes.h"
#include "adios2/helper/adiosString.h"

#include <pugixml.hpp>

namespace adios2
{

Params InitParametersXML(pugi::xml_node node, bool debugMode)
{
    Params params;
    for (pugi::xml_node paramNode : node.children("parameter"))
    {
        pugi::xml_attribute attrKey = paramNode.attribute("key");
        if (!attrKey)
        {
            if (debugMode)
            {
                throw std::invalid_argument("ERROR: XML: No \"key\" attribute "
                                            "found on <parameter> element.");
            }
            continue;
        }

        pugi::xml_attribute attrValue = paramNode.attribute("value");
        if (!attrValue)
        {
            if (debugMode)
            {
                throw std::invalid_argument("ERROR: XML: No \"value\" "
                                            "attribute found on <parameter> "
                                            "element.");
            }
            continue;
        }

        params.emplace(attrKey.value(), attrValue.value());
    }
    return params;
}

void InitIOXML(const pugi::xml_node ioNode, const MPI_Comm mpiComm,
               const bool debugMode,
               std::vector<std::shared_ptr<Transform>> &transforms,
               std::map<std::string, IO> &ios)
{
    // Extract <io name=""> attribute
    pugi::xml_attribute nameAttr = ioNode.attribute("name");
    if (!nameAttr)
    {
        if (debugMode)
        {
            throw std::invalid_argument(
                "ERROR: XML: No \"name\" attribute found on <io> element.");
        }
        return;
    }
    std::string ioName = nameAttr.value();

    // Build the IO object
    auto ioIt = ios.emplace(ioName, IO(ioName, mpiComm, true, debugMode));
    IO &io = ioIt.first->second;

    // Extract <engine> element
    pugi::xml_node engineNode = ioNode.child("engine");
    if (!engineNode)
    {
        throw std::invalid_argument(
            "ERROR: XML: No <engine> element found in <io> element.");
    }
    pugi::xml_attribute engineTypeAttr = engineNode.attribute("type");
    if (!engineTypeAttr)
    {
        throw std::invalid_argument(
            "ERROR: XML: No \"type\" attribute found on <engine> element.");
    }
    io.SetEngine(engineTypeAttr.value());

    // Process <engine> parameters
    io.SetParameters(InitParametersXML(engineNode, debugMode));

    // Extract and process <transport> elements
    for (pugi::xml_node transportNode : ioNode.children("transport"))
    {
        pugi::xml_attribute typeAttr = transportNode.attribute("type");
        if (!typeAttr)
        {
            if (debugMode)
            {
                throw std::invalid_argument("ERROR: XML: No \"type\" attribute "
                                            "found on <transport> element.");
            }
            continue;
        }
        io.AddTransport(typeAttr.value(),
                        InitParametersXML(transportNode, debugMode));
    }
}

void InitXML(const std::string configXML, const MPI_Comm mpiComm,
             const bool debugMode,
             std::vector<std::shared_ptr<Transform>> &transforms,
             std::map<std::string, IO> &ios)
{
    int mpiRank;
    MPI_Comm_rank(mpiComm, &mpiRank);
    std::string fileContents;
    unsigned long long len;

    // Read the file on rank 0 and broadcast it to everybody else
    if (mpiRank == 0)
    {
        fileContents = FileToString(configXML);
        len = static_cast<unsigned long long>(fileContents.size());
    }
    MPI_Bcast(&len, 1, MPI_UNSIGNED_LONG, 0, mpiComm);
    if (mpiRank != 0)
    {
        fileContents.resize(len);
    }
    MPI_Bcast(const_cast<char *>(fileContents.data()), len, MPI_CHAR, 0,
              mpiComm);

    pugi::xml_document doc;
    auto parse_result = doc.load_buffer_inplace(
        const_cast<char *>(fileContents.data()), fileContents.size());
    if (!parse_result)
    {
        if (debugMode)
        {
            throw std::invalid_argument(
                std::string("ERROR: XML: Parse error: ") +
                parse_result.description());
        }
        return;
    }

    pugi::xml_node configNode = doc.child("adios-config");
    if (!configNode)
    {
        if (debugMode)
        {
            throw std::invalid_argument(
                "ERROR: XML: No <adios-config> element found");
        }
        return;
    }

    ios.clear();
    for (pugi::xml_node ioNode : configNode.children("io"))
    {
        InitIOXML(ioNode, mpiComm, debugMode, transforms, ios);
    }
}

} // end namespace adios