#pragma once

#include <functional>

#include "Array.h"
#include "Maybe.h"
#include "RCPointer.h"
#include "ReferenceCounter.h"

namespace Framework
{
    class Text;

    namespace XML
    {
        class Editor;

        //! An XML element of the form \code <name attribut1
        //! attribut2="value">text or children</name> \endcode
        class Element : public virtual ReferenceCounter
        {
        private:
            RCArray<Element>* children;
            RCArray<Text>* attributes;
            RCArray<Text>* attributeValues;
            Text* name;
            Text* text;
            Element* parent;

        public:
            //! Creates an empty XML element
            DLLEXPORT Element();
            //! Creates an XML element
            //! \param string Either the name of the element or an XML text
            //! to be parsed
            DLLEXPORT Element(Text string);
            //! Creates an XML element
            //! \param string Either the name of the element or an XML text
            //! to be parsed \param zParent A pointer to the parent element
            //! (without increased reference counter)
            DLLEXPORT Element(Text string, Element* zParent);
            DLLEXPORT ~Element();
            //! Changes an attribute or adds a new one
            //! \param attribut The name of the attribute
            //! \param value The value of the attribute
            DLLEXPORT void setAttribute(Text attribut, Text value);
            //! Removes an attribute
            //! \param attribut The name of the attribute
            DLLEXPORT void removeAttribute(Text attribut);
            //! Adds a child
            //! \param child The new child element
            DLLEXPORT void addChild(Element* child);
            //! Adds a child at the front
            //! \param child The new child element
            DLLEXPORT void addChildAtFront(Element* child);
            //! Removes a child
            //! \param zChild The child to remove
            DLLEXPORT void removeChild(Element* child);
            //! Removes the i-th child
            //! \param i The index of the child (starting at 0)
            DLLEXPORT void removeChild(int i);
            //! Removes all children
            DLLEXPORT void removeAllChilds();
            //! Removes a list of children
            //! \param childs All children to be removed
            DLLEXPORT void removeChilds(RCArray<Element>* childs);
            //! Removes this element from the parent element
            DLLEXPORT void remove();
            //! Sets the text in the element if it has no children
            //! \param text The text
            DLLEXPORT void setText(Text text);
            //! Returns the text in the element
            DLLEXPORT Text getText() const;
            //! Returns the number of children
            DLLEXPORT int getChildCount() const;
            /// <summary>
            /// returns the index of the zChild in the list of children or -1 if
            /// zChild is not a child of this element
            /// </summary>
            /// <param name="zChild">the child element to search for</param>
            /// <returns>the index of zChild in the list of children or -1 if
            /// zChild is not a child of this element</returns>
            DLLEXPORT int getChildIndex(Element* zChild) const;
            //! Returns the i-th child
            DLLEXPORT Element* getChild(int i) const;
            //! Returns the i-th child (without increased reference counter)
            DLLEXPORT Element* zChild(int i) const;
            //! Returns the parent element
            DLLEXPORT Element* getParent() const;
            //! Returns the parent element (without increased reference counter)
            DLLEXPORT Element* zParent() const;
            //! Returns an iterator to iterate through all children
            DLLEXPORT ArrayIterator<Element*> getChilds() const;
            //! Returns an editor for this element
            DLLEXPORT Editor select();
            //! Returns a selector containing all children
            DLLEXPORT Editor selectChildren() const;
            //! Returns a list of children that have a specific name
            //! \param name The name of the children
            DLLEXPORT Editor selectChildsByName(Text name) const;
            //! Returns a list of children that have a specific attribute
            //! \param attribute The name of the attribute
            DLLEXPORT Editor selectChildsByAttribute(Text attribute) const;
            //! Returns a list of children that have a specific attribute
            //! with a specific value \param attribute The name of the
            //! attribute \param value The value of the attribute
            DLLEXPORT Editor selectChildsByAttribute(
                Text attribute, Text value) const;
            //! Returns 1 if an attribute name exists, 0 otherwise
            DLLEXPORT bool hasAttribute(Text name) const;
            //! Returns the number of attributes
            DLLEXPORT int getAttributeCount() const;
            //! Returns the name of the i-th attribute
            DLLEXPORT Text getAttributeName(int i) const;
            //! Returns the value of the i-th attribute
            DLLEXPORT Text getAttributeValue(int i) const;
            //! Returns the value of an attribute
            //! \param attribut The name of the attribute
            DLLEXPORT const Text& getAttributeValue(const Text& attribut) const;
            //! Returns an iterator to iterate through all attribute names
            DLLEXPORT ArrayIterator<Text*> getAttributeNames() const;
            //! Returns an iterator to iterate through all attribute values
            DLLEXPORT ArrayIterator<Text*> getAttributeValues() const;
            //! Sets the name of the element
            DLLEXPORT void setName(Text name);
            //! Returns the name of the element
            DLLEXPORT Text getName() const;
            //! Generates an XML text containing this element and all children
            DLLEXPORT Text toString() const;
            //! Creates a copy without references to this object
            DLLEXPORT Element* dublicate() const;

            friend Editor;
        };

        //! An XML Editor that can edit multiple elements at once
        class Editor : public virtual ReferenceCounter
        {
        private:
            RCArray<Element>* elements;

        public:
            //! Creates a new XML Editor with a list of objects to be edited
            DLLEXPORT Editor(RCArray<Element>* elements);
            DLLEXPORT Editor(const Editor& e);
            DLLEXPORT ~Editor();
            /// <summary>
            /// returns the first element in the list
            /// </summary>
            /// <returns> the first element of a list or an empty object if no
            /// elements are present</returns>
            DLLEXPORT Maybe<RCPointer<Element>> getFirstElement() const;
            //! Changes an attribute or adds a new one (on all elements
            //! in the list) \param attribut The name of the attribute \param
            //! value The value of the attribute
            DLLEXPORT void setAttribute(Text attribut, Text value);
            //! Removes an attribute (on all elements in the list)
            //! \param attribut The name of the attribute
            DLLEXPORT void removeAttribute(Text attribut);
            //! Adds a child (on all elements in the list)
            //! \param child The new child element
            DLLEXPORT void addChild(Element* child);
            //! Removes a child (on all elements in the list)
            //! \param zChild The child to remove
            DLLEXPORT void removeChild(Element* child);
            //! Removes the i-th child (on all elements in the list)
            //! \param i The index of the child (starting at 0)
            DLLEXPORT void removeChild(int i);
            //! Removes all children (on all elements in the list)
            DLLEXPORT void removeAllChilds();
            //! Removes a list of children (on all elements in the list)
            //! \param childs All children to be removed
            DLLEXPORT void removeChilds(RCArray<Element>* childs);
            //! Removes this element from the parent element (on all elements
            //! in the list)
            DLLEXPORT void remove();
            //! Sets the text in the element if it has no children (on all
            //! elements in the list) \param text The text
            DLLEXPORT void setText(Text text);
            //! Returns an iterator through all elements
            DLLEXPORT ArrayIterator<Element*> begin();
            //! Returns the end of the iterator
            DLLEXPORT ArrayIterator<Element*> end();

        private:
            DLLEXPORT void selectAllElements(RCArray<Element>* zResult);

        public:
            //! Returns a selector containing all elements in this selector
            //! and recursively all children of the elements
            DLLEXPORT Editor selectAllElements();
            //! Returns a selector containing all children
            DLLEXPORT Editor selectChildren() const;
            //! Returns a selector containing all parents
            DLLEXPORT Editor selectParents() const;
            //! Returns a list of elements that have a specific name
            //! \param name The name of the children
            DLLEXPORT Editor whereNameEquals(Text name) const;
            //! Returns a list of elements that have a specific child
            //! \param name The name of the child
            DLLEXPORT Editor whereChildWithNameExists(Text name) const;
            //! Returns a list of elements that have a specific child
            //! \param attribute The name of the attribute
            DLLEXPORT Editor whereChildWithAttributeExists(
                Text attribute) const;
            //! Returns a list of elements that have a specific child
            //! \param attribute The name of the attribute \param value The
            //! value of the attribute
            DLLEXPORT Editor whereChildWithAttributeExists(
                Text attribute, Text value) const;
            //! Returns a list of elements that have a specific attribute
            //! \param attribute The name of the attribute
            DLLEXPORT Editor whereAttributeExists(Text attribute) const;
            //! Returns a list of elements that have a specific attribute
            //! with a specific value \param attribute The name of the
            //! attribute \param value The value of the attribute
            DLLEXPORT Editor whereAttributeEquals(
                Text attribute, Text value) const;
            //! Returns an Editor containing only the elements that are
            //! not in e \param e An Editor with elements that should not
            //! be included
            DLLEXPORT Editor without(Editor e) const;
            //! Calls a function for each element (takes an Element object
            //! without increased reference counter as argument) \param f The
            //! function
            DLLEXPORT void forEach(std::function<void(Element*)> f) const;
            //! Returns 1 if at least one element was found
            DLLEXPORT bool exists() const;
            //! Returns the number of selected elements
            DLLEXPORT int getSize() const;
            //! assignment operator
            DLLEXPORT Editor& operator=(const Editor& e);
        };
    } // namespace XML
} // namespace Framework