#include "XML.h"

using namespace Framework;
using namespace XML;

// Erstellt ein XML Element
//  string: entweder der name des Elements oder ein XML Text der geparsed werden
//  soll
Element::Element(Text string)
    : Element(string, 0)
{}

// Erstellt ein XML Element
//  string: entweder der name des Elements oder ein XML Text der geparsed werden
//  soll zParent: Ein Zeiger auf das eltern element (ohne erhöhten reference
//  Counter)
Element::Element(Text string, Element* zParent)
    : ReferenceCounter()
{
    children = new RCArray<Element>();
    attributes = new RCArray<Text>();
    attributeValues = new RCArray<Text>();
    text = new Text();
    name = new Text();
    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] != '-')
            {
                nameEnd = i;
                break;
            }
        }
        name->setText(string.getTeilText(1, nameEnd));
        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] != '-')
                    {
                        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;
                            }
                        }
                        value.setText(string.getTeilText(start, nameEnd - 1));
                        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;
                            }
                        }
                        value.setText(string.getTeilText(start, nameEnd - 1));
                        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()]
                        == '<')
                    {
                        text->setText(string.getTeilText(nameEnd + 1,
                            string.getLength() - 3 - name->getLength()));
                        // 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();
}

// ändert ein attribut oder fügt eines hinzu
//  attribut: Der Name des Attributes
//  value: Der Wert des Attributes
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));
}

// entfernt ein attribut
//  attribut: Der Name des Attributes
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--;
        }
    }
}

// fügt ein child hinzu
//  child: Das neue Child Element
void Element::addChild(Element* child)
{
    child->parent = this;
    children->add(child);
}

// fügt ein child hinzu
//  child: Das neue Child Element
void Element::addChildAtFront(Element* child)
{
    child->parent = this;
    children->add(child, 0);
}

// entfernt ein child
//  zChild: das zu entfernende Child
void Element::removeChild(Element* child)
{
    for (int i = 0; i < children->getEintragAnzahl(); i++)
    {
        if (children->z(i) == child)
        {
            children->remove(i);
            i--;
        }
    }
    child->release();
}

// entfernt das i-te child
//  i: der Index des childs (bei 0 beginnend)
void Element::removeChild(int i)
{
    children->remove(i);
}

// entfernt alle childs
void Element::removeAllChilds()
{
    children->leeren();
}

// entfernt eine Liste mit childs
//  childs: alle Childs die entfernt werden sollen
void Element::removeChilds(RCArray<Element>* childs)
{
    for (auto i : *childs)
        removeChild(dynamic_cast<XML::Element*>(i->getThis()));
    childs->release();
}

// entfernt dieses Element vom Eltern element
void Element::remove()
{
    if (parent) parent->removeChild(dynamic_cast<XML::Element*>(getThis()));
}

// setzt den Text in dem Element falls es keine childs gibt
//  text: dert Text
void Element::setText(Text text)
{
    this->text->setText(text);
}

// gibt den Text im Element zurück
Text Element::getText() const
{
    return text->getText();
}

// gibt die Anzahl der Childs zurück
int Element::getChildCount() const
{
    return children->getEintragAnzahl();
}

// gibt das i-te child zurück
Element* Element::getChild(int i) const
{
    return children->get(i);
}

// gibt das i-te child zurück (ohne erhöhten reference Counter)
Element* Element::zChild(int i) const
{
    return children->z(i);
}

// gibt das parent element zurück
Element* Element::getParent() const
{
    return parent ? dynamic_cast<Element*>(parent->getThis()) : 0;
}

// gibt das parent element zurück (ohne erhöhten reference Counter)
Element* Element::zParent() const
{
    return parent;
}

// gibt einen iterator zurück mit dem durch alle childs iteriert werden kann
Iterator<Element*> Element::getChilds() const
{
    return children->begin();
}

// gibt einen selector zurück der alle childs beinhaltet
Editor Element::selectChildren() const
{
    return Editor(dynamic_cast<RCArray<XML::Element>*>(children->getThis()));
}

// gibt eine Liste mit childs zurück, die einen bestimmten Namen haben
//  name: der name der Childs
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);
}

// gibt eine Liste mit childs zurück, die ein bestimmtes Attribut haben
//  attribute: der name des Attributes
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);
}

// gibt eine Liste mit childs zurück, die ein bestimmtes Attribut mit einem
// bestimmten wert haben
//  attribute: der name des Attributes
//  value: der Wert des Attributes
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);
}

// gibt 1 zurück, falls ein Attribut Name existiert, 0 sonnst
bool Element::hasAttribute(Text name) const
{
    for (auto i : *attributes)
    {
        if (i->istGleich(name)) return 1;
    }
    return 0;
}

// gibt die Anzahl der Attribute zurück
int Element::getAttributeCount() const
{
    return attributes->getEintragAnzahl();
}

// gibt den Namen des i-ten Attributes zurück
Text Element::getAttributeName(int i) const
{
    return attributes->z(i)->getText();
}

// gibt den Wert des i-ten Attributes zurück
Text Element::getAttributeValue(int i) const
{
    return attributeValues->z(i)->getText();
}

// gibt den Wert eines Attributes zurück
//  attribut: Der Name des Attributes
Text Element::getAttributeValue(Text attribut) const
{
    for (auto i = attributes->begin(), j = attributeValues->begin(); i && j;
         i++, j++)
    {
        if (i->istGleich(attribut)) return j->getText();
    }
    return "";
}

// gibt einen iterator zurück mit dem durch alle Attribut Namen iteriert werden
// kann
Iterator<Text*> Element::getAttributeNames() const
{
    return attributes->begin();
}

// gibt einen iterator zurück mit dem durch alle Attribut Werte iteriert werden
// kann
Iterator<Text*> Element::getAttributeValues() const
{
    return attributeValues->begin();
}

// gibt den Namen des Elementes zurück zurück
Text Element::getName() const
{
    return name->getText();
}

// erzeugt einen XML Text der dieses Element und alle childs beinhaltet
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->getLength())
        {
            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;
}

// Erzeugt eine Kopie ohne referenzen auf dieses objekt
Element* Element::dublicate() const
{
    return new Element(toString());
}

// Erzeugt einen neuen XML Editor mit einer Liste von Objekten die editiert
// werden sollen
Editor::Editor(RCArray<Element>* elements)
    : ReferenceCounter()
{
    this->elements = new RCArray<Element>();
    for (auto i : *elements)
        this->elements->add(dynamic_cast<XML::Element*>(i->getThis()));
    elements->release();
}

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

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

// ändert ein attribut oder fügt eines hinzu (auf allen elementen in der Liste)
//  attribut: Der Name des Attributes
//  value: Der Wert des Attributes
void Editor::setAttribute(Text attribut, Text value)
{
    for (auto i : *elements)
        i->setAttribute(attribut, value);
}

// entfernt ein attribut (auf allen elementen in der Liste)
//  attribut: Der Name des Attributes
void Editor::removeAttribute(Text attribut)
{
    for (auto i : *elements)
        i->removeAttribute(attribut);
}

// fügt ein child hinzu (auf allen elementen in der Liste)
//  child: Das neue Child Element
void Editor::addChild(Element* child)
{
    for (auto i : *elements)
        i->addChild(child->dublicate());
    child->release();
}

// entfernt ein child (auf allen elementen in der Liste)
//  zChild: das zu entfernende Child
void Editor::removeChild(Element* child)
{
    for (auto i : *elements)
        i->removeChild(dynamic_cast<XML::Element*>(child->getThis()));
    child->release();
}

// entfernt das i-te child (auf allen elementen in der Liste)
//  i: der Index des childs (bei 0 beginnend)
void Editor::removeChild(int i)
{
    for (auto j : *elements)
        j->removeChild(i);
}

// entfernt alle childs (auf allen elementen in der Liste)
void Editor::removeAllChilds()
{
    for (auto i : *elements)
        i->removeAllChilds();
}

// entfernt eine Liste mit childs (auf allen elementen in der Liste)
//  childs: alle Childs die entfernt werden sollen
void Editor::removeChilds(RCArray<Element>* childs)
{
    for (auto i : *elements)
        i->removeChilds(
            dynamic_cast<RCArray<XML::Element>*>(childs->getThis()));
    childs->release();
}

// entfernt dieses Element vom Eltern element (auf allen elementen in der Liste)
void Editor::remove()
{
    for (auto i : *elements)
        i->remove();
}

// setzt den Text in dem Element falls es keine childs gibt (auf allen elementen
// in der Liste)
//  text: dert Text
void Editor::setText(Text text)
{
    for (auto i : *elements)
        i->setText(text);
}

// Gibt ein Iterator durch alle Elemente zurück
Iterator<Element*> Editor::begin()
{
    return elements->begin();
}

//! Gibt das ende des iterators zurück
Iterator<Element*> Editor::end()
{
    return elements->end();
}

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

// gibt einen selector zurück 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 zurück, 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 zurück, 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 zurück, 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 zurück, 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 zurück, 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 zurück, 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 zurück welcher nurnoch die Elemente enthält 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 für jedes Element auf
//  f: die funktion (nimmt als argument ein Element objekt ohne erhöhten
//  reference Counter)
void Editor::forEach(std::function<void(Element*)> f) const
{
    for (auto i : *elements)
        f(i);
}

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

//! gibt die anzahl der ausgewählten elemente zurück
int Editor::getSize() const
{
    return elements->getEintragAnzahl();
}