#include <nan.h>
#include <v8.h>
#include <RF24/RF24.h>

#include <iostream>
#include <ctime>
#include <stdio.h>
#include <time.h>
#include <string>

#include <cstdlib>
#include <sstream>
#include <unistd.h>

using namespace Nan;
using namespace v8;

RF24 radio(22,0);

Nan::Callback *cbPeriodic;
uv_async_t* async;

struct payload_data
{
   unsigned rpi   : 4;
   unsigned modul : 8;
   unsigned id    : 12;
   unsigned dir   : 8;
   char mes[28];
};

unsigned int message_id = 0;
uint8_t rpi = 0x00;
bool wait = false;
bool set;

int last_message_id[255] = {};


const uint64_t pipes_default[2] = {0x67896796AA, 0x67896796AB};
uint64_t pipes[2] = {0x6789679600, 0x6789679600};

bool send_data(char *buff, uint8_t rpi, uint8_t address, char data[28], uint8_t dir, int max_round);
int recieve_data(char *buff);

//--------------------------------------------------------------------------
//Below functions are just replica of RF24Network functions.
//No need to use these functions in you app.
NAN_METHOD(BeginRadio) {
  radio.begin();
}


//--------------------------------------------------------------------------------

NAN_METHOD(Begin){
  if (info.Length() < 3)
      return Nan::ThrowTypeError("Chýba paramerer");

  srand(time(NULL));

  rpi = info[0]->Uint32Value();
  pipes[0] += info[1]->Uint32Value();
  pipes[1] += info[2]->Uint32Value();

  radio.begin();

  radio.setAutoAck(false);
  radio.setDataRate(RF24_2MBPS);
  radio.printDetails();

  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1,pipes[1]);
  radio.openReadingPipe(2,pipes_default[1]);

  radio.startListening();
}

NAN_METHOD(send_data) {

    if (info.Length() < 4)
      return Nan::ThrowTypeError("Chýba paramerer");


  uint8_t address = info[0]->Uint32Value();
  v8::String::Utf8Value message(info[1]->ToString());
  std::string msg = std::string(*message);

  uint8_t code = info[2]->Uint32Value();
  int repeat = info[3]->Uint32Value();
  printf("%i\n", address);

  bool ok = false;

  char data[28];
  strncpy(data, msg.c_str(),28);
  char buff[28];

  if(code == 0x05) {
    radio.stopListening();
    radio.openWritingPipe(pipes_default[0]);
  }
  unsigned long started_waiting_at = millis();
  ok = send_data(buff, rpi, address, data, code, repeat);
  int time = millis()-started_waiting_at;
  if(code == 0x05) {
    radio.stopListening();
    radio.openWritingPipe(pipes[0]);
    radio.startListening();
  }
  v8::Local<v8::Object> obj = Nan::New<v8::Object>();
  Nan::Set(obj, Nan::New("message").ToLocalChecked(), Nan::New(buff).ToLocalChecked());
  if(ok) {
    Nan::Set(obj, Nan::New("status").ToLocalChecked(), Nan::True());
  } else {
    Nan::Set(obj, Nan::New("status").ToLocalChecked(), Nan::False());
  }

  Nan::Set(obj, Nan::New("time").ToLocalChecked(), Nan::New(time));


  info.GetReturnValue().Set(obj);
}

NAN_METHOD(recieve_data) {
  v8::Local<v8::Object> obj = Nan::New<v8::Object>();
  char buff[28];
  payload_data recieved, response;

  while(radio.available()){
    radio.read( &recieved, sizeof(recieved) );
  }

  radio.stopListening();

  usleep(1500);

  if(recieved.rpi == rpi && recieved.dir == 0x02) {

    response.rpi = rpi; // adresa RPI
    response.modul = recieved.modul; // adresa cieloveho modulu
    response.dir = 0x01; // odpoved z RPI
    response.id= recieved.id; // id poziadavky

    strncpy(response.mes, recieved.mes, 28);

    radio.write( &response, sizeof(response) );

    if(last_message_id[recieved.modul] == recieved.id) {
      Nan::Set(obj, Nan::New("message").ToLocalChecked(), Nan::New("0").ToLocalChecked());
      Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("0").ToLocalChecked());
    } else {
      Nan::Set(obj, Nan::New("module").ToLocalChecked(), Nan::New(recieved.modul));
      Nan::Set(obj, Nan::New("message").ToLocalChecked(), Nan::New(recieved.mes).ToLocalChecked());
      Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("message").ToLocalChecked());
      last_message_id[recieved.modul] = recieved.id;
    }

  } else if (recieved.rpi == 0 && recieved.dir == 0x04) {
    Nan::Set(obj, Nan::New("message").ToLocalChecked(), Nan::New(recieved.mes).ToLocalChecked());
    Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("new_device").ToLocalChecked());
  } else {
    Nan::Set(obj, Nan::New("message").ToLocalChecked(), Nan::New("0").ToLocalChecked());
    Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("0").ToLocalChecked());
  }

  radio.startListening();

    info.GetReturnValue().Set(obj);

  //info.GetReturnValue().Set(Nan::New(buff).ToLocalChecked());
}

NAN_METHOD(PrintDetails) {
  radio.printDetails();
}

NAN_METHOD(available){
  bool available = radio.available();
  if(available) {
  }
  info.GetReturnValue().Set(available);
}

NAN_METHOD(Close){
  uv_close((uv_handle_t*) &async, NULL);
}

NAN_MODULE_INIT(Init){

    Nan::Set(target, New<String>("beginRadio").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(BeginRadio)).ToLocalChecked());

    Nan::Set(target, New<String>("printDetails").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(PrintDetails)).ToLocalChecked());     

    Nan::Set(target, New<String>("send_data").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(send_data)).ToLocalChecked());  

    Nan::Set(target, New<String>("close").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Close)).ToLocalChecked());

    Nan::Set(target, New<String>("available").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(available)).ToLocalChecked()); 

    Nan::Set(target, New<String>("recieve_data").ToLocalChecked(),
                     New<FunctionTemplate>(recieve_data)->GetFunction());

    Nan::Set(target, New<String>("begin").ToLocalChecked(),
        GetFunction(New<FunctionTemplate>(Begin)).ToLocalChecked());                           
}

NODE_MODULE(nrf24Node, Init)



bool send_data(char *buff, uint8_t rpi, uint8_t address, char data[28], uint8_t dir = 0x00, int max_round = 15) {

  payload_data send, recieved;

  int round = 0;
  bool again = true;
  message_id = rand() % 50000 + 1;
  while(again) {
    radio.stopListening();

    send.rpi = rpi; // adresa RPI
    send.modul = address; // adresa cieloveho modulu
    send.dir = dir; // odosielanie z RPI
    send.id= message_id; // id poziadavky

    memcpy(send.mes, data, 28); 
    radio.write( &send, sizeof(send) );
    radio.startListening();
      // pockat do timeoutu 2ms
      unsigned long started_waiting_at = millis();
      bool timeout = false;
      while (!radio.available() && !timeout) {
        if (millis() - started_waiting_at > 4 )
          timeout = true;
      }

      if (timeout) {
        round++;
        if(round > max_round) {
          again = false;
          strncpy(buff,"0", 28);
          return false;
        }
      } else {
        radio.read(&recieved, sizeof(recieved) );
        if(recieved.id == send.id && recieved.dir == 0x03) {
          again = false;
          strncpy(buff,recieved.mes, 28);
          return true;
        }
      }
  }
  return true;
}
