#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>

#include <ncurses/curses.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#define VALS_STR_SIZE 19

// to parse iBeacons
typedef struct {
  uint8_t         length;
  uint8_t         type;
  unsigned char   data[0];
} ibeacon_rec_t;

// to store measurements
typedef struct {
  bdaddr_t        address;
  char            values[VALS_STR_SIZE];
} sensor_t;

sensor_t sensors[4] = {
  {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "looking for sensor"}, // TPMS1-front left
  {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "looking for sensor"}, // TPMS2-front right
  {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "looking for sensor"}, // TPMS3-rear left
  {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, "looking for sensor"}, // TPMS4-rear right
};

// pretty output for measured data
static void draw_tractor() {
  char tractor[] =
    "123456789012345678           123456789012345678\n"
    "\n"
    "                             ##################\n"
    "   ###########             /-##################-\\\n"
    "   ###########         /---+-################## ||\n"
    "       | |             |                    |   ||\n"
    " /-----+-+-------------|       +-------+    |---/\n"
    ")| ==               == |       |       |    |\n"
    " | ==               == |       |       |    |>\n"
    ")| ==           O   == |       |       |    |\n"
    " \\-----+-+-------------|       +-------+    |---\\\n"
    "       | |             |                    |   ||\n"
    "   ###########         \\---+-################## ||\n"
    "   ###########             \\-##################-/\n"
    "                             ##################\n"
    "\n"
    "123456789012345678           123456789012345678";

  memcpy(tractor      , sensors[1].values, VALS_STR_SIZE - 1);
  memcpy(tractor + 29 , sensors[3].values, VALS_STR_SIZE - 1);
  memcpy(tractor + 689, sensors[0].values, VALS_STR_SIZE - 1);
  memcpy(tractor + 718, sensors[2].values, VALS_STR_SIZE - 1);

  mvaddstr(0, 0, tractor);
  refresh();
}

int main() {
  int                   hci_id;                        // adapter id
  int                   hci_sock = 0;                  // socket to work with hci
  struct                hci_filter nf;                 // filter for scanner
  unsigned char         buffer[HCI_MAX_EVENT_SIZE];   // buffer to read packet in
  int                   len;                           // read data length
  evt_le_meta_event     *meta;                         // pointer to event
  le_advertising_info   *info;                         // pointer to ble advertisement
  ibeacon_rec_t         *rec;                          // for iBeacon parsing
  unsigned int          i;                             // for general iterations
  unsigned int          report;                        // for event reports iteration
  int                   pos;                           // curren position in data
  unsigned int          sensor;                        // selected sensor
  uint32_t              pressure;                      // pressure value
  uint16_t              temperature;                   // temperature value
  fd_set                fds;                           // set of descriptors for select()
  int                   exit_code = EXIT_FAILURE;      // status returned to terminal

  // get the first adapter identifier
  // if you dont connect an external adapter then it should be 0 every time
  hci_id = hci_get_route(NULL);
  if (hci_id < 0) {
    syslog(LOG_WARNING, "Bluetooth adapter is off\n");
    goto clean_finish;
  }

  // connect to the first adapter
  hci_sock = hci_open_dev(hci_id);
  if (hci_sock < 0) {
    syslog(LOG_ERR, "Connecting to the adapter failed: %s\n", strerror(errno));
    goto clean_finish;
  }

  // we want only advertisements
  hci_filter_clear(&nf);
  hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
  hci_filter_set_event(EVT_LE_META_EVENT, &nf);

  if (setsockopt(hci_sock, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
    syslog(LOG_ERR, "Set HCI filter failed: %s\n", strerror(errno));
    goto clean_finish;
  }

  // set BLE scanning parameters; at first is necessary call stop scanning for
  // case scanning running, otherwise set parameters failes
  if (hci_le_set_scan_enable(hci_sock, 0x00, 0, 1000) < 0 ||
      hci_le_set_scan_parameters(hci_sock, 0x01, htobs(0x0010), htobs(0x0010), 0x00, 0x00, 1000) < 0) {
    syslog(LOG_ERR, "Setting BLE scan parameters failed: %s\n", strerror(errno));
    goto clean_finish;
  }

  // start BLE scanning without filtering duplicity
  if (hci_le_set_scan_enable(hci_sock, 0x01, 0, 1000) < 0) {
    syslog(LOG_ERR, "BLE scanning start failed: %s\n", strerror(errno));
    goto clean_finish;
  }

  // initialize screen
  initscr();
  curs_set(0);
  draw_tractor();

  // infinite loop
  while (1) {
    // infinite wait for hci data ready or signal interrupt
    FD_ZERO(&fds);
    FD_SET(hci_sock, &fds);
    if (select(hci_sock + 1, &fds, NULL, NULL, NULL) < 0) {
      if (errno == EINTR)
        // it was signal
        exit_code = EXIT_SUCCESS;
      else
        syslog(LOG_ERR, "Waiting for data error: %s\n", strerror(errno));
      goto clean_finish;
    }

    // read data from hci socket
    len = read(hci_sock, buffer, sizeof(buffer));
    if (len == 0) {
      goto clean_finish;
    }

    // the first skipped byte is packet type
    meta = (evt_le_meta_event*) (buffer + (1 + HCI_EVENT_HDR_SIZE));
    // skipped data[0] is number of reports
    info = (le_advertising_info*) (meta->data + 1);

    // cycle through reports
    for (report = 1; report <= meta->data[0]; report++) {

      // walk through beacon data
      pos = 0;

      // we may not go out of records part and out of the read block
      while (pos < info->length - 1 && info->data + pos + info->data[pos] < buffer + len) {
        rec = (ibeacon_rec_t*)(info->data + pos);

        // we interested in type 0x09 (complete name) na 0xff (manuf. data) only
        switch (rec->type) {

          // the complete name
          case 0x09:
            // if advertised complete name is "TMPSx" where x is from 1 to 4
            if (rec->length == 13 && memcmp(&rec->data, "TPMS", 4) == 0 &&
                rec->data[4] >= '1' && rec->data[4] <= '4') {
              // get index to sensors array by sensor number
              sensor = rec->data[4] - '1';
              // store mac address
              memcpy(&sensors[sensor].address, &info->bdaddr, sizeof(bdaddr_t));
            }
            break;

          // the manufacturer data
          case 0xff:
            // check if it is one of sensors we intersted in
            for (i = 0; i < sizeof(sensors); i++) {
              if (memcmp(&sensors[i].address, &info->bdaddr, sizeof(bdaddr_t)) == 0) {
                // it is our sensor, let's go parse data
                // last byte: 0x00 = regular measurement, 0x01 immeasurable  pressure
                if (rec->data[17] == 0x01) {
                  memcpy(sensors[i].values, "not mesurable     ", VALS_STR_SIZE - 1);
                } else {
                  // bytes from 10 to 12 are bigendian pressure
                  pressure = rec->data[8] + (rec->data[9] << 8) + (rec->data[10] << 16);
                  // bytes 14 and 15 are bigendian temperature
                  temperature = rec->data[12] + (rec->data[13] << 8);
                  // store human readable values
                  snprintf(sensors[i].values, VALS_STR_SIZE, "%5u kPa, %3u C  ",
                           pressure/1000, temperature/100);
                }
                draw_tractor();
                break;
              }
            }
        }
        pos += 1 + rec->length;
      }

      // move to next report
      info = (le_advertising_info*) (((char*)info) + sizeof(le_advertising_info) + info->length + 1);
    }
  }

clean_finish:
  // restore screen
  endwin();

  if (hci_sock) {
    // stop BLE scanning
    hci_le_set_scan_enable(hci_sock, 0x00, 0, 1000);
    // disconnect from adapter
    close(hci_sock);
  }

  return exit_code;
}
