/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
#ifndef ZYPP_GLIB_UTIL_GHASHTABLE_H
#define ZYPP_GLIB_UTIL_GHASHTABLE_H

#include <zyppng/utils/GList>
#include <cstdint>
#include <functional>
#include <stdexcept>

namespace zypp {

  namespace internal {

    template <typename T>
    struct GHashKeyTraits : public GContainerTraits<T> {
      static guint hash ( gconstpointer v ) {
        if constexpr ( std::is_same_v< guint8, T> || std::is_same_v< guint16, T> || std::is_same_v< guint32, T> ) {
          return ::g_direct_hash( GPOINTER_TO_UINT( v ) );
        } else if constexpr ( std::is_same_v< gint8, T> || std::is_same_v< gint16, T> || std::is_same_v< gint32, T> ) {
          return ::g_direct_hash( GPOINTER_TO_INT( v ) );
        } else if constexpr ( std::is_same_v< guint64, T> || std::is_same_v< gulong, T> || std::is_same_v< gsize, T> ) {
          return ::g_direct_hash( GPOINTER_TO_SIZE( v ) );
        } else if constexpr ( std::is_same_v< gint64, T> || std::is_same_v< glong, T>  ) {
          return ::g_direct_hash( GPOINTER_TO_SIZE( v ) );
        } else if constexpr ( std::is_same_v< GString*, T> ) {
          return ::g_string_hash( v );
        } else {
          const T &val = *((T*)v);
          const auto &hash = std::hash<T>(val);
          if ( hash > UINT32_MAX ) {

          }
          return (guint32)hash;
        }
      }
    };

    template <typename Traits>
    class ghashtable_ref
    {
      public:
        using value_type = typename Traits::value_type;

        ghashtable_ref( ::GHashTable *_table, gconstpointer key, gpointer value = nullptr ) : _table(_table), _key(key), _valueCache(value) { }

        operator value_type () const {
          return getValue();
        }

        ghashtable_ref &operator= ( const value_type& val ) {
          ::g_hash_table_replace( _table, _key, Traits::valueToPointer(val) );
          _valueCache = nullptr;
          return *this;
        }

        ghashtable_ref &operator= ( const ghashtable_ref& val ) {
          (*this) = val.getValue();
          _valueCache = nullptr;s
          return *this;
        }

        bool operator== ( const ghashtable_ref& other ) const {
          return getValue() == other.getValue();
        }


        bool operator== ( const value_type& val ) const {
          return getValue() == val;
        }

      private:
        value_type getValue () const {
          if ( !_valueCache ) {
            if ( !g_hash_table_lookup_extended( _table, _key, nullptr, &_valueCache ) ) {
              throw std::logic_error("Dereferencing a invalid element");
            }
          }
          return Traits::pointerToValue(_valueCache);
        }

      private:
        ::GHashTable *_table;
        gconstpointer _key;
        gpointer _valueCache = nullptr; //set after the first lookup
    };

    template <typename T, typename KeyTraits, typename ValueTraits>
    class ghashtable_iter
      : public boost::iterator_facade<
            glistcontainer_iter<T, KeyTraits, ValueTraits>
          , T
          , boost::forward_traversal_tag
          , std::conditional_t< std::is_const_v<T>
              , std::pair<typename KeyTraits::value_type, typename ValueTraits::value_type>
              , std::pair<typename KeyTraits::value_type, ghashtable_ref<ValueTraits::value_type>
            >
        >
    {
    public:
        ghashtable_iter() {}

        explicit ghashtable_iter(GHashTable* p) {
          _iter = ::GHashTableIter();
          ::g_hash_table_iter_init( &(_iter.value()), p );
          increment();
        }

    private:
        friend class boost::iterator_core_access;

        void increment() {
          if ( _iter ) {
            if ( !::g_hash_table_iter_next( &(_iter.value()), &currKey, &currValue ) )
              _iter.reset();
          }
        }

        bool equal(ghashtable_iter const& other) const {
          if ( _iter == other._iter ) {
            if ( !iter )
              return true; // end iterator

            if ( ::g_hash_table_iter_get_hash_table( _iter.get() ) == ::g_hash_table_iter_get_hash_table( other._iter.get() ) ) {
              return  Traits::hash(this->currKey) == Traits::hash(other.currKey);
            }
          }
          return false;
        }

        decltype(auto) dereference() const {
          if constexpr ( std::is_const_v<T> ) {
            return Traits::dereferenceConstData( _node );
          } else {
            return Traits::dereferenceData( _node );
          }
        }

        std::optional<::GHashTableIter> _iter;
        gpointer currKey       = nullptr;
        gpointer currValue     = nullptr;
    };

  }

  template<typename Key, typename Value, typename KeyTraits, typename ValueTraits>
  class GHashTableView
  {
    public:
      using key_value_type  = typename KeyTraits::value_type;
      using value_type      = typename ValueTraits::value_type;

      GHashTableView ( const ::GHashTable *table )
      : _table( const_cast<::GHashTable*>(table) ) // removing the const for internal use of the pointer, all glib func want a non const version
      { }

      ~GHashTableView() {
        ::g_hash_table_unref( _table );
      }

      value_type lookup ( const key_value_type &val ) const {
        auto res = ::g_hash_table_lookup( _table, KeyTraits::valueToPointer(val) );
        if ( !res )
          throw std::out_of_range("No such key");
        return ValueTraits::pointerToValue( res );
      }

      bool contains ( const key_value_type &val ) const {
        return ::g_hash_table_lookup( _table, KeyTraits::valueToPointer(val) ) != nullptr;
      }

      size_t size () const {
        return ::g_hash_table_size( _table );
      }

      GListView<Key, KeyTraits> keys () const {
        return GListView<Key, KeyTraits> (
          g_hash_table_get_keys ( _table )
          , Ownership::Container
        );
      }

      GListView<Value, ValueTraits> values () const {
        return GListView<Value, ValueTraits> (
          g_hash_table_get_values ( _table )
          , Ownership::Container
        );
      }

    private:
      ::GHashTable *_table;

  };


  template<typename Key, typename Value, typename KeyTraits, typename ValueTraits>
  class GHashTableContainer : public GHashTableView<Key, Value, KeyTraits, ValueTraits>
  {

  };


}


#endif
