
#ifndef PLS_DATAFLOW_INTERNAL_INPUT_H_
#define PLS_DATAFLOW_INTERNAL_INPUT_H_

#include "pls/internal/base/error_handling.h"

#include "token.h"
#include "node.h"

namespace pls {
namespace dataflow {
namespace internal {

/**
 * Represents a single, logical input port (no data store, simply signal propagation).
 * @tparam T Type of the input port
 */
template<typename T>
class in_port {
  template<typename OT>
  friend
  class out_port;

 public:
  explicit in_port(node *owning_node) : owning_node_{owning_node} {};
  node *owning_node() const { return owning_node_; }
  bool is_connected() const { return connected_; }

 protected:
  virtual void token_pushed(token<T> token) = 0;

 private:
  bool connected_{false};
  node *const owning_node_;

  void push_token(token<T> token) {
    token_pushed(token);
  }

  void connect() {
    if (connected_) {
      PLS_ERROR("Must only connect on input once. Disconnect the output pointing to it before reconnecting.")
    }
    connected_ = true;
  }
};

/**
 * Represents multiple input ports bundled together (a tuple of inputs).
 * Allows for a unified callback method to handle multiple typed inputs.
 *
 * template<int POS, typename T>
 * void token_pushed(token<T> token) { Notified when tokens arrive }
 *
 * @tparam CB The class implementing a callback
 * @tparam N Put 0 to start the recursive implementation
 * @tparam I A variadic list of input types
 */
template<typename CB, int N, typename ...I>
class multi_in_port {
  // end of template recursion
 public:
  explicit multi_in_port(node *, CB *) {};

  bool fully_connected() const {
    return true;
  }
};
template<typename CB, int N, typename I0, typename ...I>
class multi_in_port<CB, N, I0, I...> : public in_port<I0> {
 public:
  // Helpers for managing recursive types
  using my_type = multi_in_port<CB, N, I0, I...>;
  using child_type = multi_in_port<CB, N + 1, I...>;
  using value_type = I0;

  explicit multi_in_port(node *owning_node, CB *cb) : in_port<I0>{owning_node}, cb_{cb}, rec_{owning_node, cb} {};

  void token_pushed(token<I0> token) override {
    cb_->template token_pushed<N, I0>(token);
  }

  // Helper struct required for recursive access to types by index
  template<int POS, typename ...T>
  struct type_at {
  };
  template<typename T0, typename ...T>
  struct type_at<0, T0, T...> {
    using type = T0;
    using in_port_type = in_port<type>;
  };
  template<int POS, typename T0, typename ...T>
  struct type_at<POS, T0, T...> {
    using type = typename type_at<POS - 1, T...>::type;
    using in_port_type = in_port<type>;
  };

  // Simple interface to get types by index
  template<int POS>
  using in_port_type_at = typename type_at<POS, I0, I...>::in_port_type;
  template<int POS>
  using value_type_at = typename type_at<POS, I0, I...>::type;

  // Helper struct required for recursive access to input's by index
  template<int POS, typename RES_T, typename MY_T>
  struct get_at {
    static RES_T &get(MY_T &self) {
      return get_at<POS - 1, RES_T, typename MY_T::child_type>::get(self.rec_);
    }
  };
  template<typename RESULT, typename CURRENT>
  struct get_at<0, RESULT, CURRENT> {
    static RESULT &get(CURRENT &self) {
      return self;
    }
  };

  // Simple interface to access input's by index
  template<int POS>
  in_port_type_at<POS> &get() {
    return get_at<POS, in_port_type_at<POS>, my_type>::get(*this);
  }

  bool fully_connected() const {
    return this->is_connected() && rec_.fully_connected();
  }

  child_type rec_;
  CB *cb_;
};

}
}
}

#endif //PLS_DATAFLOW_INTERNAL_INPUT_H_
