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

#include "NoiseSensor.h"
#include "time.h"
#include <ESP32Time.h>
#include <TimeLib.h>

ESP32Time t;

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.");
  }


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

  Wire.begin(21, 22, 400000);
  sendSyncBeacon();
  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 (t.getEpoch() - _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 &&
      t.getEpoch() - _last_sync_time ==
          (5 * this->_tx_offset * this->_tick_duration + 1)) {
    Serial.println("---->Request ready");
    return true;
  }
  return false;
}

bool NoiseSensorClass::read() {
  for (int pkt = 0; pkt < PACKET_COUNT; pkt++) {  
    NoiseSensorNode* node = _nodes[pkt];
    // Calculate the time of the sync message that triggered this packet.
    uint32_t time_offset = (_tick_count * _tick_duration);  // TODO Move
    node->_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 (node->missing_answers > 0) {
      node->_sync_message_ts -= time_offset * node->missing_answers;
    }
    */
    Wire.requestFrom(_i2c_address, _PACKET_SIZE * 2);
    char req_buf[_PACKET_SIZE * 2] = {0};
    uint8_t count = 0;

    while (Wire.available()) {
      char c = Wire.read();
      req_buf[count] = c;
      count++;
    }
        
    // TODO There's probably a neater way than doing these low level operations.
    for (int i = 0; i < _PACKET_SIZE * 2; i+=2) {
      uint16_t n = req_buf[i+1] << 8;
      n |= req_buf[i];
      node->_measurements[i/2] = n;
      Serial.print(n);
      Serial.print(" ");
    }
    
    Serial.println();
  }

  // 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++) {
      // TODO Timestamps
      
      Serial.println(this->_nodes[node_id]->_measurements[i]);
      ts += this->_tick_duration;
    }
}

bool NoiseSensorClass::sendSyncBeacon() {
  uint8_t setup_values[4];

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

  Serial.println("Sent beacon");
  
  Wire.beginTransmission(_i2c_address);
  Wire.write((uint8_t *) setup_values, sizeof(setup_values));
  Wire.endTransmission();
  
  this->_first_sync = 0;
  _last_sync_time = t.getEpoch();
  Serial.println(_last_sync_time);

}

String NoiseSensorClass::buildSenseBoxJSON(uint8_t device_id) { 
  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() {
  configTime(0, 0, "de.pool.ntp.org");

  // Allow processing of the package.
  delay(1000);
  
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
      Serial.println("Failed to obtain time");
      return false;
    }
  char timestamp[24];
  uint32_t ts = t.getEpoch();
  sprintf(timestamp, "%i-%02i-%02iT%02i:%02i:%02iZ", year(ts), month(ts),
              day(ts), hour(ts), minute(ts), second(ts));
  Serial.println(timestamp);
  return true;
}

NoiseSensorClass NoiseSensor;
