#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 )
{
    ref = 1;
    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;
                            while( start < text->getLength() )
                            {
                                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 '/':
                                        if( !inString1 && !inString2 )
                                        {
                                            lastSlash = 1;
                                            if( lastOpen )
                                                openSlash = 1;
                                        }
                                        esc = 0;
                                        lastSlash = 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->getIterator(), j = attributeValues->getIterator(); 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->getIterator(); i; i++ )
        removeChild( i->getThis() );
    childs->release();
}

// entfernt dieses Element vom Eltern element
void Element::remove()
{
    if( parent )
        parent->removeChild( 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 ? 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->getIterator();
}

// gibt einen selector zur�ck der alle childs beinhaltet
Editor Element::selectChildren() const
{
    return Editor( 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->getIterator(); i; i++ )
    {
        if( i->getName().istGleich( name ) )
            tmp->add( 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->getIterator(); i; i++ )
    {
        if( i->hasAttribute( attribute ) )
            tmp->add( 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->getIterator(); i; i++ )
    {
        if( i->hasAttribute( attribute ) && i->getAttributeValue( attribute ).istGleich( value ) )
            tmp->add( 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->getIterator(); i; i++ )
    {
        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->getIterator(), j = attributeValues->getIterator(); 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->getIterator();
}

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

// 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->getIterator(), j = attributeValues->getIterator(); 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->getIterator(); i; i++ )
                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() );
}

// erh�ht den reference Counter um 1 und gibt this zur�ck
Element *Element::getThis()
{
    ref++;
    return this;
}

// verringert den reference Counter um 1 und gibt 0 zur�ck
Element *Element::release()
{
    if( !--ref )
        delete this;
    return 0;
}


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

Editor::Editor( const Editor &e )
    : Editor( 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->getIterator(); i; i++ )
        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->getIterator(); i; i++ )
        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->getIterator(); i; i++ )
        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->getIterator(); i; i++ )
        i->removeChild( 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->getIterator(); j; j++ )
        j->removeChild( i );
}

// entfernt alle childs (auf allen elementen in der Liste)
void Editor::removeAllChilds()
{
    for( auto i = elements->getIterator(); i; i++ )
        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->getIterator(); i; i++ )
        i->removeChilds( childs->getThis() );
    childs->release();
}

// entfernt dieses Element vom Eltern element (auf allen elementen in der Liste)
void Editor::remove()
{
    for( auto i = elements->getIterator(); i; i++ )
        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->getIterator(); i; i++ )
        i->setText( text );
}

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

// gibt einen selector zur�ck der alle childs beinhaltet
Editor Editor::selectChildren() const
{
    RCArray<Element> *list = new RCArray<Element>();
    for( auto i = elements->getIterator(); i; i++ )
    {
        for( auto j = i->selectChildren().getIterator(); j; j++ )
        {
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->parent )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->getName().istGleich( name ) )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->selectChildsByName( name ).elements->getEintragAnzahl() )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->selectChildsByAttribute( attribute ).elements->getEintragAnzahl() )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->selectChildsByAttribute( attribute, value ).elements->getEintragAnzahl() )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->hasAttribute( attribute ) )
            list->add( 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->getIterator(); i; i++ )
    {
        if( i->hasAttribute( attribute ) && i->getAttributeValue( attribute ).istGleich( value ) )
            list->add( 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->getIterator(); i; i++ )
    {
        bool found = 0;
        for( auto j = e.elements->getIterator(); j; j++ )
            found |= i._ == j._;
        if( !found )
            list->add( 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->getIterator(); i; i++ )
    {
        f( i );
    }
}