/****************************************************************************/
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
// Copyright (C) 2012-2019 German Aerospace Center (DLR) and others.
// This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v2.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v20.html
// SPDX-License-Identifier: EPL-2.0
/****************************************************************************/
/// @file    BinaryFormatter.h
/// @author  Daniel Krajzewicz
/// @author  Jakob Erdmann
/// @author  Michael Behrisch
/// @date    2012
/// @version $Id$
///
// Output formatter for plain XML output
/****************************************************************************/
#ifndef BinaryFormatter_h
#define BinaryFormatter_h


// ===========================================================================
// included modules
// ===========================================================================
#include <config.h>

#include <vector>
#include <utils/common/FileHelpers.h>
#include <utils/common/ToString.h>
#include <utils/xml/SUMOXMLDefinitions.h>
#include "OutputFormatter.h"


// ===========================================================================
// class declarations
// ===========================================================================
class Position;
class PositionVector;
class Boundary;
class RGBColor;
class ROEdge;
class MSEdge;


// ===========================================================================
// class definitions
// ===========================================================================
/**
 * @class BinaryFormatter
 * @brief Output formatter for plain XML output
 *
 * BinaryFormatter format XML like output into the output stream.
 */
class BinaryFormatter : public OutputFormatter {
public:
    /// @brief data types in binary output
    enum DataType {
        /// @brief
        BF_BYTE,
        /// @brief
        BF_INTEGER,
        /// @brief
        BF_FLOAT,
        /// @brief
        BF_STRING,
        /// @brief
        BF_LIST,
        /// @brief
        BF_XML_TAG_START,
        /// @brief
        BF_XML_TAG_END,
        /// @brief
        BF_XML_ATTRIBUTE,
        /// @brief
        BF_EDGE,
        /// @brief
        BF_LANE,
        /// @brief
        BF_POSITION_2D,
        /// @brief
        BF_POSITION_3D,
        /// @brief
        BF_BOUNDARY,
        /// @brief
        BF_COLOR,
        /// @brief
        BF_NODE_TYPE,
        /// @brief
        BF_EDGE_FUNCTION,
        /// @brief
        BF_ROUTE,
        /// @brief
        BF_SCALED2INT,
        /// @brief
        BF_SCALED2INT_POSITION_2D,
        /// @brief
        BF_SCALED2INT_POSITION_3D,
        /// @brief
        BF_INVALID
    };

    /// @brief Constructor
    BinaryFormatter();


    /// @brief Destructor
    virtual ~BinaryFormatter() { }


    /** @brief Writes an XML header with optional configuration
     *
     * If something has been written (myXMLStack is not empty), nothing
     *  is written and false returned.
     *
     * @param[in] into The output stream to use
     * @param[in] rootElement The root element to use
     * @param[in] attrs Additional attributes to save within the rootElement
     * @todo Describe what is saved
     */
    bool writeXMLHeader(std::ostream& into, const std::string& rootElement,
                        const std::map<SumoXMLAttr, std::string>& attrs);


    /** @brief Writes a header with optional edge list and connections.
     *
     * If something has been written (myXMLStack is not empty), nothing
     *  is written and false returned. This header is only used by the binary formatter.
     *
     * @param[in] into The output stream to use
     * @param[in] rootElement The root element to use
     */
    template <typename E>
    bool writeHeader(std::ostream& into, const SumoXMLTag& rootElement);


    /** @brief Opens an XML tag
     *
     * An indentation, depending on the current xml-element-stack size, is written followed
     *  by the given xml element ("<" + xmlElement)
     * The xml element is added to the stack, then.
     *
     * @param[in] into The output stream to use
     * @param[in] xmlElement Name of element to open
     * @returns The OutputDevice for further processing
     */
    void openTag(std::ostream& into, const std::string& xmlElement);


    /** @brief Opens an XML tag
     *
     * Helper method which finds the correct string before calling openTag.
     *
     * @param[in] into The output stream to use
     * @param[in] xmlElement Id of the element to open
     */
    void openTag(std::ostream& into, const SumoXMLTag& xmlElement);


    /** @brief Closes the most recently opened tag
     *
     * @param[in] into The output stream to use
     * @returns Whether a further element existed in the stack and could be closed
     * @todo it is not verified that the topmost element was closed
     */
    bool closeTag(std::ostream& into, const std::string& comment = "");


    /** @brief writes an arbitrary attribute
     *
     * @param[in] into The output stream to use
     * @param[in] attr The attribute (name)
     * @param[in] val The attribute value
     */
    template <typename dummy, typename T>
    static void writeAttr(dummy& into, const SumoXMLAttr attr, const T& val);


    /** @brief writes a named attribute
     *
     * @param[in] into The output stream to use
     * @param[in] attr The attribute (name)
     * @param[in] val The attribute value
     */
    template <typename dummy, typename T>
    static void writeAttr(dummy& into, const std::string& attr, const T& val);


    /** @brief writes a preformatted tag to the device but ensures that any
     * pending tags are closed
     * @param[in] into The output stream to use
     * @param[in] val The preformatted data
     */
    void writePreformattedTag(std::ostream& into, const std::string& val) {
        FileHelpers::writeString(into, val);
    }

    /// @brief padding is ignored for binary output
    void writePadding(std::ostream& /*into*/, const std::string&) { }


    /* we need to use dummy templating here to compile those functions where they get
        called to avoid an explicit dependency of utils/iodevices on the edge implementations */
    template <typename dummy>
    static void writeAttr(dummy& into, const SumoXMLAttr attr, const std::vector<const ROEdge*>& val);
    template <typename dummy>
    static void writeAttr(dummy& into, const SumoXMLAttr attr, const std::vector<const MSEdge*>& val);


private:
    /** @brief writes the header for an arbitrary attribute
     *
     * @param[in] into The output stream to use
     * @param[in] attr The attribute (name)
     * @param[in] type The attribute type
     */
    static inline void writeAttrHeader(std::ostream& into, const SumoXMLAttr attr, const DataType type = BF_INVALID) {
        FileHelpers::writeByte(into, static_cast<unsigned char>(BF_XML_ATTRIBUTE));
        const int attrNum = (int)attr;
        FileHelpers::writeByte(into, static_cast<unsigned char>(attrNum % 256));
        FileHelpers::writeByte(into, static_cast<unsigned char>(attrNum / 256));
        if (type != BF_INVALID) {
            FileHelpers::writeByte(into, static_cast<unsigned char>(type));
        }
    }


    /** @brief writes the part of the header which is always unchanged.
     *
     * This method exists only to reduce include dependencies of BinaryFormatter.h (which promote to OutputDevice.h, ...)
     *
     * @param[in] into The output stream to use
     */
    static void writeStaticHeader(std::ostream& into);


    /** @brief writes a list of strings
     *
     * @param[in] into The output stream to use
     * @param[in] list the list to write
     */
    static void writeStringList(std::ostream& into, const std::vector<std::string>& list);


    /** @brief writes a position
     *
     * @param[in] into The output stream to use
     * @param[in] val the position to write
     */
    static void writePosition(std::ostream& into, const Position& val);


private:
    /// @brief The stack of begun xml elements
    std::vector<SumoXMLTag> myXMLStack;


};


template <typename E>
bool BinaryFormatter::writeHeader(std::ostream& into, const SumoXMLTag& rootElement) {
    if (myXMLStack.empty()) {
        writeStaticHeader(into);
        const int numEdges = (const int)E::getAllEdges().size();
        FileHelpers::writeByte(into, BF_LIST);
        FileHelpers::writeInt(into, numEdges);
        for (int i = 0; i < numEdges; i++) {
            FileHelpers::writeByte(into, BF_STRING);
            FileHelpers::writeString(into, E::getAllEdges()[i]->getID());
        }
        FileHelpers::writeByte(into, BF_LIST);
        FileHelpers::writeInt(into, numEdges);
        for (int i = 0; i < numEdges; i++) {
            E* e = E::getAllEdges()[i];
            FileHelpers::writeByte(into, BF_LIST);
            FileHelpers::writeInt(into, e->getNumSuccessors());
            for (int j = 0; j < e->getNumSuccessors(); j++) {
                FileHelpers::writeByte(into, BF_INTEGER);
                FileHelpers::writeInt(into, e->getSuccessors()[j]->getNumericalID());
            }
        }
        openTag(into, rootElement);
        return true;
    }
    return false;
}


template <typename dummy, typename T>
void BinaryFormatter::writeAttr(dummy& into, const SumoXMLAttr attr, const T& val) {
    BinaryFormatter::writeAttrHeader(into, attr, BF_STRING);
    FileHelpers::writeString(into, toString(val, into.precision()));
}


template <typename dummy, typename T>
void BinaryFormatter::writeAttr(dummy& into, const std::string& attr, const T& val) {
    if (SUMOXMLDefinitions::Attrs.hasString(attr)) {
        writeAttr(into, (const SumoXMLAttr)(SUMOXMLDefinitions::Attrs.get(attr)), val);
    }
}

template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const bool& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const double& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const int& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const SumoXMLNodeType& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const SumoXMLEdgeFunc& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const Position& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const PositionVector& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const Boundary& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const RGBColor& val);
template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const std::vector<int>& val);
//template<> void BinaryFormatter::writeAttr(std::ostream& into, const SumoXMLAttr attr, const std::vector<double>& val);


template <typename dummy>
void BinaryFormatter::writeAttr(dummy& into, const SumoXMLAttr attr, const std::vector<const ROEdge*>& val) {
    BinaryFormatter::writeAttrHeader(into, attr, BF_ROUTE);
    FileHelpers::writeEdgeVector(into, val);
}


template <typename dummy>
void BinaryFormatter::writeAttr(dummy& into, const SumoXMLAttr attr, const std::vector<const MSEdge*>& val) {
    BinaryFormatter::writeAttrHeader(into, attr, BF_ROUTE);
    FileHelpers::writeEdgeVector(into, val);
}

#endif

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

