#include "src/parser/PnmlParser.h"
#ifdef USE_XERCES
#include <iostream>

#include "src/adapters/XercesAdapter.h"

#include "src/exceptions/UnexpectedException.h"
#include "src/exceptions/WrongFormatException.h"
#include "src/utility/macros.h"

namespace storm {
    namespace parser {
        storm::gspn::GSPN * PnmlParser::parse(xercesc::DOMElement const*  elementRoot ) {
            if (storm::adapters::getName(elementRoot) == "pnml") {
                traversePnmlElement(elementRoot);
            } else {
                // If the top-level node is not a "pnml" or "" node, then throw an exception.
                STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Failed to identify the root element.\n");
            }
            return builder.buildGspn();
        }

        void PnmlParser::traversePnmlElement(xercesc::DOMElement const* const element) {
            // traverse attributes
            for (uint_fast64_t i = 0; i < element->getAttributes()->getLength(); ++i) {
                auto attr = element->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);

                // Found node or attribute which is at the moment nod handled by this parser.
                // Notify the user and continue the parsing.
                STORM_PRINT_AND_LOG("unknown attribute (node=pnml): " + name + "\n");
            }

            // traverse children
            for (uint_fast64_t i = 0; i < element->getChildNodes()->getLength(); ++i) {
                auto child = element->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);

                if (name == "net") {
                    traverseNetOrPage(child);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=pnml): " + name + "\n");
                }
            }
        }

        void PnmlParser::traverseNetOrPage(xercesc::DOMNode const* const node) {
            // traverse attributes
            for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) {
                auto attr = node->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);

                if (name == "id") {
                    builder.setGspnName(storm::adapters::XMLtoString(attr->getNodeValue()));
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown attribute (node=" + storm::adapters::XMLtoString(node->getNodeName()) + "): " + name + "\n");
                }
            }

            // traverse children
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);

                if (name == "place") {
                    traversePlace(child);
                } else if (name == "transition") {
                    traverseTransition(child);
                } else if (name == "arc") {
                    traverseArc(child);
                } else if (name == "page") {
                    // Some pnml files have a child named page.
                    // The page node has the same children like the net node (e.g., place, transition, arc)
                    traverseNetOrPage(child);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=" + storm::adapters::XMLtoString(node->getNodeName()) + "): " + name + "\n");
                }
            }
        }

        void PnmlParser::traversePlace(xercesc::DOMNode const* const node) {
            std::string placeName;
            // the first entry is false if the corresponding information was not found in the pnml file
            std::pair<bool, uint_fast64_t> numberOfInitialTokens(false, defaultNumberOfInitialTokens);
            std::pair<bool, int_fast64_t> capacity(false, defaultCapacity);

            // traverse attributes
            for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) {
                auto attr = node->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);

                if (name == "id") {
                    placeName = storm::adapters::XMLtoString(attr->getNodeValue());
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown attribute (node=place): " + name + "\n");
                }
            }

            // traverse children
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);

                if (name == "initialMarking") {
                    numberOfInitialTokens.first = true;
                    numberOfInitialTokens.second = traverseInitialMarking(child);
                } else if(name == "capacity") {
                    capacity.first = true;
                    capacity.second = traverseCapacity(child);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else if (name == "name" ||
                           name == "graphics") {
                    // ignore these tags
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=place): " + name + "\n");
                }
            }

            if (!numberOfInitialTokens.first) {
                // no information about the number of initial tokens is found
                // use the default number of initial tokens
                STORM_PRINT_AND_LOG("unknown numberOfInitialTokens (place=" + placeName + ")\n");
            }
            if (!capacity.first) {
                // no information about the capacity is found
                // use default capacity
                STORM_PRINT_AND_LOG("unknown capacity (place=" + placeName + ")\n");
            }
            builder.addPlace(capacity.first ? capacity.second : -1, numberOfInitialTokens.first ? numberOfInitialTokens.second : 0, placeName);
        }

        void PnmlParser::traverseTransition(xercesc::DOMNode const* const node) {
            // the first entry is false if the corresponding information was not found in the pnml file
            std::pair<bool, bool> timed(false, defaultTransitionType);
            std::pair<bool, std::string> value(false, "");
            std::string id;
            uint_fast64_t priority = defaultPriority;

            // parse attributes
            for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) {
                auto attr = node->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);

                if (name == "id") {
                    id = storm::adapters::XMLtoString(attr->getNodeValue());
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown attribute (node=transition): " + name + "\n");
                }
            }

            // traverse children
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);

                if (name == "rate") {
                    value.first = true;
                    value.second = traverseTransitionValue(child);
                } else if (name == "timed") {
                    timed.first = true;
                    timed.second = traverseTransitionType(child);
                } else if (name == "priority") {
                    priority = traversePriority(child);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else if (name == "graphics" ||
                           name == "name" ||
                           name == "orientation") {
                    // ignore these tags
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=transition): " + name + "\n");
                }
            }

            // build transition and add it to the gspn
            if (!timed.first) {
                // found unknown transition type
                // abort parsing
                STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "unknown transition type (transition=" + id + ")\n");
                return;
            }

            if (timed.second) {
                if (!value.first) {
                    // no information about the rate is found
                    // abort the parsing
                    STORM_LOG_THROW(false, storm::exceptions::UnexpectedException ,"unknown transition rate (transition=" + id + ")\n");
                }
                builder.addTimedTransition(priority, std::stod(value.second), id);
            } else {
                if (!value.first) {
                    // no information about the weight is found
                    // continue with the default weight
                    STORM_PRINT_AND_LOG("unknown transition weight (transition=" + id + ")\n");
                }
                builder.addImmediateTransition(priority, std::stod(value.second), id);
            }
        }

        void PnmlParser::traverseArc(xercesc::DOMNode const* const node) {
            // the first entry is false if the corresponding information was not found in the pnml file
            std::pair<bool, std::string> source(false, "");
            std::pair<bool, std::string> target(false, "");
            std::pair<bool, std::string> type(false, defaultArcType);
            std::pair<bool, uint_fast64_t> multiplicity(false, defaultMultiplicity);
            std::string id;

            // parse attributes
            for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) {
                auto attr = node->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);

                if (name == "source") {
                    source.first = true;
                    source.second = storm::adapters::XMLtoString(attr->getNodeValue());
                } else if (name == "target") {
                    target.first = true;
                    target.second = storm::adapters::XMLtoString(attr->getNodeValue());
                } else if (name == "id") {
                    id = storm::adapters::XMLtoString(attr->getNodeValue());
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown attribute (node=arc): " + name + "\n");
                }
            }

            // parse children
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "type") {
                    type.first = true;
                    type.second = traverseArcType(child);
                } else if(name == "inscription") {
                    multiplicity.first = true;
                    multiplicity.second = traverseMultiplicity(child);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else if (name == "graphics" || name == "arcpath" || name == "tagged") {
                    // ignore these tags
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=arc): " + name + "\n");
                }
            }

            // check if all necessary information where stored in the pnml file
            if (!source.first) {
                // could not find start of the arc
                // abort parsing
                STORM_LOG_THROW(false, storm::exceptions::UnexpectedException ,"unknown arc source (arc=" + id + ")\n");
            }
            if (!target.first) {
                // could not find the target of the arc
                // abort parsing
                STORM_LOG_THROW(false, storm::exceptions::UnexpectedException ,"unknown arc target (arc=" + id + ")\n");
            }
            if (!multiplicity.first) {
                // no information about the multiplicity of the arc
                // continue and use the default multiplicity
                STORM_PRINT_AND_LOG("unknown multiplicity (node=arc): " + id + "\n");
            }

            STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "No arc type specified for arc '" + id + "'");
            if (type.second == "normal") {
                builder.addNormalArc(source.second, target.second, multiplicity.second);
            } else if (type.second == "inhibition") {
                builder.addInhibitionArc(source.second, target.second, multiplicity.second);
            } else {
                STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Arc type '" << type.second << "' in arc '"  << id << "' is unknown.");
            }
        }

        uint_fast64_t PnmlParser::traverseInitialMarking(xercesc::DOMNode const* const node) {
            uint_fast64_t result = defaultNumberOfInitialTokens;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "text") {
                    result = std::stoull(storm::adapters::getName(child->getFirstChild()));
                } else if (name == "value") {
                    auto value = storm::adapters::getName(child->getFirstChild());
                    value = value.substr(std::string("Default,").length());
                    result = std::stoull(value);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else if (name == "graphics") {
                    // ignore these tags
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=initialMarking): " + name + "\n");
                }
            }
            return result;
        }

        int_fast64_t PnmlParser::traverseCapacity(xercesc::DOMNode const* const node) {
            int_fast64_t result= defaultCapacity;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "value") {
                    auto value = storm::adapters::getName(child->getFirstChild());
                    if (value.find("Default,") == 0) {
                        value = value.substr(std::string("Default,").length());
                    }
                    result = std::stoull(value);
                } else if (name == "graphics") {
                    // ignore these nodes
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=capacity): " + name + "\n");
                }
            }
            return result;
        }

        uint_fast64_t PnmlParser::traverseMultiplicity(xercesc::DOMNode const* const node) {
            uint_fast64_t result = defaultMultiplicity;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "value") {
                    auto value = storm::adapters::getName(child->getFirstChild());
                    if (value.find("Default,") == 0) {
                        value = value.substr(std::string("Default,").length());
                    }
                    result = std::stoull(value);
                } else if (name == "graphics") {
                    // ignore these nodes
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=inscription): " + name + "\n");
                }
            }
            return result;
        }


        std::string PnmlParser::traverseTransitionValue(xercesc::DOMNode const* const node) {
            std::string result;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "value") {
                    result = storm::adapters::getName(child->getFirstChild());
                } else  if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=rate): " + name + "\n");
                }
            }
            return result;
        }

        bool PnmlParser::traverseTransitionType(xercesc::DOMNode const* const node) {
            bool result;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "value") {
                    result = storm::adapters::getName(child->getFirstChild()) == "true" ? true : false;
                } else  if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=timed): " + name + "\n");
                }
            }
            return result;
        }

        std::string PnmlParser::traverseArcType(xercesc::DOMNode const* const node) {
            for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) {
                auto attr = node->getAttributes()->item(i);
                auto name = storm::adapters::getName(attr);
                if (name == "value") {
                    return storm::adapters::XMLtoString(attr->getNodeValue());
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=type): " + name + "\n");
                }
            }
            return defaultArcType;
        }

        uint_fast64_t PnmlParser::traversePriority(xercesc::DOMNode const* const node) {
            uint_fast64_t result = defaultPriority;
            for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) {
                auto child = node->getChildNodes()->item(i);
                auto name = storm::adapters::getName(child);
                if (name == "text") {
                    result = std::stoull(storm::adapters::getName(child->getFirstChild()));
                } else if (name == "value") {
                    auto value = storm::adapters::getName(child->getFirstChild());
                    value = value.substr(std::string("Default,").length());
                    result = std::stoull(value);
                } else if (std::all_of(name.begin(), name.end(), isspace)) {
                    // ignore node (contains only whitespace)
                } else if (name == "graphics") {
                    // ignore these tags
                } else {
                    // Found node or attribute which is at the moment nod handled by this parser.
                    // Notify the user and continue the parsing.
                    STORM_PRINT_AND_LOG("unknown child (node=priority): " + name + "\n");
                }
            }
            return result;
        }
    }
}

#endif