text: #ifndef CGFW_EVENT_DISPATCHER_H_
  #define CGFW_EVENT_DISPATCHER_H_
  
  #include <algorithm>
  #include <functional>
  #include <iostream>
  #include <list>
  #include <map>
  #include <memory>
  #include <sstream>
  #include <string>
  #include <type_traits>
  #include <typeindex>
  #include <unordered_map>
  
  namespace cgfw {
  
  class Event {
    public:
      Event() = default;
  
      bool isImmediatePropagationStopped() const {
          return m_immediatedPropagationStopped;
      }
  
      void stopImmediatePropagation() {
          m_immediatedPropagationStopped = true;
          m_propagationStopped = true;
      }
  
      bool isPropagationStopped() const { return m_propagationStopped; }
  
      void stopPropagation() { m_propagationStopped = true; }
  
    protected:
      bool m_immediatedPropagationStopped = false;
      bool m_propagationStopped = false;
  };
  
  class SpecificEventDispatcherBase {
    public:
      using callback_identifyer_t = std::string;
  };
  
  template <typename T,
            typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
  class SpecificEventDispatcher : public SpecificEventDispatcherBase {
  
      typedef void(callback_t)(T&);
  
    public:
      void dispatch(T& t) {
          for (auto& pair : m_eventListener) {
              if (t.isImmediatePropagationStopped()) {
                  break;
              }
              pair.second(t);
          }
      }
  
      void addListener(const callback_identifyer_t& key,
                       std::function<void(T&)> f) {
  
          callback_identifyer_t tmpKey = key;
  
          // If the key is for a lambda expression, an contiguous index has to be
          // appended.
          // TODO: this maybe should be in EventDispatcher using a static variable
          // in the function
          if (tmpKey == "l") {
              tmpKey = tmpKey + ":" + std::to_string(m_lamdaIdx++);
          }
  
          m_eventListener.erase(
              std::remove_if(m_eventListener.begin(), m_eventListener.end(),
                             [&](auto& pair) { return pair.first == key; }),
              m_eventListener.end());
  
          m_eventListener.push_back(std::make_pair(tmpKey, f));
      }
  
      void removeListener(const callback_identifyer_t& key) {
  
          m_eventListener.erase(
              std::remove_if(m_eventListener.begin(), m_eventListener.end(),
                             [&](auto& pair) { return pair.first == key; }),
              m_eventListener.end());
  
          // m_eventListener.erase(key);
      }
  
    protected:
      unsigned int m_lamdaIdx = 1;
      std::list<std::pair<callback_identifyer_t, std::function<void(T&)>>>
          m_eventListener;
  };
  
  template <typename T,
            typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
  SpecificEventDispatcherBase::callback_identifyer_t
  get_callback_type(void (*f)(T&)) {
      std::stringstream cb_key;
      cb_key << "f:";
  
      for (size_t i = 0; i < sizeof f; i++) {
  
          cb_key << std::hex << (int)(reinterpret_cast<unsigned char*>(&f))[i];
      }
  
      return cb_key.str();
  }
  
  template <typename T,
            typename C,
            typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
  SpecificEventDispatcherBase::callback_identifyer_t
  get_callback_type(void (C::*f)(T&), C* obj) {
      std::stringstream cb_key;
      cb_key << "m:";
  
      for (size_t i = 0; i < sizeof f; i++) {
  
          cb_key << std::hex << (int)(reinterpret_cast<unsigned char*>(&f))[i];
      }
  
      cb_key << ":";
  
      for (size_t i = 0; i < sizeof obj; i++) {
  
          cb_key << std::hex << (int)(reinterpret_cast<unsigned char*>(&obj))[i];
      }
  
      return cb_key.str();
  }
  
  class EventDispatcher {
  
    public:
      template <
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void addListener(std::function<void(T&)> f) {
          // TODO this will crash if std::bind is passed
          auto typeIdx = std::type_index(typeid(T));
          if (m_specificDispatcher.find(typeIdx) == m_specificDispatcher.end()) {
              m_specificDispatcher.insert(std::make_pair(
                  typeIdx, std::make_shared<SpecificEventDispatcher<T>>()));
          }
  
          // TODO set the l with idx inb the get_callback_type ?
          SpecificEventDispatcherBase::callback_identifyer_t key;
          auto fnTarget = f.template target<void (*)(T&)>();
          if (fnTarget != nullptr) {
              key = get_callback_type(*fnTarget);
          } else {
              key = "l";
          }
  
          std::static_pointer_cast<SpecificEventDispatcher<T>>(
              m_specificDispatcher[typeIdx])
              ->addListener(key, f);
      }
  
      /*template <typename T, typename = typename std::enable_if<
                                std::is_base_of<Event, T>::value>>
      inline void addListener(void (*f)(T&)) {
          addListener(std::function<void(Event&)>(f));
      }*/
  
      template <
          typename C,
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void addListener(void (C::*f)(T&), C* obj) {
          using namespace std::placeholders;
          auto typeIdx = std::type_index(typeid(T));
          if (m_specificDispatcher.find(typeIdx) == m_specificDispatcher.end()) {
              m_specificDispatcher.insert(std::make_pair(
                  typeIdx, std::make_shared<SpecificEventDispatcher<T>>()));
          }
          std::static_pointer_cast<SpecificEventDispatcher<T>>(
              m_specificDispatcher[typeIdx])
              ->addListener(get_callback_type(f, obj),
                            std::bind(f, obj, std::placeholders::_1));
  
          ;
          // add using key
      }
  
      template <
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void removeListener(std::function<void(T&)> f) {
          auto typeIdx = std::type_index(typeid(T));
          if (m_specificDispatcher.find(typeIdx) != m_specificDispatcher.end()) {
              std::static_pointer_cast<SpecificEventDispatcher<T>>(
                  m_specificDispatcher[typeIdx])
                  ->removeListener(
                      get_callback_type(*f.template target<void (*)(T&)>()));
          }
      }
  
      template <
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void removeListener(void (*f)(T&)) {
          removeListener(std::function<void(Event&)>(f));
      }
  
      template <
          typename C,
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void removeListener(void (C::*f)(T&), C* obj) {
          auto typeIdx = std::type_index(typeid(T));
          if (m_specificDispatcher.find(typeIdx) != m_specificDispatcher.end()) {
              std::static_pointer_cast<SpecificEventDispatcher<T>>(
                  m_specificDispatcher[typeIdx])
                  ->removeListener(get_callback_type(f, obj));
          }
      }
  
      template <
          typename T,
          typename = typename std::enable_if<std::is_base_of<Event, T>::value>>
      inline void dispatch(T& t) {
          auto typeIdx = std::type_index(typeid(T));
          if (m_specificDispatcher.find(typeIdx) != m_specificDispatcher.end()) {
  
              std::static_pointer_cast<SpecificEventDispatcher<T>>(
                  m_specificDispatcher[typeIdx])
                  ->dispatch(t);
          }
      }
  
    protected:
      std::unordered_map<std::type_index,
                         std::shared_ptr<SpecificEventDispatcherBase>>
          m_specificDispatcher;
  };
  
  class InputEventDispatcher {};
  
  } // namespace cgfw
  
  #endif
  