// vim: noai:sw=2:tw=88

#include <TimeLib.h>
#include "NoiseSensor.h"
//#include "NoiseSensorNode.h"


bool NoiseSensorClass::begin() {
  _PACKET_SIZE = ( _tick_count + LORA_HEADER_LENGTH + BATTERY_FIELD_SIZE );
  _REQUEST_SIZE = ( PACKET_COUNT * _PACKET_SIZE * 2 );    // * 2, it's 16 bit.

  for (int i = 0; i < PACKET_COUNT; i++) {
    _nodes[i] = new NoiseSensorNode(i, _PACKET_SIZE * 2);
  }
  
  if (this->_tick_duration < 2) {
    Serial.println("--> Using a tick duration of less than two seconds. This "
                   "will fail unless you've increased the MCUs SYSCLK!");
  } 

  if (this->_tick_count >= 100) {
    Serial.println(
        "--> Using a tick count of more than 100 gets dangerously close to the "
        "LoRa transmission limitations and is not supported as of now.");
  } else if (this->_tick_count < 10) {
    Serial.println(
        "--> Using a tick count smaller than 10 might screw things up in a "
        "unpredictable manner and is not recommended.");
  }

  
  if (this->_tick_count >= 120) {
    Serial.println(
      "--> Increased the tick count over 120. This results in I²C packages of "
      "(tick_count + 3 ) * 2. "
      "Note that I²C packets longer than 256 bytes will need to be split into "
      "smaller chunks to allow them to transfer."
  }

  if (!setupNTPClient()) {
    Serial.println("!!!-------------------------!!!");
    Serial.println("NTPClient couldn't be started.");
    Serial.println("!!!-------------------------!!!");
    return false;
  }
  I2C.begin(400000);

  this->sendSyncBeacon();
  while (I2C.writeBusy) {
    delay(250);
    Serial.println("Writing!");
  }
  this->_first_sync = 1;
}

bool NoiseSensorClass::begin(uint8_t tick_count) {
  this->_tick_count = tick_count;
  this->begin();
}

bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration) {
  this->_tick_count = tick_count;
  this->_tick_duration = tick_duration;
  this->begin();
}

bool NoiseSensorClass::begin(uint8_t tick_count, uint8_t tick_duration,
                             uint8_t tx_offset) {
  this->_tick_count = tick_count;
  this->_tick_duration = tick_duration;
  this->_tx_offset = tx_offset;
  this->begin();
}

void NoiseSensorClass::setIds(const char* sensebox_ids[], const char* sensor_ids[]) {
  for (int i = 0; i < PACKET_COUNT; i++) {
    char tmp[24] = {0};
    _nodes[i]->sensebox_id = sensebox_ids[i];
    _nodes[i]->sensor_id = sensor_ids[i];
  }
}


bool NoiseSensorClass::beaconReady() {
//  if (this->_time_client->getEpochTime() - this->_last_sync_time ==
    if (now() - _last_sync_time ==
      (this->_tick_count * this->_tick_duration)) {
    Serial.println("---->Beacon ready");
    return true;
  }
  return false;
}

bool NoiseSensorClass::requestReady() {
  // 1 second as tolerance
  if (!this->_first_sync &&
      //this->_time_client->getEpochTime() - this->_last_sync_time ==
      now() - _last_sync_time ==
          (5 * this->_tx_offset * this->_tick_duration + 1)) {
    Serial.println("---->Request ready");
    return true;
  }
  return false;
}

bool NoiseSensorClass::read() {
  Serial.println("Starting read");
  for (int pkt = 0; pkt < PACKET_COUNT; pkt++) {
    // Calculate the time of the sync message that triggered this packet.
    uint32_t time_offset = (_tick_count * _tick_duration) + 1;  // TODO Move
    this->_nodes[pkt]->_sync_message_ts = this->_last_sync_time - time_offset;
    Serial.print("Last sync time: ");
    Serial.println(_last_sync_time);
    Serial.print("Offset: ");
    Serial.println(time_offset);
    Serial.print("TS: ");
    Serial.println(_nodes[pkt]->_sync_message_ts);
    Serial.print(minute(_nodes[pkt]->_sync_message_ts));
    Serial.print(":");
    Serial.println(second(_nodes[pkt]->_sync_message_ts));
    /*
    if (_nodes[pkt]->missing_answers > 0) {
      _nodes[pkt]->_sync_message_ts -= time_offset * _nodes[pkt]->missing_answers;
    }
    */
    I2C.readBytes(this->_i2c_address, (uint8_t *) this->_nodes[pkt]->_measurements, this->_PACKET_SIZE * 2);
  }

  uint32_t read_start_time = millis();
  while (I2C.readBusy) {
    Serial.println("Reading!");
    delay(1);

    // Restart I²C if no response for more than a second.
    if (millis() - read_start_time >= 1000)  {
      Serial.println("Restarting I²C");
      I2C.end();
      I2C.begin(400000);          
      return false;
    }
  }
  // Skip 0, it has no battery.
  for (int pkt = 1; pkt < PACKET_COUNT; pkt++) {
    // TODO Move this to a sane place (own function).
    if (_nodes[pkt]->_measurements[_PACKET_SIZE - 1]) {
      Serial.print("Battery low on device");
      Serial.println(pkt);
     }
  }
  
  Serial.println("Reading done.");
  return true;
}

void NoiseSensorClass::printMeasurements(uint8_t node_id) {
    uint32_t ts = this->_nodes[node_id]->_sync_message_ts;
    char printbuf[32];
    sprintf(printbuf, "- ID %u - ", node_id);
    Serial.println(printbuf);

    for (int i = 2; i < (this->_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) {
      sprintf(printbuf, "%02i:%02i:%02i -> ", hour(ts), minute(ts), second(ts));
      Serial.print(printbuf);
      
      Serial.println(this->_nodes[node_id]->_measurements[i]);
      ts += this->_tick_duration;

    }
}

bool NoiseSensorClass::sendSyncBeacon() {
  //_last_sync_time = _time_client->getEpochTime();
  uint8_t setup_values[3];

  setup_values[0] = this->_tick_count;
  setup_values[1] = this->_tick_duration;
  setup_values[2] = this->_tx_offset;       

  Serial.println("Sent beacon");
  I2C.writeBytes(this->_i2c_address, 0x01, (uint8_t *)setup_values,
                 sizeof(setup_values));
  this->_first_sync = 0;
  _last_sync_time = now();
  Serial.println(_last_sync_time);
  Serial.print(minute(_last_sync_time));
  Serial.print(":");
  Serial.println(second(_last_sync_time));

  while (I2C.writeBusy) {
    delay(250);
    Serial.println("Writing!");
  }
}

String NoiseSensorClass::buildSenseBoxJSON(uint8_t device_id) { 
  // _time_client->update(); 
  NoiseSensorNode* node = _nodes[device_id];
  uint32_t ts = node->_sync_message_ts; 
  
  // Each measurement line is max. 94 chars long + 2 curly brackets.
  uint16_t bufsize = _tick_count * 94 + 2;
  char json_buf[bufsize] = {0};
  char sensor_id[25] = {0};
  node->sensor_id.toCharArray(sensor_id, 25);

  // Build a json string in a very comprehensible way...
  strcat(json_buf, "[\n");

  for (int i = 2; i < (_PACKET_SIZE - BATTERY_FIELD_SIZE); i++) {
    char tmpbuf[1024] = {0};
    char timestamp[24] = {0};
    float m = node->_measurements[i] / 10.0; // From fixed point to float.

    if (m >= 29 && m <= 120) {
      sprintf(timestamp, "%i-%02i-%02iT%02i:%02i:%02iZ", year(ts), month(ts),
              day(ts), hour(ts), minute(ts), second(ts));
              
      sprintf(tmpbuf, "{\"sensor\":\"%s\", \"value\":\"%.1f\", \"createdAt\":\"%s\"}", sensor_id, m, timestamp);
      
      // No trailing comma on the last entry.
      if (i != _PACKET_SIZE - BATTERY_FIELD_SIZE - 1) {
        strcat(tmpbuf, ",");
      }
      strcat(json_buf, tmpbuf);
      strcat(json_buf, "\n");
  
    }
    
  ts += this->_tick_duration;
  }

  /*
  if (strlen(json_buf) > 2) {
    node->missing_answers = 0;
    Serial.println("Missing answers reset.");
  } else {
    node->missing_answers++;
    Serial.print("Missing answers: ");
    Serial.println(node->missing_answers);
  }
  */

  strcat(json_buf, "]");

  return json_buf;
}

String NoiseSensorClass::buildHTTPHeader(uint8_t device_id, const char* server, uint16_t content_length) {
  char sb_id[25] = {0};
  _nodes[device_id]->sensebox_id.toCharArray(sb_id, 25);
  
  char uploadbuf[2048] = {0};
  sprintf(uploadbuf, "POST /boxes/%s/data HTTP/1.1\n"
                     "Host: %s\n"
                     "content-type: application/json\n"
                     "Connection: close\n"
                     "Content-Length: %u", sb_id, server, content_length); 

  return String(uploadbuf);
}


// Private functions

bool NoiseSensorClass::setupNTPClient() {
  uint32_t start_time = millis();
  while (timeStatus() != timeSet) {
    if (millis() - start_time >= 200) {
      return false;
    }
  }
  return true;
  //setSyncProvider(
  
  
  //WiFiUDP ntpUDP;
  //this->_time_client = new NTPClient(ntpUDP);
  //this->_time_client->begin();

  //if (this->_time_client->update()) { return true; }

}

NoiseSensorClass NoiseSensor;
