#include <QDBusConnection>
#include <QDebug>
#include <QDir>
#include <QStringList>
#include <QSettings>
#include <QHash>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "watchit.h"

class WatchKey;

uint qHash(const WatchKey & elt);

class WatchKey {
public:

   WatchKey(QString init_busName,
            QString init_service,
            QString init_path,
            QString init_interface,
            QString init_member);

   WatchKey(const WatchKey & cpy);

   ~WatchKey();

   WatchKey &
   operator=(const WatchKey & cpy);

   bool
   operator==(const WatchKey & src) const;

   uint
   qHash() const;

   QString getBusName()   const { return busName;}
   QString getService()   const { return service;}
   QString getPath()      const { return path;}
   QString getInterface() const { return interface;}
   QString getMember()    const { return member;}

private:

   QString busName;
   QString service;
   QString path;
   QString interface;
   QString member;
};

WatchKey::WatchKey(
   QString init_busName,
   QString init_service,
   QString init_path,
   QString init_interface,
   QString init_member)
 : busName(init_busName), service(init_service), path(init_path),
   interface(init_interface), member(init_member) {
}

WatchKey::WatchKey(const WatchKey & cpy)
 : busName(cpy.busName), service(cpy.service), path(cpy.path),
   interface(cpy.interface), member(cpy.member) {
}

WatchKey::~WatchKey() {
}

WatchKey &
WatchKey::operator=(const WatchKey & cpy) {
   busName = cpy.busName;
   service = cpy.service;
   path = cpy.path;
   interface = cpy.interface;
   member = cpy.member;
   return *this;
}

bool
WatchKey::operator==(const WatchKey & src) const {
   return busName == src.busName &&
          service == src.service &&
          path == src.path &&
          interface == src.interface &&
          member == src.member;
}

uint
qHash(const WatchKey & elt) {
   return qHash(elt.getBusName()) ^
          qHash(elt.getService()) ^
          qHash(elt.getPath()) ^
          qHash(elt.getInterface()) ^
          qHash(elt.getMember());
}

QHash<WatchKey, WatchIt *> watch_table;

WatchIt::WatchIt(
   QString init_busName,
   QString init_service,
   QString init_path,
   QString init_interface,
   QString init_member)
 : busName(init_busName), service(init_service), path(init_path),
   interface(init_interface), member(init_member) {

   if (busName == "system") {
      QDBusConnection::systemBus().
         connect(service, path, interface, member, this,
                 SLOT(SeeString(QString)));
   } else {
      QDBusConnection::sessionBus().
         connect(service, path, interface, member, this,
                 SLOT(SeeString(QString)));
   }
}

WatchIt::~WatchIt() {
   if (busName == "system") {
      QDBusConnection::systemBus().
         disconnect(service, path, interface, member, this,
                    SLOT(SeeString(QString)));
   } else {
      QDBusConnection::sessionBus().
         disconnect(service, path, interface, member, this,
                    SLOT(SeeString(QString)));
   }
}

void
WatchIt::AddExec(QString string_arg, QString exec) {
   exec_table.insert(string_arg, exec);
}

void
WatchIt::ReadConfigFiles(QString configdir) {
   QDir d(configdir);
   QStringList suffix;
   suffix << "*.xdbd";
   d.setFilter(QDir::Files|QDir::Readable);
   d.setNameFilters(suffix);
   QStringList config_files = d.entryList();
   for (int i = 0; i < config_files.size(); ++i) {
      QString errors;
      QString one_config = config_files.at(i);
      QString fullname(configdir);
      fullname.append("/");
      fullname.append(one_config);
      QSettings qs(fullname, QSettings::IniFormat);
      QString busName = qs.value("busName", QVariant("session")).toString();
      if (! ((busName == "session") || (busName == "system"))) {
         errors.append(fullname);
         errors.append(" busName must be \"session\" or \"system\"\n");
         busName = "session";
      }
      QString service = qs.value("service", QVariant("")).toString();
      QString path = qs.value("path", QVariant("")).toString();
      QString interface;
      if (! qs.contains("interface")) {
         errors.append(fullname);
         errors.append(" missing required key \"interface\"\n");
      } else {
         interface = qs.value("interface").toString();
      }
      QString member;
      if (! qs.contains("member")) {
         errors.append(fullname);
         errors.append(" missing required key \"member\"\n");
      } else {
         member = qs.value("member").toString();
      }
      QString string_arg;
      if (! qs.contains("string")) {
         errors.append(fullname);
         errors.append(" missing required key \"string\"\n");
      } else {
         string_arg = qs.value("string").toString();
      }
      QString exec;
      if (! qs.contains("exec")) {
         errors.append(fullname);
         errors.append(" missing required key \"exec\"\n");
      } else {
         exec = qs.value("exec").toString();
      }
      QString keyval = qs.value("key", QVariant("default")).toString();
      if (errors.size() > 0) {
         std::cerr << errors.toStdString();
         errors = QString();
      } else {
         AddWatch(busName, service, path, interface, member, string_arg, exec);
      }
   }
}

void
WatchIt::AddWatch(
   QString busName,
   QString service,
   QString path,
   QString interface,
   QString member,
   QString string_arg,
   QString exec) {

   WatchKey key(busName, service, path, interface, member);
   WatchIt * wip;
   if (watch_table.contains(key)) {
      wip = watch_table[key];
   } else {
      wip = new WatchIt(busName, service, path, interface, member);
      watch_table[key] = wip;
   }
   wip->AddExec(string_arg, exec);
}

void
WatchIt::SeeString(QString arg) {
   QStringList hits = exec_table.values(arg);
   for (int i = 0; i < hits.size(); ++i) {
      pid_t kid = ::fork();
      if (kid == 0) {
         pid_t grandkid = ::fork();
         if (grandkid == 0) {
            ::execl("/bin/bash", "/bin/bash", "-c",
                    hits.at(i).toStdString().c_str(), NULL);
            ::_exit(2);
         }
         ::_exit(0);
      } else if (kid == (pid_t)-1) {
         std::cerr << "Fork failed!\n";
      } else {
         int waitstat;
         ::waitpid(kid, &waitstat, 0);
      }
   }
}
