/***************************************************************************

    source::worx xtree
    Copyright © 2024-2025 c.holzheuer
    christoph.holzheuer@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

***************************************************************************/


#ifndef ZNODE_H
#define ZNODE_H


#include <vector>
#include <memory>
#include <QString>
#include <QDebug>
#include <xqmaptor.h>


#include <znode_iterator.h>
//#include <znode_vector.h>
#include <znode_id.h>
#include <znode_payload.h>

namespace znode
{

  /*

    - die payload attributes wird 'drangeerbt'. sollte die nicht besser ge-templatet sein?
    - die finder sollten besser ausgelagert sein

   */

    //! tree-klasse, besonderheit: der nutzlast & string-type sind templated.
    template<typename str_t, typename payload_t>
    class zbasic_node : public zid, public payload_t, public std::enable_shared_from_this<zbasic_node<str_t,payload_t>>
    {

    public:

      using zweak_node   = std::weak_ptr<zbasic_node>;
      using zshared_node = std::shared_ptr<zbasic_node>;
      using znode_list   = std::vector<zshared_node>;
      using zshared_cref = const std::shared_ptr<zbasic_node>&;

      using str_cref     = const str_t&;
      using str_list     = std::vector<str_t>;

      using ziterator    = znode_iterator<zbasic_node>;

    protected:

      zweak_node   _parent;
      znode_list   _children;

      // functor, der auf pointer gleichheit prüft.
      struct match_node
      {
        match_node( zbasic_node* match )
            : _match{match}
        {}

        bool operator()( const zshared_node& node )
        {
          return _match == node.get();
        }

        zbasic_node* _match{};
      };

    public:

      //! shortcut auf std::make_shared...
      //! beachte: der eltern-knoten wird hier nicht gesetzt, der neue knoten
      //! wird nirgends eingefügt.
      static zshared_node make_node( str_cref arg1, str_cref arg2 = "" )
      {
        return std::make_shared<zbasic_node>( arg1, arg2 );
      }

      //! leerer konstruktor
      zbasic_node() = default;

      //! konstruktor mit tag_name und optionalem elternknoten
      zbasic_node( str_cref tag_name, zshared_cref parent = nullptr )
        : payload_t{tag_name}, _parent{parent}
      {

      }

      //! konstruktor mit tag_name, value und optionalem elternknoten
      zbasic_node( str_cref tag_name, str_cref value, zshared_cref parent = nullptr )
         : payload_t{tag_name,value}, _parent{parent}
      {

      }

      //! konstruktor mit tag_name, attributlist und optionalem elternknoten
      zbasic_node( str_cref tag_name, const str_list& attributes, zshared_cref parent = nullptr )
        : zbasic_node{tag_name, parent}
      {
        for( str_cref entry : attributes )
        {
            str_t key_value, data_value;
            if( xstr_split_by( entry, "=", key_value, data_value) )
                set_attribute( key_value, data_value );
        }
      }

      //! default destruktor
      virtual ~zbasic_node() = default;

      //! kopieren ist hier nicht sinnvoll
      zbasic_node(const zbasic_node&)            = delete;
      zbasic_node& operator=(const zbasic_node&) = delete;

      //! 'move' geht (shared_from_this bleibt gültig)
      zbasic_node(zbasic_node&&) noexcept            = default;
      zbasic_node& operator=(zbasic_node&&) noexcept = default;

      //! erzeugt eine deep-copy von 'mir' _mit_ allen kindknoten
      virtual zshared_node clone(const zshared_node& parent = nullptr  ) const
      {       
        // copy als shared ptr erzeugen
        zshared_node new_me = std::make_shared<zbasic_node>(this->_tag_name, this->_value );
        // auch die attribute kopieren ...
        new_me->_attributes = this->_attributes;
        // ... und, so vorhanden, den parent-node.
        new_me->_parent = parent;
        // kinder kopieren
        for (const zshared_node& child : _children)
          new_me->_children.push_back( child->clone( new_me ) );

        return new_me;

      }

      //! stl-ish traverse iterators
      auto begin()
      {
        return ziterator(this);
      }

      //! stl-ish traverse iterators
      auto end()
      {
        return ziterator(nullptr);
      }

      // ... set_int
      // ... as_int

      //! erzeugt einen shared_ptr
      zshared_node parent() const
      {
        return _parent.lock();
      }

      //! gibt den nachfolge-knoten oder 'end()' zurück.
      zshared_node sibling()
      {
        if( parent() )
        {
          znode_list& childs = _parent->_children;
          auto it = std::find( childs.begin(), childs.end(), this->shared_from_this() );
          return *(it);
        }
        throw std::runtime_error("sibling(): no parent node");
      }

      //! gibt den vector mit kind-knoten zurück
      inline const znode_list& children() const
      {
        return _children;
      }

      //! testet, ob kinder vorhanden sind.
      bool has_children() const
      {
        return !children().empty();
      }

      bool has_child(int idx) const
      {
        return (idx>-1 && idx<children().size());
      }

      //! testet, ob kinder vorhanden sind.
      bool has_child(str_cref tagname ) const
      {
        return child(tagname ) != nullptr;
      }

      //! gibt das erste kind zurück
      zshared_node first_child()
      {
        if(!children().empty())
          return children().front();
        return nullptr;
      }

      //! gibt das letzte kind oder nullptr zurück
      zshared_node last_child()
      {
        if(!children().empty())
          return children().back();
        return nullptr;
      }

      zshared_node child( int idx )
      {
        if(idx>-1 && idx<children().size())
          return _children[idx];
        return nullptr;
      }

      //
      zshared_node child(str_cref tagname ) const
      {
        for( auto child : _children )
        {
          if( child->tag_name() == tagname )
            return child;
        }
        return nullptr;
      }

      //! hängt einen knoten an meine kinderliste an.
      int add_child( const zshared_node& node )
      {
        _children.push_back( node );
        node->_parent = this->shared_from_this();
        return int(children().size() - 1);
      }

      //! fügt einen knoten in meine kinderliste ein und macht mich
      //! zu dessen elternknoten.
      int add_child_at( int idx, const zshared_node& node )
      {
        // _fixme! was ist, wenn da schon ein elternknoten ist?
        _children.insert(children().begin() + idx, node );
        node->_parent = this->shared_from_this();
        return int(children().size() - 1);
      }

      //! fügt einen shard_ptr von 'mir' in die kinderliste meines elternknotens ein.
      void add_me_at( int offset )
      {
        if( parent() )
          parent()->add_child_at( offset, this->shared_from_this() );
        else
          throw std::runtime_error("add_me_at(offset): no parent node");
      }

      //! fügt einen shared_ptr von 'mir' in die kinderliste des übergebenen knotens ein
      //! und macht diesen zu meinem elternknoten.
      void add_me_at( int offset, const zshared_node& parent_node )
      {
        if( parent_node )
        {
          _parent = parent_node;
          parent_node->add_child_at( offset, this->shared_from_this() );
        }
        else
        {
          throw std::runtime_error("add_me_at(offset,parent): no parent node");
        }
      }

      //! findet die eigene position im eltern-knoten
      int own_pos()
      {
        if( parent())
            return parent()->child_pos( this->shared_from_this() );
        return -1;
      }

      //! findet die postion eines kind-knotens
      int child_pos(const zshared_node& child)
      {
        //auto pos = std::find_if(children().begin(), children().end(), match_node(child) );
        auto pos = std::find(children().begin(), children().end(), child );
        if ( pos != children().end() )
          return std::distance( children().begin(), pos );
        return -1;
      }

      //! findet die postion eines kind-knotens
      zshared_node unlink_child( const zshared_node& node )
      {
        auto it = std::find(_children.begin(), _children.end(), node);
        if (it == _children.end())
          return nullptr;
        zshared_node removed = *it;
        // Parent-Zeiger im Kind zurücksetzen
        removed->_parent.reset();
        _children.erase(it);
        return removed;
      }

      //! entfernt 'mich' aus der kinderliste des elternknotens.
      void unlink_self()
      {
        if(parent())
          parent()->unlink_child( this->shared_from_this() );
        else
         throw std::runtime_error("unlink_self(): no parent node");
      }

      //! findet den ersten kind-knoten mit dem attribut 'attrkey' welches den
      //! wert 'attrvalue' hat.
      zshared_node find_child_by_attribute(str_cref attrkey, str_cref attrvalue ) const
      {
        for( auto child : _children )
        {
          if( child->has_attribute( attrkey, attrvalue ))
            return child;
        }
        return nullptr;
      }



      zshared_node find_child_by_id( int id ) const
      {
        for (auto child : _children )
        {
          if (child->_id == id)
            return child;
        }
        return nullptr;
      }

      // find ...
      // depth first ...
      // by attr
      // by child
      // by value
      // by find( auto xxx )
      // ...

    };

} //namespace znode


#endif // znode_H
