
#ifndef PLS_DATAFLOW_INTERNAL_OUTPUT_H_
#define PLS_DATAFLOW_INTERNAL_OUTPUT_H_

#include <tuple>

#include "in_port.h"
#include "node.h"

namespace pls {
namespace dataflow {
namespace internal {

template<class T>
class out_port {
 public:
  void connect(in_port<T> &target) {
    if (connected_) {
      PLS_ERROR("Must only connect output once. Please disconnect it before reconnecting.")
    }

    target.connect();
    target_ = &target;
    connected_ = true;
  }

  void operator>>(in_port<T> &input) {
    connect(input);
  }

  void push_token(token<T> token) const {
    target_->push_token(token);
  }

  node *next_node() const {
    return target_->owning_node();
  }

  bool is_connected() const { return connected_; }

 private:
  bool connected_{false};
  in_port<T> *target_{nullptr};
};

/**
 * Represents multiple output ports bundled together (a tuple of inputs).
 *
 * @tparam I A variadic list of input types
 */
template<typename O0, typename ...O>
class multi_out_port {
 private:
  // Helpers for managing recursive types
  using value_tuple_type = std::tuple<O0, O...>;
  using out_port_tupel_type = std::tuple<out_port<O0>, out_port<O>...>;
  static constexpr int num_outputs = std::tuple_size<value_tuple_type>();

 public:
  // Simple interface to get types by index
  template<int POS>
  using out_port_type_at = typename std::tuple_element<POS, out_port_tupel_type>::type;
  template<int POS>
  using value_type_at = typename std::tuple_element<POS, value_tuple_type>::type;

  // Simple interface to access input's by index
  template<int POS>
  out_port_type_at<POS> &get() {
    return std::get<POS>(outputs_);
  }

  // Simple interface to connect multiple intputs to matching, multiple outputs
  template<typename CB>
  void operator>>(multi_in_port<CB, 0, O0, O...> &input) {
    connect_to < CB, 0 > {this, &input}.connect();
  }

  node *next_node_at(int pos) const {
    return next_node < 0 > {&outputs_}.get(pos);
  }

  bool fully_connected() const {
    return connected < 0 > {&outputs_}.get();
  }

 private:
  out_port_tupel_type outputs_;

  template<int POS, typename DUMMY=void>
  struct next_node {
    const out_port_tupel_type *outputs_;
    node *get(int i) {
      if (POS == i) {
        return std::get<POS>(*outputs_).next_node();
      } else {
        return next_node<POS + 1>{outputs_}.get(i);
      }
    }
  };
  template<typename DUMMY>
  struct next_node<num_outputs, DUMMY> {
    const out_port_tupel_type *outputs_;
    node *get(int i) {
      PLS_ERROR("Try to access invalid successor node index!")
    }
  };

  template<int POS, typename DUMMY=void>
  struct connected {
    const out_port_tupel_type *outputs_;
    bool get() {
      bool self = std::get<POS>(*outputs_).is_connected();
      bool children = connected<POS + 1>{outputs_}.get();
      return self && children;
    }
  };
  template<typename DUMMY>
  struct connected<num_outputs, DUMMY> {
    const out_port_tupel_type *outputs_;
    bool get() {
      return true;
    }
  };

  template<typename CB, int POS, typename DUMMY=void>
  struct connect_to {
    multi_out_port<O0, O...> *out_port_;
    multi_in_port<CB, 0, O0, O...> *in_port_;

    void connect() {
      out_port_->template get<POS>() >> in_port_->template get<POS>();
      connect_to<CB, POS + 1>{out_port_, in_port_}.connect();
    }
  };
  template<typename CB, typename DUMMY>
  struct connect_to<CB, num_outputs, DUMMY> {
    multi_out_port<O0, O...> *out_port_;
    multi_in_port<CB, 0, O0, O...> *in_port_;

    void connect() {
    };
  };
};
}
}
}

#endif //PLS_DATAFLOW_INTERNAL_OUTPUT_H_
