#ifndef ZNODE_PAYLOAD_H
#define ZNODE_PAYLOAD_H

#include <vector>
#include <map>

namespace znode
{
  template<typename str_t>
  class zstringmap : public std::map<str_t,str_t>
  {

  public:

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

    using zvalue       = str_t; // could be any or variant
    // __fixme: should this be a vector? or a maptor?
    //using zattributes  = std::map<str_t, zvalue>;
    using zattributes  = zstringmap<str_t>;
  
    str_cref key_of(str_cref value) const
	{
        for( const auto& pair : *this )
        {
            if (pair.second == value)
                return pair.first;
        }

        static str_t s_dummy;
        return s_dummy;
    }
  
  };
  
  template<typename str_t>
  class zpayload
  {

  public:

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

    using zvalue       = str_t; // could be any or variant
    // __fixme: should this be a vector? or a maptor?
    //using zattributes  = std::map<str_t, zvalue>;
    using zattributes  = zstringmap<str_t>;
    using callmap_ref  = std::map<str_t, str_cref (zpayload<str_t>::*)() const>;
    using callmap_ptr  = std::map<str_t, str_ptr  (zpayload<str_t>::*)() const>;

    static const str_t cTagName; // = "TagName";
    static const str_t cType;//  = "Type";
    static const str_t cName;//  = "Name";
    static const str_t cValue;// = "Value";
    static const str_t cFriendlyName;

  protected:

    str_t       _tag_name;
    str_t       _value;
    zattributes _attributes;

    static zvalue s_dummy_value;

  public:

    zpayload( str_cref tag_name )
        : _tag_name{tag_name}
    {}

    // ...
    zpayload( str_cref tag_name, str_cref value )
        : _tag_name{tag_name}, _value{value}
    {}

    zpayload( str_cref tag_name, const str_list& attriblist )
        : zpayload(tag_name)
    {
      for( str_cref entry : attriblist )
      {
        str_t key_value, data_value;
        if( xstr_split_by( entry, "=", key_value, data_value) )
          set_attribute( key_value, data_value );
      }
    }

    str_cref tag_name() const
    {
      return _tag_name;
    }

    str_ptr tag_name_ptr()
    {
      return &_tag_name;
    }

    void set_tag_name( str_cref tag_name )
    {
      _tag_name = tag_name;
    }

    str_cref tag_value() const
    {
      return _value;
    }

    str_ptr tag_value_ptr()
    {
      return &_value;
    }

    void set_tag_value( str_cref value )
    {
      _value = value;
    }

    str_cref friendly_name() const
    {
      return attribute(cFriendlyName);
    }

    str_ptr friendly_name_ptr()
    {
      return &attribute(cFriendlyName);
    }

    str_cref type() const
    {
      return attribute(cType);
    }

    str_ptr type_ptr() const
    {
      return &attribute(cType);
    }

    void set_type( str_cref type )
    {
      set_attribute( cType, type);
    }

    str_cref name() const
    {
      return attribute(cName);
    }

    str_ptr name_ptr()
    {
      return &attribute(cName);
    }

    void set_name( str_cref name )
    {
      set_attribute( cType, name);
    }

    const zattributes& attributes() const
    {
      return _attributes;
    }

    bool has_attribute(str_cref key ) const
    {
      if( xstr_is_empty( key ) )
        return false;
      typename zattributes::const_iterator pos = _attributes.find(key);
      if( pos == _attributes.end() )
        return false;
      // leer gilded nicht
      if( xstr_is_empty( pos->second ) )
        return false;
      return true;
    }

    bool test_attribute(str_cref key, str_cref value ) const
    {
      if( xstr_is_empty( key ) )
        return false;
      typename zattributes::const_iterator pos = _attributes.find(key);
      if( pos == _attributes.end() )
        return false;
      if( !xstr_is_empty( value ) )
        return (*pos).second == value;
      return true;
    }

    const zvalue& fixed_attribute(str_cref key ) const
    {

      static callmap_ref s_fixed_attributes
      {
        { cTagName, &zpayload::tag_name },
        { cValue,   &zpayload::tag_value }
      };

      auto it = s_fixed_attributes.find(key);
      if (it != s_fixed_attributes.end())
        return (this->*(it->second))();

      return s_dummy_value;
    }

    const zvalue& attribute(str_cref key ) const
    {
      if( !xstr_is_empty( key ) )
      {
        typename zattributes::const_iterator pos = _attributes.find(key);
        if (pos != _attributes.end())
        {
          const zvalue& result = (*pos).second;
          if (!xstr_is_empty(result) && result[0] == '@')
            return attribute( xstr_sub_str(result, 1) );
          return result;
        }
      }
      //return s_dummy_value;
      return fixed_attribute( key );
    }


    const zvalue& fixed_attribute_ptr(str_cref key ) const
    {
      static callmap_ptr s_fixed_attributes_ptr
      {
        { cTagName, &zpayload<str_t>::tag_name_ptr },
        { cValue,   &zpayload<str_t>::tag_value_ptr }
      };

      auto it = s_fixed_attributes_ptr.find(key);
      if (it != s_fixed_attributes_ptr.end() )
        return (this->*(it->second))();

      return &s_dummy_value;
    }

    const zvalue* attribute_ptr(str_cref key ) const
    {
      if( !xstr_is_empty( key ) )
      {
        typename zattributes::const_iterator pos = _attributes.find(key);
        if (pos != _attributes.end())
        {
          const zvalue& result = (*pos).second;
          if (!xstr_is_empty(result) && result[0] == '@')
            return attribute_ptr( xstr_sub_str(result, 1) );
          return &result;
        }
      }
      //return fixed_attribute_ptr( key );
      return &s_dummy_value;
    }

    void set_attribute(str_cref key, zvalue value )
    {
      if( !xstr_is_empty( key ) )
        _attributes[key] = value;
    }

    // dumper
    str_t to_string() const
    {
      str_t result = '<' +_tag_name +'>';
      if( !xstr_is_empty(_value) )
        result += ": " + _value;
      if( !_attributes.empty() )
      {
        result += " [";
        bool beg = true;
        for (const auto& entry : _attributes)
        {
          if(beg)
          {
            result +=  entry.first + "=" + entry.second;
            beg = false;
            continue;
          }
          result += ", " + entry.first + "=" + entry.second;
        }
        result += "]";
      }
      return result;
    }

    // forward helpers
    bool  xstr_split_by(str_cref entry, str_cref sep, str_t& key, str_t& value );
    str_t xstr_sub_str(str_cref entry, int pos) const;
    bool  xstr_is_empty(str_cref key ) const;

  };

  // init statics
  template<typename str_t>
  str_t zpayload<str_t>::s_dummy_value;

}

#endif // ZNODE_PAYLOAD_H
