#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>

#include "wpa_ctrl.h"
#include "plugin.h"

#define WIRELESS_PATH      PLUGIN_ROOT_PATH "/wireless"
#define WIRELESS_INTERFACE PLUGIN_ROOT_INTF ".wireless"

static int init(plugin_t *plugin, DBusConnection *connection);
static void deinit(plugin_t *plugin, DBusConnection *connection);
static void handle_incoming(plugin_t *plugin, DBusConnection *connection);
static DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *message, plugin_t *plugin);
static DBusHandlerResult select_network(DBusConnection *connection, DBusMessage *message, plugin_t *plugin);
static DBusHandlerResult wpa_supplicant_request(DBusConnection *connection, DBusMessage *message, plugin_t *plugin, char *cmd);

static int init(plugin_t *plugin, DBusConnection *connection) {
    struct wpa_ctrl *ctrl_conn = NULL;
    const char *ctrl_iface_dir = "/var/run/wpa_supplicant";
    DIR *dir = opendir(ctrl_iface_dir);

    if (dir) {
        struct dirent *dent;
        while ((dent = readdir(dir))) {
            char *cfile;
            int flen;
            if (strcmp(dent->d_name, ".") == 0 ||
                strcmp(dent->d_name, "..") == 0)
                continue;
            printf("Selected interface '%s'\n",
                   dent->d_name);
            flen = strlen(ctrl_iface_dir) + strlen(dent->d_name) + 2;
            cfile = malloc(flen);
            if (cfile == NULL)
                return -1;
            snprintf(cfile, flen, "%s/%s", ctrl_iface_dir, dent->d_name);
            ctrl_conn = wpa_ctrl_open(cfile);
            free(cfile);
            break;
        }
        closedir(dir);
    }

    if (ctrl_conn != NULL) {
        plugin->fd = wpa_ctrl_get_fd(ctrl_conn);
    } else {
        /* do not fail, the plugin will try to re-init when needed */
        plugin->fd = -1;
    }
    plugin->priv = (void *) ctrl_conn;
    return 0;
}

static void deinit(plugin_t *plugin, DBusConnection *connection) {
    if (plugin->fd > 0)
        close(plugin->fd);
}

static void handle_incoming(plugin_t *plugin, DBusConnection *connection) {
    struct wpa_ctrl *ctrl_conn = (struct wpa_ctrl *) plugin->priv;
    char buf[2048];
    size_t len;
    wpa_ctrl_recv(ctrl_conn, buf, &len);
    buf[len] = '\0';
    printf("received event: %s\n", buf);
}

static DBusHandlerResult handle_message(DBusConnection *connection, DBusMessage *message, plugin_t *plugin) {
    if (dbus_message_is_method_call(message, WIRELESS_INTERFACE, "ScanResults")) {
        return wpa_supplicant_request(connection, message, plugin, "SCAN_RESULTS");
    } else if (dbus_message_is_method_call(message, WIRELESS_INTERFACE, "ListNetworks")) {
        return wpa_supplicant_request(connection, message, plugin, "LIST_NETWORKS");
    } else if (dbus_message_is_method_call(message, WIRELESS_INTERFACE, "SelectNetwork")) {
        return select_network(connection, message, plugin);
    }
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static DBusHandlerResult select_network(DBusConnection *connection, DBusMessage *message, plugin_t *plugin) {
    DBusError error;
    u_int32_t net;
    char cmd[32];

    dbus_error_init (&error);
    if (!dbus_message_get_args (message,
                                &error,
                                DBUS_TYPE_UINT32,
                                &net,
                                DBUS_TYPE_INVALID)) {
        fprintf(stderr, "select_network(): failed to read D-BUS message args: %s\n", error.message);
        dbus_error_free (&error);
        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
    dbus_error_free (&error);

    snprintf(cmd, sizeof(cmd), "SELECT_NETWORK %u", net);
    return wpa_supplicant_request(connection, message, plugin, cmd);
}

static DBusHandlerResult wpa_supplicant_request(DBusConnection *connection, DBusMessage *message, plugin_t *plugin, char *cmd) {
    struct wpa_ctrl *ctrl_conn = (struct wpa_ctrl *) plugin->priv;
    DBusMessage *reply;
    char buf[2048];
    size_t len;
    int ret = -1;

    len = sizeof(buf) - 1;
    if (ctrl_conn) {
        ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), buf, &len, NULL);
    }
    if (ret == -1) {
        fprintf(stderr, "connection to wpa_supplicant daemon lost, reconnecting\n");
        init(plugin, connection);
        ctrl_conn = (struct wpa_ctrl *) plugin->priv;
        if (ctrl_conn) {
            ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), buf, &len, NULL);
        }
    }
    if (ret != 0) {
	fprintf(stderr, "unable to request command\n");
	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    }
    buf[len] = '\0';

    reply = dbus_message_new_method_return(message);
    dbus_message_append_args(reply,
			     DBUS_TYPE_STRING,
			     buf,
			     DBUS_TYPE_INVALID);
    dbus_connection_send(connection, reply, NULL);
    dbus_connection_flush(connection);
    dbus_message_unref(reply);

    return DBUS_HANDLER_RESULT_HANDLED;
}

plugin_t wpa_supplicant_plugin = {
    .name = "Wireless",
    .path = WIRELESS_PATH,
    .init = init,
    .handle_incoming = handle_incoming,
    .handle_message = handle_message,
    .deinit = deinit,
};