#include "XML.h"

using namespace Framework;
using namespace XML;

Framework::XML::Element::Element()
    : ReferenceCounter(),
      children(new RCArray<Element>()),
      attributes(new RCArray<Text>()),
      attributeValues(new RCArray<Text>()),
      name(new Text()),
      text(new Text()),
      parent(0)
{}

// Creates an XML Element
//  string: either the name of the element or an XML text to be parsed
Element::Element(Text string)
    : Element(string, 0)
{}

// Creates an XML Element
//  string: either the name of the element or an XML text to be parsed
//  zParent: A pointer to the parent element (without increased reference
//  counter)
Element::Element(Text string, Element* zParent)
    : Element()
{
    string.removeWhitespaceAfter(0);
    string.removeWhitespaceBefore(string.getLength());
    setText(string);
    if (string[0] == '<' && string[string.getLength() - 1] == '>')
    {
        string.removeWhitespaceAfter(1);
        string.removeWhitespaceBefore(string.getLength() - 1);
        int nameEnd = 0;
        for (int i = 1; i < string.getLength(); i++)
        {
            if ((string[i] < 'a' || string[i] > 'z')
                && (string[i] < 'A' || string[i] > 'Z')
                && (string[i] < '0' || string[i] > '9') && string[i] != '-'
                && string[i] != '_' && string[i] != '.')
            {
                nameEnd = i;
                break;
            }
        }
        Text* tmp = string.getTeilText(1, nameEnd);
        name->setText(*tmp);
        tmp->release();
        if (string.hatAt(
                string.getLength() - 1 - name->getLength(), name->getText())
            || string[string.getLength() - 2] == '/')
        {
            string.removeWhitespaceAfter(nameEnd);
            // parse attributes
            int start = nameEnd;
            while (string[nameEnd] != '>' && string[nameEnd] != '/')
            {
                for (int i = nameEnd + 1; i < string.getLength(); i++)
                {
                    if ((string[i] < 'a' || string[i] > 'z')
                        && (string[i] < 'A' || string[i] > 'Z')
                        && (string[i] < '0' || string[i] > '9')
                        && string[i] != '-' && string[i] != '_'
                        && string[i] != '.')
                    {
                        nameEnd = i;
                        break;
                    }
                }
                Text* attrName = string.getTeilText(start, nameEnd);
                string.removeWhitespaceAfter(nameEnd);
                if (string[nameEnd] == '=')
                {
                    string.removeWhitespaceAfter(nameEnd + 1);
                    Text value = "";
                    if (string[nameEnd + 1] == '"')
                    {
                        bool esc = 0;
                        start = nameEnd + 2;
                        for (int i = nameEnd + 2; string[i]; i++)
                        {
                            if (string[i] == '\\')
                                esc = !esc;
                            else
                            {
                                if (string[i] == '"' && !esc)
                                {
                                    nameEnd = i + 1;
                                    break;
                                }
                                esc = 0;
                            }
                        }
                        tmp = string.getTeilText(start, nameEnd - 1);
                        value.setText(*tmp);
                        tmp->release();
                        value.ersetzen("\\\"", "\"");
                    }
                    if (string[nameEnd + 1] == '\'')
                    {
                        bool esc = 0;
                        start = nameEnd + 2;
                        for (int i = nameEnd + 2; string[i]; i++)
                        {
                            if (string[i] == '\\')
                                esc = !esc;
                            else
                            {
                                if (string[i] == '\'' && !esc)
                                {
                                    nameEnd = i + 1;
                                    break;
                                }
                                esc = 0;
                            }
                        }
                        tmp = string.getTeilText(start, nameEnd - 1);
                        value.setText(*tmp);
                        tmp->release();
                        value.ersetzen("\\'", "'");
                    }
                    setAttribute(attrName->getText(), value);
                }
                else
                    setAttribute(attrName->getText(), "");
                attrName->release();
                string.removeWhitespaceAfter(nameEnd);
                start = nameEnd;
            }
            if (string[string.getLength() - 2] != '/')
            {
                string.removeWhitespaceBefore(
                    string.getLength() - 1 - name->getLength());
                if (string[string.getLength() - 2 - name->getLength()] == '/')
                {
                    string.removeWhitespaceBefore(
                        string.getLength() - 2 - name->getLength());
                    if (string[string.getLength() - 3 - name->getLength()]
                        == '<')
                    {
                        tmp = string.getTeilText(nameEnd + 1,
                            string.getLength() - 3 - name->getLength());
                        text->setText(*tmp);
                        tmp->release();
                        // parse children
                        text->removeWhitespaceAfter(0);
                        text->removeWhitespaceBefore(text->getLength());
                        if (text->getText()[0] == '<'
                            && text->getText()[text->getLength() - 1] == '>')
                        {
                            int start = 0;
                            int lastStart = -1;
                            while (start < text->getLength())
                            {
                                if (lastStart == start) break;
                                lastStart = start;
                                bool esc = 0;
                                bool inString1 = 0;
                                bool inString2 = 0;
                                int poc = 0;
                                bool lastSlash = 0;
                                bool lastOpen = 0;
                                bool openSlash = 0;
                                for (int i = 0; text->getText()[i]; i++)
                                {
                                    switch (text->getText()[i])
                                    {
                                    case '\\':
                                        esc = !esc;
                                        lastSlash = 0;
                                        lastOpen = 0;
                                        break;
                                    case '"':
                                        if (!esc && !inString2)
                                            inString1 = !inString1;
                                        esc = 0;
                                        lastSlash = 0;
                                        lastOpen = 0;
                                        break;
                                    case '\'':
                                        if (!esc && !inString1)
                                            inString2 = !inString2;
                                        esc = 0;
                                        lastSlash = 0;
                                        lastOpen = 0;
                                        break;
                                    case '<':
                                        if (!inString1 && !inString2)
                                            lastOpen = 1;
                                        esc = 0;
                                        lastSlash = 0;
                                        break;
                                    case '/':
                                        lastSlash = 0;
                                        if (!inString1 && !inString2)
                                        {
                                            lastSlash = 1;
                                            if (lastOpen) openSlash = 1;
                                        }
                                        esc = 0;
                                        lastOpen = 0;
                                        break;
                                    case '>':
                                        if (!inString1 && !inString2)
                                        {
                                            if (openSlash)
                                                poc--;
                                            else if (!lastSlash)
                                                poc++;
                                            if (poc == 0)
                                            {
                                                Text* str = text->getTeilText(
                                                    start, i + 1);
                                                addChild(new Element(
                                                    str->getText(), this));
                                                str->release();
                                                start = i + 1;
                                            }
                                        }
                                        esc = 0;
                                        lastSlash = 0;
                                        openSlash = 0;
                                        break;
                                    default:
                                        esc = 0;
                                        if (text->getText()[i] != ' '
                                            && text->getText()[i] != '\t'
                                            && text->getText()[i] != '\r'
                                            && text->getText()[i] != '\n')
                                        {
                                            lastSlash = 0;
                                            lastOpen = 0;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            else
                text->setText("");
        }
    }
    parent = zParent;
}

Element::~Element()
{
    children->release();
    attributes->release();
    attributeValues->release();
    text->release();
    name->release();
}

// Changes an attribute or adds a new one
//  attribut: The name of the attribute
//  value: The value of the attribute
void Element::setAttribute(Text attribut, Text value)
{
    for (auto i = attributes->begin(), j = attributeValues->begin(); i && j;
        i++, j++)
    {
        if (i->istGleich(attribut))
        {
            j->setText(value);
            return;
        }
    }
    attributes->add(new Text(attribut));
    attributeValues->add(new Text(value));
}

// Removes an attribute
//  attribut: The name of the attribute
void Element::removeAttribute(Text attribut)
{
    for (int i = 0; i < attributes->getEintragAnzahl(); i++)
    {
        if (attributes->z(i)->istGleich(attribut))
        {
            attributes->remove(i);
            attributeValues->remove(i);
            i--;
        }
    }
}

// Adds a child
//  child: The new child element
void Element::addChild(Element* child)
{
    child->parent = this;
    children->add(child);
}

// Adds a child at the front
//  child: The new child element
void Element::addChildAtFront(Element* child)
{
    child->parent = this;
    children->add(child, 0);
}

// Removes a child
//  zChild: The child to remove
void Element::removeChild(Element* child)
{
    for (int i = 0; i < children->getEintragAnzahl(); i++)
    {
        if (children->z(i) == child)
        {
            children->remove(i);
            i--;
        }
    }
    child->release();
}

// Removes the i-th child
//  i: The index of the child (starting at 0)
void Element::removeChild(int i)
{
    children->remove(i);
}

// Removes all children
void Element::removeAllChilds()
{
    children->leeren();
}

// Removes a list of children
//  childs: All children to be removed
void Element::removeChilds(RCArray<Element>* childs)
{
    for (auto i : *childs)
        removeChild(dynamic_cast<XML::Element*>(i->getThis()));
    childs->release();
}

// Removes this element from the parent element
void Element::remove()
{
    if (parent) parent->removeChild(dynamic_cast<XML::Element*>(getThis()));
}

// Sets the text in the element if it has no children
//  text: The text
void Element::setText(Text text)
{
    this->text->setText(text);
}

// Returns the text in the element
Text Element::getText() const
{
    return text->getText();
}

// Returns the number of children
int Element::getChildCount() const
{
    return children->getEintragAnzahl();
}

int Framework::XML::Element::getChildIndex(Element* zChild) const
{
    return children->indexOf(zChild);
}

// Returns the i-th child
Element* Element::getChild(int i) const
{
    return children->get(i);
}

// Returns the i-th child (without increased reference counter)
Element* Element::zChild(int i) const
{
    return children->z(i);
}

// Returns the parent element
Element* Element::getParent() const
{
    return parent ? dynamic_cast<Element*>(parent->getThis()) : 0;
}

// Returns the parent element (without increased reference counter)
Element* Element::zParent() const
{
    return parent;
}

// Returns an iterator to iterate through all children
ArrayIterator<Element*> Element::getChilds() const
{
    return children->begin();
}

//! Returns an editor for this element
Editor Element::select()
{
    RCArray<Element>* tmp = new RCArray<Element>();
    tmp->add(dynamic_cast<XML::Element*>(getThis()));
    return Editor(tmp);
}

// Returns a selector containing all children
Editor Element::selectChildren() const
{
    return Editor(dynamic_cast<RCArray<XML::Element>*>(children->getThis()));
}

// Returns a list of children that have a specific name
//  name: The name of the children
Editor Element::selectChildsByName(Text name) const
{
    RCArray<Element>* tmp = new RCArray<Element>();
    for (auto i : *children)
    {
        if (i->getName().istGleich(name))
            tmp->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(tmp);
}

// Returns a list of children that have a specific attribute
//  attribute: The name of the attribute
Editor Element::selectChildsByAttribute(Text attribute) const
{
    RCArray<Element>* tmp = new RCArray<Element>();
    for (auto i : *children)
    {
        if (i->hasAttribute(attribute))
            tmp->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(tmp);
}

// Returns a list of children that have a specific attribute with a
// specific value
//  attribute: The name of the attribute
//  value: The value of the attribute
Editor Element::selectChildsByAttribute(Text attribute, Text value) const
{
    RCArray<Element>* tmp = new RCArray<Element>();
    for (auto i : *children)
    {
        if (i->hasAttribute(attribute)
            && i->getAttributeValue(attribute).istGleich(value))
            tmp->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(tmp);
}

// Returns 1 if an attribute name exists, 0 otherwise
bool Element::hasAttribute(Text name) const
{
    for (auto i : *attributes)
    {
        if (i->istGleich(name)) return 1;
    }
    return 0;
}

// Returns the number of attributes
int Element::getAttributeCount() const
{
    return attributes->getEintragAnzahl();
}

// Returns the name of the i-th attribute
Text Element::getAttributeName(int i) const
{
    return attributes->z(i)->getText();
}

// Returns the value of the i-th attribute
Text Element::getAttributeValue(int i) const
{
    return attributeValues->z(i)->getText();
}

// Returns the value of an attribute
//  attribut: The name of the attribute
const Text& Element::getAttributeValue(const Text& attribut) const
{
    for (auto i = attributes->begin(), j = attributeValues->begin(); i && j;
        i++, j++)
    {
        if (i->istGleich(attribut)) return *j.val();
    }
    return Text::EMPTY;
}

// Returns an iterator to iterate through all attribute names
ArrayIterator<Text*> Element::getAttributeNames() const
{
    return attributes->begin();
}

// Returns an iterator to iterate through all attribute values
ArrayIterator<Text*> Element::getAttributeValues() const
{
    return attributeValues->begin();
}

void Framework::XML::Element::setName(Text name)
{
    this->name->setText(name);
}

// Returns the name of the element
Text Element::getName() const
{
    return name->getText();
}

// Generates an XML text containing this element and all children
Text Element::toString() const
{
    Text ret = "<";
    ret += name->getText();
    if (attributes->getEintragAnzahl()) ret += " ";
    for (auto i = attributes->begin(), j = attributeValues->begin(); i && j;
        i++, j++)
    {
        ret += i->getText();
        if (j->hat('"'))
        {
            ret += "='";
            Text txt = j->getText();
            txt.ersetzen("'", "\\'");
            ret += txt;
            ret += "'";
        }
        else
        {
            ret += "=\"";
            Text txt = j->getText();
            txt.ersetzen("\"", "\\\"");
            ret += txt;
            ret += "\"";
        }
        if (i.hasNext()) ret += " ";
    }
    if (children->getEintragAnzahl() || text->getLength())
    {
        ret += ">";
        if (children->getEintragAnzahl())
        {
            for (auto i : *children)
                ret += i->toString();
        }
        else
            ret += text->getText();
        ret += "</";
        ret += name->getText();
        ret += ">";
    }
    else
        ret += "/>";
    return ret;
}

// Creates a copy without references to this object
Element* Element::dublicate() const
{
    return new Element(toString());
}

// Creates a new XML Editor with a list of objects to be edited
Editor::Editor(RCArray<Element>* elements)
    : ReferenceCounter()
{
    this->elements = elements;
}

Editor::Editor(const Editor& e)
    : Editor(dynamic_cast<RCArray<XML::Element>*>(e.elements->getThis()))
{}

Editor::~Editor()
{
    elements->release();
}

Maybe<RCPointer<Element>> Framework::XML::Editor::getFirstElement() const
{
    if (this->elements->getEintragAnzahl() > 0)
    {
        return Maybe<RCPointer<Element>>::of(
            RCPointer<Element>::of(this->elements->get(0)));
    }
    return Maybe<RCPointer<Element>>::empty();
}

// Changes an attribute or adds a new one (on all elements in the list)
//  attribut: The name of the attribute
//  value: The value of the attribute
void Editor::setAttribute(Text attribut, Text value)
{
    for (auto i : *elements)
        i->setAttribute(attribut, value);
}

// Removes an attribute (on all elements in the list)
//  attribut: The name of the attribute
void Editor::removeAttribute(Text attribut)
{
    for (auto i : *elements)
        i->removeAttribute(attribut);
}

// Adds a child (on all elements in the list)
//  child: The new child element
void Editor::addChild(Element* child)
{
    for (auto i : *elements)
        i->addChild(child->dublicate());
    child->release();
}

// Removes a child (on all elements in the list)
//  zChild: The child to remove
void Editor::removeChild(Element* child)
{
    for (auto i : *elements)
        i->removeChild(dynamic_cast<XML::Element*>(child->getThis()));
    child->release();
}

// Removes the i-th child (on all elements in the list)
//  i: The index of the child (starting at 0)
void Editor::removeChild(int i)
{
    for (auto j : *elements)
        j->removeChild(i);
}

// Removes all children (on all elements in the list)
void Editor::removeAllChilds()
{
    for (auto i : *elements)
        i->removeAllChilds();
}

// Removes a list of children (on all elements in the list)
//  childs: All children to be removed
void Editor::removeChilds(RCArray<Element>* childs)
{
    for (auto i : *elements)
        i->removeChilds(
            dynamic_cast<RCArray<XML::Element>*>(childs->getThis()));
    childs->release();
}

// Removes this element from the parent element (on all elements in the list)
void Editor::remove()
{
    for (auto i : *elements)
        i->remove();
}

// Sets the text in the element if it has no children (on all elements
// in the list)
//  text: The text
void Editor::setText(Text text)
{
    for (auto i : *elements)
        i->setText(text);
}

// Returns an iterator through all elements
ArrayIterator<Element*> Editor::begin()
{
    return elements->begin();
}

//! Returns the end of the iterator
ArrayIterator<Element*> Editor::end()
{
    return elements->end();
}

void Editor::selectAllElements(RCArray<Element>* zResult)
{
    for (auto i : *elements)
    {
        zResult->add(dynamic_cast<XML::Element*>(i->getThis()));
        i->selectChildren().selectAllElements(zResult);
    }
}

//! Gibt einen selector zurueck der alle elemente beinhaltet die in diesem
//! selector vorkommen und rekursiv alle Kinder der elemente Enthaelt
Editor Editor::selectAllElements()
{
    RCArray<Element>* list = new RCArray<Element>();
    selectAllElements(list);
    return Editor(list);
}

// gibt einen selector zurueck der alle childs beinhaltet
Editor Editor::selectChildren() const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        for (Element* j : i->selectChildren())
        {
            list->add(dynamic_cast<XML::Element*>(j->getThis()));
        }
    }
    return Editor(list);
}

// gibt einen selector zurueck der alle parents beinhaltet
Editor Editor::selectParents() const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->parent)
            list->add(dynamic_cast<XML::Element*>(i->parent->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die einen bestimmten Namen haben
//  name: der name der Childs
Editor Editor::whereNameEquals(Text name) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->getName().istGleich(name))
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die ein bestimmtes child haben
//  name: der name des childs
Editor Editor::whereChildWithNameExists(Text name) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->selectChildsByName(name).elements->getEintragAnzahl())
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die ein bestimmtes child haben
//  attribute: der name des attributes
Editor Editor::whereChildWithAttributeExists(Text attribute) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->selectChildsByAttribute(attribute).elements->getEintragAnzahl())
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die ein bestimmtes child haben
//  attribute: der name des attributes
//  value: der Wert des Attributes
Editor Editor::whereChildWithAttributeExists(Text attribute, Text value) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->selectChildsByAttribute(attribute, value)
                .elements->getEintragAnzahl())
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die ein bestimmtes Attribut haben
//  attribute: der name des Attributes
Editor Editor::whereAttributeExists(Text attribute) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->hasAttribute(attribute))
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// gibt eine Liste mit elementen zurueck, die ein bestimmtes Attribut mit einem
// bestimmten wert haben
//  attribute: der name des Attributes
//  value: der Wert des Attributes
Editor Editor::whereAttributeEquals(Text attribute, Text value) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        if (i->hasAttribute(attribute)
            && i->getAttributeValue(attribute).istGleich(value))
            list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// Gibt einen Editor zurueck welcher nurnoch die Elemente enthaelt die nicht in e
// sind
//  e: Ein Editor mit elementen die nicht enthalten sein sollen
Editor Editor::without(Editor e) const
{
    RCArray<Element>* list = new RCArray<Element>();
    for (auto i : *elements)
    {
        bool found = 0;
        for (auto j : *e.elements)
            found |= i == j;
        if (!found) list->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return Editor(list);
}

// Ruft eine funktion fuer jedes Element auf
//  f: die funktion (nimmt als argument ein Element objekt ohne erhoehten
//  reference Counter)
void Editor::forEach(std::function<void(Element*)> f) const
{
    for (auto i : *elements)
        f(i);
}

//! gibt 1 zurueck, wenn mindestens ein Element gefunden wurde
bool Editor::exists() const
{
    return elements->getEintragAnzahl() > 0;
}

//! gibt die anzahl der ausgewaehlten elemente zurueck
int Editor::getSize() const
{
    return elements->getEintragAnzahl();
}

DLLEXPORT Editor& Framework::XML::Editor::operator=(const Editor& e)
{
    if (this != &e)
    {
        this->elements->leeren();
        for (auto i : *e.elements)
            this->elements->add(dynamic_cast<XML::Element*>(i->getThis()));
    }
    return *this;
}
