// **************************************************************************
// * This file is part of the zen::Xml project. It is distributed under the *
// * Boost Software License: http://www.boost.org/LICENSE_1_0.txt           *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#ifndef ZEN_XML_DOM_HEADER_82085720723894567204564256
#define ZEN_XML_DOM_HEADER_82085720723894567204564256

#include <string>
#include <vector>
#include <memory>
#include <map>
#include "cvrt_text.h" //"readText/writeText"

namespace zen
{
class XmlDoc;

/// An XML element
class XmlElement
{
    struct PrivateConstruction {};
public:
    //Construct an empty XML element
    //This constructor should be private, however std::make_shared() requires public access
    //Therefore at least prevent users from calling it via private dummy type PrivateConstruction
    template <class String>
    XmlElement(const String& name, XmlElement* parentElement, PrivateConstruction) : name_(utfCvrtTo<std::string>(name)), parent_(parentElement) {}

    ///Retrieve the name of this XML element.
    /**
      \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
      \returns Name of the XML element.
    */
    template <class String>
    String getNameAs() const { return utfCvrtTo<String>(name_); }

    ///Get the value of this element as a user type.
    /**
      \tparam T Arbitrary user data type: e.g. any string class, all built-in arithmetic numbers, STL container, ...
      \returns "true" if Xml element was successfully converted to value, cannot fail for string-like types
    */
    template <class T>
    bool getValue(T& value) const { return readStruc(*this, value); }

    ///Set the value of this element.
    /**
      \tparam T Arbitrary user data type: e.g. any string-like type, all built-in arithmetic numbers, STL container, ...
    */
    template <class T>
    void setValue(const T& value) { writeStruc(value, *this); }

    ///Retrieve an attribute by name.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
      \tparam T String-convertible user data type: e.g. any string class, all built-in arithmetic numbers
      \param name The name of the attribute to retrieve.
      \param value The value of the attribute converted to T.
      \return "true" if value was retrieved successfully.
    */
    template <class String, class T>
    bool getAttribute(const String& name, T& value) const
    {
        auto it = attributes.find(utfCvrtTo<std::string>(name));
        return it == attributes.end() ? false : readText(it->second, value);
    }

    ///Create or update an XML attribute.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
      \tparam T String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers
      \param name The name of the attribute to create or update.
      \param value The value to set.
    */
    template <class String, class T>
    void setAttribute(const String& name, const T& value)
    {
        std::string attrValue;
        writeText(value, attrValue);
        attributes[utfCvrtTo<std::string>(name)] = attrValue;
    }

    ///Remove the attribute with the given name.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
    */
    template <class String>
    void removeAttribute(const String& name) { attributes.erase(utfCvrtTo<std::string>(name)); }

    ///Create a new child element and return a reference to it.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
      \param name The name of the child element to be created.
    */
    template <class String>
    XmlElement& addChild(const String& name)
    {
        std::string utf8Name = utfCvrtTo<std::string>(name);
        auto newElement = std::make_shared<XmlElement>(utf8Name, this, PrivateConstruction());
        childElements.push_back(newElement);
        childElementsSorted.insert(std::make_pair(utf8Name, newElement));
        return *newElement;
    }

    ///Retrieve a child element with the given name.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
      \param name The name of the child element to be retrieved.
      \return A pointer to the child element or nullptr if none was found.
    */
    template <class String>
    const XmlElement* getChild(const String& name) const
    {
        auto it = childElementsSorted.find(utfCvrtTo<std::string>(name));
        return it == childElementsSorted.end() ? nullptr : &*(it->second);
    }

    ///\sa getChild
    template <class String>
    XmlElement* getChild(const String& name)
    {
        return const_cast<XmlElement*>(static_cast<const XmlElement*>(this)->getChild(name));
    }

    template < class IterTy,     //underlying iterator type
             class T,            //target object type
             class AccessPolicy > //access policy: see AccessPtrMap
    class PtrIter : public std::iterator<std::input_iterator_tag, T>, private AccessPolicy //get rid of shared_ptr indirection
    {
    public:
        PtrIter(IterTy it) : it_(it) {}
        PtrIter(const PtrIter& other) : it_(other.it_) {}
        PtrIter& operator++() { ++it_; return *this; }
        PtrIter operator++(int) { PtrIter tmp(*this); operator++(); return tmp; }
        inline friend bool operator==(const PtrIter& lhs, const PtrIter& rhs) { return lhs.it_ == rhs.it_; }
        inline friend bool operator!=(const PtrIter& lhs, const PtrIter& rhs) { return !(lhs == rhs); }
        T& operator* () { return  AccessPolicy::template objectRef<T>(it_); }
        T* operator->() { return &AccessPolicy::template objectRef<T>(it_); }
    private:
        IterTy it_;
    };

    struct AccessPtrMap
    {
        template <class T, class IterTy>
        T& objectRef(const IterTy& it) { return *(it->second); }
    };

    typedef PtrIter<std::multimap<std::string, std::shared_ptr<XmlElement>>::iterator, XmlElement, AccessPtrMap> ChildIter2;
    typedef PtrIter<std::multimap<std::string, std::shared_ptr<XmlElement>>::const_iterator, const XmlElement, AccessPtrMap> ChildIterConst2;

    ///Access all child elements with the given name via STL iterators.
    /**
      \code
      auto iterPair = elem.getChildren("Item");
      std::for_each(iterPair.first, iterPair.second,
    		[](const XmlElement& child) { ... });
      \endcode
      \param name The name of the child elements to be retrieved.
      \return A pair of STL begin/end iterators to access the child elements sequentially.
    */
    template <class String>
    std::pair<ChildIterConst2, ChildIterConst2> getChildren(const String& name) const { return childElementsSorted.equal_range(utfCvrtTo<std::string>(name)); }

    ///\sa getChildren
    template <class String>
    std::pair<ChildIter2, ChildIter2> getChildren(const String& name) { return childElementsSorted.equal_range(utfCvrtTo<std::string>(name)); }

    struct AccessPtrVec
    {
        template <class T, class IterTy>
        T& objectRef(const IterTy& it) { return **it; }
    };

    typedef PtrIter<std::vector<std::shared_ptr<XmlElement>>::iterator, XmlElement, AccessPtrVec> ChildIter;
    typedef PtrIter<std::vector<std::shared_ptr<XmlElement>>::const_iterator, const XmlElement, AccessPtrVec> ChildIterConst;

    ///Access all child elements sequentially via STL iterators.
    /**
      \code
      auto iterPair = elem.getChildren();
      std::for_each(iterPair.first, iterPair.second,
    		[](const XmlElement& child) { ... });
      \endcode
      \return A pair of STL begin/end iterators to access all child elements sequentially.
    */
    std::pair<ChildIterConst, ChildIterConst> getChildren() const { return std::make_pair(childElements.begin(), childElements.end()); }

    ///\sa getChildren
    std::pair<ChildIter, ChildIter> getChildren() { return std::make_pair(childElements.begin(), childElements.end()); }

    ///Get parent XML element, may be nullptr for root element
    XmlElement* parent() { return parent_; };
    ///Get parent XML element, may be nullptr for root element
    const XmlElement* parent() const { return parent_; };


    typedef std::map<std::string, std::string>::const_iterator AttrIter;

    /* -> disabled documentation extraction
      \brief Get all attributes associated with the element.
      \code
        auto iterPair = elem.getAttributes();
        for (auto it = iterPair.first; it != iterPair.second; ++it)
           std::cout << "name: " << it->first << " value: " << it->second << "\n";
      \endcode
      \return A pair of STL begin/end iterators to access all attributes sequentially as a list of name/value pairs of std::string.
    */
    std::pair<AttrIter, AttrIter> getAttributes() const { return std::make_pair(attributes.begin(), attributes.end()); }

    //Transactionally swap two elements.  -> disabled documentation extraction
    void swap(XmlElement& other)
    {
        name_     .swap(other.name_);
        value_    .swap(other.value_);
        attributes.swap(other.attributes);
        childElements.swap(other.childElements);
        childElementsSorted.swap(other.childElementsSorted);
        //std::swap(parent_, other.parent_); -> parent is physical location; update children's parent reference instead:
        std::for_each(      childElements.begin(),       childElements.end(), [&](const std::shared_ptr<XmlElement>& child) { child->parent_ = this;   });
        std::for_each(other.childElements.begin(), other.childElements.end(), [&](const std::shared_ptr<XmlElement>& child) { child->parent_ = &other; });
    }

private:
    friend class XmlDoc;

    XmlElement(const XmlElement&);            //not implemented
    XmlElement& operator=(const XmlElement&); //

    std::string name_;
    std::string value_;
    std::map<std::string, std::string> attributes;
    std::vector<std::shared_ptr<XmlElement>>                childElements;       //all child elements in order of creation
    std::multimap<std::string, std::shared_ptr<XmlElement>> childElementsSorted; //alternate key: sorted by element name
    XmlElement* parent_;
};


//XmlElement::setValue<T>() calls zen::writeStruc() which calls XmlElement::setValue() ... => these two specializations end the circle
template <> inline
void XmlElement::setValue(const std::string& value) { value_ = value; }

template <> inline
bool XmlElement::getValue(std::string& value) const { value = value_; return true; }


///The complete XML document
class XmlDoc
{
public:
    ///Default constructor setting up an empty XML document with a standard declaration: <?xml version="1.0" encoding="UTF-8" ?>
    XmlDoc() : version_("1.0"), encoding_("UTF-8"), rootElement("Root", nullptr, XmlElement::PrivateConstruction()) {}

    XmlDoc(XmlDoc&& tmp) : rootElement("Root", nullptr, XmlElement::PrivateConstruction()) { swap(tmp); }
    XmlDoc& operator=(XmlDoc&& tmp) { swap(tmp); return *this; }

    //Setup an empty XML document
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
      \param rootName The name of the XML document's root element.
    */
    template <class String>
    XmlDoc(String rootName) : version_("1.0"), encoding_("UTF-8"), rootElement(rootName, nullptr, XmlElement::PrivateConstruction()) {}

    ///Get a const reference to the document's root element.
    const XmlElement& root() const { return rootElement; }
    ///Get a reference to the document's root element.
    XmlElement& root() { return rootElement; }

    ///Get the version used in the XML declaration.
    /**
      \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
    */
    template <class String>
    String getVersionAs() const { return utfCvrtTo<String>(version_); }

    ///Set the version used in the XML declaration.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
    */
    template <class String>
    void setVersion(const String& version) { version_ = utfCvrtTo<std::string>(version); }

    ///Get the encoding used in the XML declaration.
    /**
      \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
    */
    template <class String>
    String getEncodingAs() const { return utfCvrtTo<String>(encoding_); }

    ///Set the encoding used in the XML declaration.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
    */
    template <class String>
    void setEncoding(const String& encoding) { encoding_ = utfCvrtTo<std::string>(encoding); }

    ///Get the standalone string used in the XML declaration.
    /**
      \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
    */
    template <class String>
    String getStandaloneAs() const { return utfCvrtTo<String>(standalone_); }

    ///Set the standalone string used in the XML declaration.
    /**
      \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
    */
    template <class String>
    void setStandalone(const String& standalone) { standalone_ = utfCvrtTo<std::string>(standalone); }

    //Transactionally swap two elements.  -> disabled documentation extraction
    void swap(XmlDoc& other)
    {
        version_   .swap(other.version_);
        encoding_  .swap(other.encoding_);
        standalone_.swap(other.standalone_);
        rootElement.swap(other.rootElement);
    }

private:
    XmlDoc(const XmlDoc&);            //not implemented, thanks to XmlElement::parent_
    XmlDoc& operator=(const XmlDoc&); //

    std::string version_;
    std::string encoding_;
    std::string standalone_;

    XmlElement rootElement;
};

}

#endif //ZEN_XML_DOM_HEADER_82085720723894567204564256