
#ifndef PLS_DATAFLOW_INTERNAL_SWITCH_NODE_H_
#define PLS_DATAFLOW_INTERNAL_SWITCH_NODE_H_

#include <atomic>

#include "in_port.h"
#include "out_port.h"
#include "node.h"
#include "token.h"

namespace pls {
namespace dataflow {
namespace internal {

template<typename I>
class switch_node : public node {
  // Our own type
  using self_type = switch_node<I>;

  // Input-Output port types
  using multi_in_port_type = multi_in_port<self_type, 0, I, bool>;
  using multi_out_port_type = multi_out_port<I, I>;

  // Input-Output tuples
  using input_tuple = std::tuple<token < I>, token<bool>>;

  // Memory used for ONE active invocation of the dataflow-graph
  struct invocation_memory {
    std::atomic<int> inputs_missing_;
    input_tuple input_buffer_;
  };

 public:
  explicit switch_node() : in_port_{this, this}, out_port_{} {}

  in_port<I> &value_in_port() {
    return in_port_.template get<0>();
  }

  in_port<bool> &condition_in_port() {
    return in_port_.template get<1>();
  }

  out_port<I> &true_out_port() {
    return out_port_.template get<0>();
  }

  out_port<I> &false_out_port() {
    return out_port_.template get<1>();
  }

  template<int POS, typename T>
  void token_pushed(token<T> token) {
    auto current_memory = get_invocation<invocation_memory>(token.invocation());

    std::get<POS>(current_memory->input_buffer_) = token;
    auto remaining_inputs = --(current_memory->inputs_missing_);
    if (remaining_inputs == 0) {
      bool condition = std::get<1>(current_memory->input_buffer_).value();
      auto &data = std::get<0>(current_memory->input_buffer_);
      if (condition) {
        true_out_port().push_token(data);
      } else {
        false_out_port().push_token(data);
      }
      current_memory->inputs_missing_ = 2;
    }
  }

  int num_successors() const override {
    return 2;
  }
  node *successor_at(int pos) const override {
    return out_port_.next_node_at(pos);
  }

  bool is_fully_connected() const override {
    return in_port_.fully_connected() && out_port_.fully_connected();
  }

  int instance_buffer_size() const override {
    return sizeof(invocation_memory);
  }
  void init_instance_buffer(void *memory) const override {
    auto invocation = new(memory) invocation_memory{};
    invocation->inputs_missing_ = 2;
  };
  void clean_up_instance_buffer(void *memory) const override {
    auto invocation = reinterpret_cast<invocation_memory *>(memory);
    invocation->~invocation_memory();
  }

 private:
  multi_in_port_type in_port_;
  multi_out_port_type out_port_;
};

}
}
}

#endif //PLS_DATAFLOW_INTERNAL_SWITCH_NODE_H_
