#include "message.h"

#include <cassert>
#include <cstdio>
#include <cstring>
#include <utility>
#include <vector>

#include <unistd.h>

using std::pair;
using std::vector;

int threads = 0;
int thread_id = -1, thread_pipe_w = -1, thread_pipe_r = -1;
bool thread_initialized = false;

vector <char> *thread_buffer_to;
pair <vector <char>, int> *thread_buffer_from;
vector <char> *thread_buffer_global;
int thread_buffer_index = 0;

void thread_initialize() {
  if (thread_initialized)
    return;
  assert (scanf ("%d%d%d%d", &threads, &thread_id, &thread_pipe_w, &thread_pipe_r) == 4);
  thread_buffer_to = new vector <char> [threads];
  thread_buffer_from = new pair <vector <char>, int> [threads];
  thread_buffer_global = new vector <char> ();
  thread_initialized = true;
}

int NumberOfNodes () {
  thread_initialize ();
  return threads;
}

int MyNodeId () {
  thread_initialize ();
  return thread_id;
}

void PutChar ( int target, char value ) {
  thread_initialize ();
  assert (0 <= target && target < threads);
  thread_buffer_to[target].push_back (value);
}

void PutInt ( int target, int _value ) {
  thread_initialize ();
  assert (0 <= target && target < threads);
  unsigned value = _value;
  for (int i = 0; i < 4; i++) {
    thread_buffer_to[target].push_back ((char)(value & 0xff));
    value >>= 8;
  }
}

void PutLL ( int target, int _value ) {
  thread_initialize ();
  assert (0 <= target && target < threads);
  unsigned value = _value;
  for (int i = 0; i < 8; i++) {
    thread_buffer_to[target].push_back ((char)(value & 0xff));
    value >>= 8;
  }
}

void Send ( int target ) {
  thread_initialize ();
  assert (0 <= target && target < threads);
  struct {
    unsigned target;
    unsigned length;
  } header = {
    .target = (unsigned)target,
    .length = (unsigned)thread_buffer_to[target].size ()
  };
  int r;
  r = write (thread_pipe_w, &header, sizeof (header));
  assert (r == (int)sizeof (header));
  r = write (thread_pipe_w, thread_buffer_to[target].data (), thread_buffer_to[target].size ());
  assert (r == (int)thread_buffer_to[target].size ());
  thread_buffer_to[target].clear ();
}

int Receive ( int source ) {
  thread_initialize ();
  assert (source == -1 || (0 <= source && source < threads));
  struct {
    unsigned target;
    unsigned length;
  } header = {
    .target = (unsigned)source,
    .length = (unsigned)-1
  };
  int r = write (thread_pipe_w, &header, sizeof (header));
  assert (r == (int)sizeof (header));
  // fprintf (stderr, "[debug lib %d] send request for %d\n", thread_id, source);
  while (true) {
    if (thread_buffer_index + (int)sizeof (header) <= (int)thread_buffer_global->size ()) {
      memcpy (&header, thread_buffer_global->data () + thread_buffer_index, sizeof (header));
      if (thread_buffer_index + (int)sizeof (header) + (int)header.length <= (int)thread_buffer_global->size ()) {
        for (int i = 0; i < (int)header.length; i++)
          thread_buffer_from[header.target].first.push_back ((*thread_buffer_global)[thread_buffer_index + sizeof (header) + i]);
        thread_buffer_index += sizeof (header) + header.length;
        if (source == -1 || source == (int)header.target)
          return header.target;
        continue;
      }
    }
    char buffer[1024];
    int r = read (thread_pipe_r, buffer, 1024);
    // fprintf (stderr, "[debug lib %d] read data: %d bytes\n", thread_id, r);
    for (int i = 0; i < r; i++)
      thread_buffer_global->push_back (buffer[i]);
  }
}

char GetChar ( int source ) {
  thread_initialize ();
  assert (0 <= source && source < threads);
  auto& buffer = thread_buffer_from[source];
  assert (buffer.second + sizeof (char) <= buffer.first.size ());
  char value = *(char *)(buffer.first.data () + buffer.second);
  buffer.second += sizeof (char);
  return value;
}

int GetInt ( int source ) {
  thread_initialize ();
  assert (0 <= source && source < threads);
  auto& buffer = thread_buffer_from[source];
  assert (buffer.second + sizeof (int) <= buffer.first.size ());
  int value = *(int *)(buffer.first.data () + buffer.second);
  buffer.second += sizeof (int);
  return value;
}

long long GetLL ( int source ) {
  thread_initialize ();
  assert (0 <= source && source < threads);
  auto& buffer = thread_buffer_from[source];
  assert (buffer.second + sizeof (long long) <= buffer.first.size ());
  long long value = *(long long *)(buffer.first.data () + buffer.second);
  buffer.second += sizeof (long long);
  return value;
}

