/*
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 *  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
 *  CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES 
 *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 
 *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 
 *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *  ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, 
 *  COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS 
 *  SOFTWARE IS DISCLAIMED.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include <syslog.h>
#include <stdarg.h>

#include <termios.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/ioctl.h>

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

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define BTCONNECTTIMEOUT 5000
#define DEFAULTTHRESHOLD 200
#define DEFAULTINTERVAL 100
//#define DEBUG

#define DBUS_INTROSPECTION_DATA "<!DOCTYPE node PUBLIC \
 \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" \
 \"http://dbus.freedesktop.org/doc/introspect.dtd\"> \n\
<node name=\"/cx/ath/matthew/bluemon/Bluemon\">\n\
 <interface name=\"cx.ath.matthew.bluemon.Bluemon\">\n\
  <method name=\"Status\" >\n\
   <arg name=\"address\" type=\"s\" direction=\"in\"/>\n\
   <arg name=\"address\" type=\"s\" direction=\"out\"/>\n\
   <arg name=\"status\" type=\"b\" direction=\"out\"/>\n\
   <arg name=\"level\" type=\"u\" direction=\"out\"/>\n\
  </method>\n\
 </interface>\n\
 <interface name=\"cx.ath.matthew.bluemon.ProximitySignal\">\n\
  <signal name=\"Connect\" >\n\
   <arg name=\"address\" type=\"s\" direction=\"out\"/>\n\
  </signal>\n\
  <signal name=\"Disconnect\" >\n\
   <arg name=\"address\" type=\"s\" direction=\"out\"/>\n\
  </signal>\n\
 </interface>\n\
 <interface name=\"org.freedesktop.DBus.Introspectable\">\n\
  <method name=\"Introspect\">\n\
   <arg type=\"s\" direction=\"out\"/>\n\
  </method>\n\
 </interface>\n\
</node>"


struct devstruct;
typedef struct devstruct {
   char* btid;
   bdaddr_t bdaddr;
   int dd;
   int track;
   bool connected;
   struct devstruct* next;
} btdev_t;

btdev_t* btdev_append_new(btdev_t* current, char* btid)
{
   btdev_t* temp;
   if (NULL == current) {
      temp = malloc(sizeof(btdev_t));
      temp->btid = btid;
      temp->dd = 0;
      temp->connected = false;
      temp->next = NULL;
      temp->track = -1;
      return temp;
   }
   else {
      current->next = btdev_append_new(current->next, btid);
      return current;
   }
}

typedef struct {
   char* config;
   btdev_t* btdevroot;
   int threshold;
   int interval;
   bool stdout;
   bool fork;
   bool verbose;
   bool authenticate;
   int uid;
   int gid;
   bool disconnecthack;
   bool linkquality;
} arg_t;
arg_t opts;
arg_t args;
DBusConnection* conn = NULL;

void message(bool verbose, char* msg, ...);
void sendsignal(bool connected, btdev_t* btdev);

#define assert(a, b) if(!(a)) real_assert(b)
void real_assert (char* msg)
{
   if (0 != errno) {
      char* errstr = strerror(errno);
      message(false, "[Assert] failed with errno %d/%s: %s\n", errno, errstr, msg);
   } else
      message(false, "[Assert] failed: %s\n", msg);
#ifdef DEBUG
   exit(1);
#endif
}

static int find_conn(int s, int dev_id, long arg)
{
	struct hci_conn_list_req *cl;
	struct hci_conn_info *ci;
	int i;

	if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl)))) {
		perror("Can't allocate memory");
		exit(1);
	}
	cl->dev_id = dev_id;
	cl->conn_num = 10;
	ci = cl->conn_info;

	if (ioctl(s, HCIGETCONNLIST, (void*)cl)) {
		perror("Can't get connection list");
		exit(1);
	}

	for (i=0; i < cl->conn_num; i++, ci++)
		if (!bacmp((bdaddr_t *)arg, &ci->bdaddr)) {
         free(cl);
			return 1;
      }
   free(cl);
	return 0;
}

// open connection
bool btconnect(btdev_t* btdev)
{
	int ptype;
	uint16_t handle = 0;
	uint8_t role;
   bool rval;
   struct hci_conn_info_req *cr;
   struct hci_request rq;
   auth_requested_cp acp;
   evt_auth_complete arp;
   evt_encrypt_change erp;
   set_conn_encrypt_cp ecp;

	role = 0x01;
	ptype = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5;

   int dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &(btdev->bdaddr));
	if (dev_id < 0) {
      message(true, "Connecting to: %x:%x:%x:%x:%x:%x\n", btdev->bdaddr.b[5], btdev->bdaddr.b[4], btdev->bdaddr.b[3], btdev->bdaddr.b[2], btdev->bdaddr.b[1], btdev->bdaddr.b[0]);
      if (hci_create_connection(btdev->dd, &(btdev->bdaddr), htobs(ptype), 0, role, &handle, BTCONNECTTIMEOUT) < 0) { 
         message(true, "Can't create connection\n");
         return false;
      }
      btdev->connected = true;
   }
   else btdev->connected = false;

   if (0 == handle) {
      cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));

      bacpy(&cr->bdaddr, &(btdev->bdaddr));
      cr->type = ACL_LINK;
      if (ioctl(btdev->dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
         message(true, "Get connection info failed");
         return false;
      }
      handle = htobs(cr->conn_info->handle);
      free(cr);
   }
   
   if (opts.authenticate || args.authenticate) {
   
      acp.handle = htobs(handle);
      
      memset(&rq, 0, sizeof(rq));
      rq.ogf    = OGF_LINK_CTL;
      rq.ocf    = OCF_AUTH_REQUESTED;
      rq.cparam = &acp;
      rq.clen   = AUTH_REQUESTED_CP_SIZE;
      rq.rparam = &arp;
      rq.rlen   = EVT_AUTH_COMPLETE_SIZE;
      rq.event  = EVT_AUTH_COMPLETE;

      if (hci_send_req(btdev->dd, &rq, 25000) < 0) {
#ifdef DEBUG
         message(true, "Can't authenticate link\n");
#endif
         return false;
      }
      ecp.handle = htobs(handle);
      ecp.encrypt = 1;

      memset(&rq, 0, sizeof(rq));
      rq.ogf    = OGF_LINK_CTL;
      rq.ocf    = OCF_SET_CONN_ENCRYPT;
      rq.cparam = &ecp;
      rq.clen   = SET_CONN_ENCRYPT_CP_SIZE;
      rq.rparam = &erp;
      rq.rlen   = EVT_ENCRYPT_CHANGE_SIZE;
      rq.event  = EVT_ENCRYPT_CHANGE;

      if (hci_send_req(btdev->dd, &rq, 25000) < 0) {
#ifdef DEBUG
         message(true, "Can't encrypt link\n");
#endif
         return false;
      }
   }
   return true;
}


// close connection
void btdisconnect(btdev_t* btdev)
{
	struct hci_conn_info_req *cr;
	cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
	if (!cr)
		return;

	bacpy(&cr->bdaddr, &(btdev->bdaddr));
	cr->type = ACL_LINK;
	if (ioctl(btdev->dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
		message(false, "Get connection info failed\n");
		exit(1);
	}

	if (hci_disconnect(btdev->dd, htobs(cr->conn_info->handle), HCI_OE_USER_ENDED_CONNECTION, 100) < 0)
		message(false, "Disconnect failed\n");
   free(cr);
}



// query link quality
int get_link_qual(btdev_t* btdev)
{
	struct hci_conn_info_req *cr;
	struct hci_request rq;
#ifdef OCF_GET_LINK_QUALITY
	get_link_quality_rp rp;
#else
   read_link_quality_rp rp;
#endif
   uint16_t handle;
   int qual;

   // check the quality
   if (opts.linkquality || args.linkquality) {
      cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info));
      if (!cr)
         return;

      bacpy(&cr->bdaddr, &(btdev->bdaddr));
      cr->type = ACL_LINK;
      if (ioctl(btdev->dd, HCIGETCONNINFO, (unsigned long) cr) < 0) {
#ifdef DEBUG
         message(true, "Get connection info failed, trying reconnect\n");
#endif
         return -1;
      }

      handle = htobs(cr->conn_info->handle);

      memset(&rq, 0, sizeof(rq));
      rq.ogf    = OGF_STATUS_PARAM;
#ifdef OCF_GET_LINK_QUALITY
      rq.ocf    = OCF_GET_LINK_QUALITY;
#else
      rq.ocf    = OCF_READ_LINK_QUALITY;
#endif
      rq.cparam = &handle;
      rq.clen   = 2;
      rq.rparam = &rp;
#ifdef OCF_GET_LINK_QUALITY_RP_SIZE
      rq.rlen   = GET_LINK_QUALITY_RP_SIZE;
#else
      rq.rlen   = READ_LINK_QUALITY_RP_SIZE;
#endif

      if (hci_send_req(btdev->dd, &rq, 100) < 0) {
         message(true, "HCI get_link_quality request failed, trying reconnect\n");
         free(cr);
         return -1;
      }

      if (rp.status) {
#ifdef DEBUG
         message(true, "HCI get_link_quality cmd failed (0x%2.2X), trying reconnect\n", rp.status);
#endif
         free(cr);
         return -1;
      }

      qual = rp.link_quality;
      free(cr);
      return qual;
   } 
   // just check for connection
   else {
      qual = hci_for_each_dev(HCI_UP, find_conn, (long) &(btdev->bdaddr));
      return qual < 0 ? qual : 255;
   }
}

void readoptions(arg_t args, arg_t* opts)
{
   opts->config = NULL;
   opts->btdevroot = NULL;
   opts->threshold = DEFAULTTHRESHOLD;
   opts->interval = DEFAULTINTERVAL;
   opts->stdout = false;
   opts->fork = true;
   opts->verbose = false;
   opts->uid = 0;
   opts->gid = 0;
   opts->authenticate = false;
   opts->linkquality = false;
}

char* progname;

void syntax()
{
   printf("Syntax: %s <options>\n", progname);
   //printf("   -c configfile --config=configfile\n");
   printf("   -b aa:bb:cc:dd:ee:ff --btid=aa:bb:cc:dd:ee:ff\n");
   printf("   -t threshold --threshold=threshold\n");
   printf("   -i interval --interval=interval\n");
   printf("   -s --stdout --no-syslog\n");
   printf("   -n --no-fork\n");
   printf("   -v --verbose\n");
   printf("   -V --version\n");
   printf("   -d --disconnect-hack\n");
   printf("   -q --link-quality\n");
   printf("   -a --authenticate\n");
   printf("   -h --help\n");
   exit(1);
}

/*
 * -c --config
 * -b --btid
 * -t --threshold
 * -i --interval
 * -s --stdout --no-syslog
 * -n --no-fork
 * -v --verbose
 * -a --authenticate
 * -h --help
 */
void readargs(int argc, char** argv, arg_t* args)
{
   args->config = NULL;
   args->btdevroot = NULL;
   args->threshold = DEFAULTTHRESHOLD;
   args->interval = DEFAULTINTERVAL;
   args->stdout = false;
   args->fork = true;
   args->verbose = false;
   args->authenticate = false;
   args->disconnecthack = false;
   args->linkquality = false;
   args->uid = 0;
   args->gid = 0;

   int c;
   static struct option long_opts[] = {
      {"config", 1, 0, 'c'},
      {"btid", 1, 0, 'b'},
      {"threshold", 1, 0, 't'},
      {"interval", 1, 0, 'i'},
      {"stdout", 0, 0, 's'},
      {"help", 0, 0, 'h'},
      {"no-fork", 0, 0, 'n'},
      {"verbose", 0, 0, 'v'},
      {"version", 0, 0, 'V'},
      {"authenticate", 0, 0, 'a'},
      {"no-syslog", 0, 0, 's'},
      {"uid", 1, 0, 'u'},
      {"gid", 1, 0, 'u'},
      {"disconnect-hack", 1, 0, 'd'},
      {"link-quality", 1, 0, 'q'}
   };
   int idx = 0;
   while (-1 != (c = getopt_long(argc, argv, "-c:b:t:i:o:sqhVndvag:u:",long_opts, &idx))) {
      switch (c) {
         case 'c':
            args->config = optarg;
            break;
         case 'b':
            args->btdevroot = btdev_append_new(args->btdevroot, optarg);
            break;
         case 't':
            args->threshold = atoi(optarg);
            break;
         case 'i':
            args->interval = atoi(optarg);
            break;
         case 'u':
            args->uid = atoi(optarg);
            break;
         case 'g':
            args->gid = atoi(optarg);
            break;
         case 's':
            args->stdout = true;
            break;
         case 'n':
            args->fork = false;
            break;
         case 'v':
            args->verbose = true;
            break;
         case 'd':
            args->disconnecthack = true;
            break;
         case 'a':
            args->authenticate = true;
            break;
         case 'q':
            args->linkquality = true;
            break;
         case 'V':
            printf("Bluemon version %s.\n", VERSION);
            break;
         case 'h':
         case ':':
         case '?':
         case 1:
            syntax();
            break;
      }
      idx = 0;
   }
}

int testint(int a, int b, char* errormsg)
{
   if (0 != a)
      return a;
   if (0 != b)
      return b;
   message(false, "%s\n", errormsg);
   exit(1);
}
btdev_t* testdev(btdev_t* a, btdev_t* b, char* errormsg)
{
   if (NULL != a)
      return a;
   if (NULL != b)
      return b;
   message(false, "%s\n", errormsg);
   
   exit(1);
}
char* testchar(char* a, char* b, char* errormsg)
{
   if (NULL != a)
      return a;
   if (NULL != b)
      return b;
   message(false, "%s\n", errormsg);
   
   exit(1);
}

// write a message to syslog or stdout depending on setup
void message(bool verbose, char* msg, ...)
{
   if (!args.verbose && !opts.verbose && verbose) return;
   va_list vargs;
   va_start(vargs, msg);
         
   if (args.stdout || opts.stdout) 
      vprintf(msg, vargs);
   else
      vsyslog(LOG_NOTICE, msg, vargs);

   va_end(vargs);
}



void sighandle(int signum)
{
   btdev_t* btdev;
   switch (signum) {
      case SIGINT:
      case SIGTERM:
         btdev = testdev(args.btdevroot, opts.btdevroot, "No Devices Specified");
         while (NULL != btdev) {
            if (btdev->connected) {
               // disconnect bluetooth
               btdisconnect(btdev);
               message(true, "Disconnected %s\n", btdev->btid);
            }
            if (NULL != conn) 
               sendsignal(false, btdev);
            hci_close_dev(btdev->dd);
            btdev_t* temp = btdev->next;
            free(btdev);
            btdev = temp;
         }
         message(false, "Caught SIGTERM, announcing our demise\n");
         if (NULL != conn) 
            dbus_connection_unref(conn);
         exit(0);
      case SIGHUP:
         readoptions(args, &opts);
         break;
   }
}

void writepidfile(void)
{
   FILE* fd;
   fd = fopen("/var/run/bluemon.pid", "w");
   if (NULL != fd) {
      fprintf(fd, "%d", getpid());
      fclose(fd);
   } else {
      message(true, "Could not write PID file, quitting\n");
      exit(1);
   }
}

DBusConnection* register_dbus()
{
   DBusError err;
   DBusConnection* conn;
   int ret;

   dbus_error_init(&err);

   conn = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
   if (dbus_error_is_set(&err)) { 
      message(true, "DBUS Connection Error (%s)\n", err.message); 
      dbus_error_free(&err); 
   }
   if (NULL == conn) { 
      exit(1);
   }

   ret = dbus_bus_request_name(conn, "cx.ath.matthew.bluemon.server", 
                               DBUS_NAME_FLAG_REPLACE_EXISTING, 
                               &err);
   if (dbus_error_is_set(&err)) {
      message(true, "DBUS Name Error (%s)\n", err.message); 
      dbus_error_free(&err);
   }
   if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) { 
      exit(1);
   }
   return conn;
}

void sendsignal(bool inrange, btdev_t* btdev)
{
   DBusMessage* msg;
   DBusMessageIter it;
   dbus_uint32_t serial;
   if (inrange)
      msg = dbus_message_new_signal("/cx/ath/matthew/bluemon/Bluemon", "cx.ath.matthew.bluemon.ProximitySignal", "Connect");
   else
      msg = dbus_message_new_signal("/cx/ath/matthew/bluemon/Bluemon", "cx.ath.matthew.bluemon.ProximitySignal", "Disconnect");
   if (NULL == msg) { message(true, "DBUS Message Null, ALIEN INVASION\n"); exit(1); }
   dbus_message_iter_init_append(msg, &it);
   if (!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &(btdev->btid))) {
      message(true, "Out Of Memory!");
      exit(1);
   }
   if (!dbus_connection_send(conn, msg, &serial)) {
      message(true, "Out Of Memory!");
      exit(1);
   }

   //dbus_connection_flush(conn);
   dbus_message_unref(msg);
   message(true, "Signal sent (%s, %s)\n", inrange?"true":"false", btdev->btid);
}

void handle_dbus_message(DBusMessage* msg, int threshold)
{
   DBusMessage* reply;
   DBusMessageIter it;
   dbus_uint32_t serial, level;
   dbus_bool_t stat;
   char* address;

   if (!dbus_message_iter_init(msg, &it)) {
      message(true, "Bad parameter count for Status message");
      return;      
   }
   if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&it)) {
      message(true, "Bad parameter type (%d) for Status message", dbus_message_iter_get_arg_type(&it));
      return;      
   }
   dbus_message_iter_get_basic(&it, &address);
   message(true, "Handling dbus status query for %d\n", address);
   reply = dbus_message_new_method_return(msg);
   if (NULL == reply) {
      message(true, "Reply is NULL!! You have been eaten by a GRUE!");
      return;      
   }
   dbus_message_iter_init_append(reply, &it);
   btdev_t* btdev = testdev(args.btdevroot, opts.btdevroot, "No Devices Specified");
   for (; NULL != btdev; btdev = btdev->next) {
      if (0 == strncmp("", address, 1) || 0 == strncasecmp(btdev->btid, address, strlen(btdev->btid))) {
         if (!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &(btdev->btid))) {
            message(true, "Out Of Memory! Lock me in the cellar and feed me pins, PINS!!");
            dbus_message_unref(reply);
            return;      
         }
         if (btdev->track >= threshold) {
            stat = TRUE;
            level = btdev->track;
         } else {
            stat = FALSE;
            level = -1;
         }
         break;
      } else {
         //address = "";
         stat = FALSE;
         level = -1;
         dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &address);
      }
   }
   if (!dbus_message_iter_append_basic(&it, DBUS_TYPE_BOOLEAN, &stat)){
      message(true, "Out Of Memory! The bells! the bells!");
      dbus_message_unref(reply);
      return;      
   }

   if (!dbus_message_iter_append_basic(&it, DBUS_TYPE_UINT32, &level)){
      message(true, "Out Of Memory! Every night the steam part of my brain and feed it to their mum!");
      dbus_message_unref(reply);
      return;      
   }


   if (!dbus_connection_send(conn, reply, &serial)){
      message(true, "Out Of Memory! Universe error, redo from start!");
      dbus_message_unref(reply);
      return;      
   }
   dbus_connection_flush(conn);
   dbus_message_unref(reply);
}


void handle_introspection_message(DBusMessage* msg)
{
   DBusMessage* reply;
   DBusMessageIter it;
   dbus_uint32_t serial;
   char* xml = DBUS_INTROSPECTION_DATA;

   message(true, "Handling dbus introspection query\n");
   reply = dbus_message_new_method_return(msg);
   if (NULL == reply) {
      message(true, "Reply is NULL!! You have been eaten by a GRUE!");
      return;      
   }
   dbus_message_iter_init_append(reply, &it);
   if (!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &xml)){
      message(true, "Out Of Memory! The bells! the bells!");
      dbus_message_unref(reply);
      return;      
   }

   if (!dbus_connection_send(conn, reply, &serial)){
      message(true, "Out Of Memory! Universe error, redo from start!");
      dbus_message_unref(reply);
      return;      
   }
   dbus_connection_flush(conn);
   dbus_message_unref(reply);
}

int main(int argc, char ** argv)
{
   int dev_id, pid;
   int track, threshold, interval;
   char* btid;
   int ret;
   int lq = 0;
   bool run = true;
   bool foo = false;
   DBusMessage* msg;
   DBusMessage* err;
   dbus_uint32_t serial;

   progname = argv[0];

   readargs(argc, argv, &args);
   readoptions(args, &opts);

   // fork
   if (args.fork && opts.fork) {
      pid = fork();
      if (pid < 0)
         printf("Cannot fork");
      if (pid > 0)
         exit(0);
      else {
         setsid();
         chdir("/");
         umask(0);
      }
   }

   writepidfile();

   // syslog
   if (!(args.stdout || opts.stdout)) 
      openlog("bluetooth-monitor",0, LOG_DAEMON);

   message(false, "Bluetooth monitoring system startup, version %s\n", VERSION);
   
   // register cleanup code
   signal(SIGTERM, sighandle);
   signal(SIGINT, sighandle);
   signal(SIGHUP, sighandle);

   conn = register_dbus();

   threshold = testint(args.threshold, opts.threshold, "No Link Quality Threshold Specified");

   btdev_t* btdev = testdev(args.btdevroot, opts.btdevroot, "No Devices Specified");
   for (; NULL != btdev; btdev = btdev->next) {
      str2ba(btdev->btid, &(btdev->bdaddr));

      message(true, "Connecting to address %x:%x:%x:%x:%x:%x\n", btdev->bdaddr.b[5], btdev->bdaddr.b[4], btdev->bdaddr.b[3], btdev->bdaddr.b[2], btdev->bdaddr.b[1], btdev->bdaddr.b[0]);
      dev_id = hci_get_route(&(btdev->bdaddr));

      if (dev_id < 0) {
         message(false, "Device is not available.\n");
         exit(1);
      }

      btdev->dd = hci_open_dev(dev_id);
      if (btdev->dd < 0) {
         message(false, "HCI device open failed\n");
         exit(1);
      }

      foo = btconnect(btdev);
      if (!btdev->connected)
         message(true, "Using existing connection\n");
      if (!foo) lq = -1;

      message(true, "Monitoring link quality\n");
      btdev->track = get_link_qual(btdev);
      if (btdev->track >= threshold)
         sendsignal(true, btdev);
   }
   
   // drop privs
   if ((args.uid || opts.uid) && (args.gid || opts.gid))
      setreuid(args.uid?args.uid:opts.uid, args.gid?args.gid:opts.gid);

   while (run) {
      threshold = testint(args.threshold, opts.threshold, "No Link Quality Threshold Specified");
      interval = testint(args.interval, opts.interval, "No Interval Specified");

      btdev_t* btdev = testdev(args.btdevroot, opts.btdevroot, "No Devices Specified");
      for (; NULL != btdev; btdev = btdev->next) {
         // if device disconnects every 54s check before reconnect statement
         if (args.disconnecthack || opts.disconnecthack)
            lq = get_link_qual(btdev);
         if (-1 == lq)
            btconnect(btdev);
         
         lq = get_link_qual(btdev);

         if (lq >= threshold && btdev->track < threshold) {
            // good quality, stop the screensaver
            sendsignal(true, btdev);
            message(false, "Device %s Connected\n", btdev->btid);
         }
         else if (lq < threshold && btdev->track >= threshold) {
            // bad quality, start the screensaver
            sendsignal(false, btdev);
            message(false, "Device %s Disconnected\n", btdev->btid);
         }
         btdev->track = lq;
      }

      dbus_connection_read_write(conn, interval);
      while (NULL != (msg = dbus_connection_pop_message(conn))) {
         if (dbus_message_is_method_call(msg, "cx.ath.matthew.bluemon.Bluemon", "Status"))
            handle_dbus_message(msg, threshold);
         else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect"))
            handle_introspection_message(msg);
         else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Peer", "Ping")) {
            err = dbus_message_new_method_return (msg);
            dbus_connection_send(conn, err, &serial);
            dbus_message_unref(err);
         }
         else if (DBUS_MESSAGE_TYPE_METHOD_CALL == dbus_message_get_type(msg)) {
            err = dbus_message_new_error (msg, "cx.ath.matthew.bluemon.error", "Unknown Method");
            dbus_connection_send(conn, err, &serial);
            dbus_message_unref(err);
         }
         dbus_message_unref(msg);
      } 
   } 

   btdev = testdev(args.btdevroot, opts.btdevroot, "No Devices Specified");
   while (NULL != btdev) {
      if (btdev->connected) {
         // disconnect bluetooth
         btdisconnect(btdev);
         if (NULL != conn)
            sendsignal(false, btdev);
         message(true, "Disconnected %s\n", btdev->btid);
      }
      hci_close_dev(btdev->dd);
      btdev_t* temp = btdev->next;
      free(btdev);
      btdev = temp;
   }
   // disconnect socket
   if (NULL != conn)
      dbus_connection_unref(conn);
}

