/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#if defined(__linux__)
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <linux/wireless.h> /* wireless extension */

#include <qstringlist.h>

#include "kwirelessmonitor.h"

int wm_getWirelessInterfaces(QStringList& ifList)
{
    /* use /proc/net/dev for all interfaces */
    FILE *f = fopen("/proc/net/wireless", "r");
    if (f == NULL) {
        return -1;
    }
    
    char buf[512];
    fgets(buf, sizeof(buf), f);
    fgets(buf, sizeof(buf), f);
    while (fgets(buf, sizeof(buf), f)) {
        char *begin = buf;
        while (isspace(*begin)) {
            begin += 1;
        }
        char *end = strchr(buf, ':');
        if (end) {
            *end = 0;
            ifList.append(begin);
        }
    }
    
    fclose(f);
    return 0; 
}

int wm_openIFSocket(const char *ifname)
{
    if (strlen(ifname) < 1) {
        return -1;
    }
    return socket(PF_INET, SOCK_DGRAM, 0);
}

void wm_closeIFSocket(int sock)
{
    if (sock >= 0) {
        close(sock);
    }
}

void wm_getESSID(int sock, const char *ifname, char *buf, int len)
{
    struct iwreq wreq;
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    wreq.u.essid.pointer = (caddr_t) buf;
    wreq.u.essid.length = len;
    wreq.u.essid.flags = 0;
    if (ioctl(sock, SIOCGIWESSID, &wreq) < 0) {
        strncpy(buf, "unknown", len - 1);
        buf[len] = 0;
    } else {
        buf[wreq.u.essid.length] = 0;
    }
}

int wm_getBitrate(int sock, const char *ifname)
{
    struct iwreq wreq;
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    if (ioctl(sock, SIOCGIWRATE, &wreq) < 0) {
        return -1;
    }
    return wreq.u.bitrate.value;
}

int getMaxQuality(int sock, const char *ifname)
{
    char buffer[sizeof(struct iw_range) * 2];
    struct iwreq wreq;

    memset(buffer, 0, sizeof(buffer));
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);

    wreq.u.data.pointer = (caddr_t) buffer;
    wreq.u.data.length = sizeof(buffer);
    wreq.u.data.flags = 0;
    if (ioctl(sock, SIOCGIWRANGE, &wreq) < 0) {
        return 1;
    }
    
    struct iw_range *wrange = (struct iw_range *) buffer;
    return wrange->max_qual.qual;
}

int wm_getQuality(int sock, const char *ifname)
{
    /* Get interface stats */
    /* (Assume WIRELESS_EXT > 11) */
    struct iwreq wreq;
    struct iw_statistics wstats;
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    wreq.u.data.pointer = (caddr_t) &wstats;
    wreq.u.data.length = 0;
    wreq.u.data.flags = 1;
    if (ioctl(sock, SIOCGIWSTATS, &wreq) < 0) {
        return -1;
    }
  
    int max_qual = getMaxQuality(sock, ifname);
    int level = wstats.qual.level;
    int noise = wstats.qual.noise;
    int quality = wstats.qual.qual;
    
    if (level <= 0) {
        return 0;
    }
    if ((quality > max_qual) || (quality <= 1)) {
        /* XXX assume bogus */
        quality = level - noise;

        /* XXX arbitrary threshold */
        quality = (quality <= 100) ? quality : 100;
        max_qual = 100;
    }
    
    return (int) rint(log((double) quality) / log((double) max_qual)
                      * 100.0);
}

int wm_getPowerMgmt(int sock, const char *ifname)
{
    struct iwreq wreq;
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    if (ioctl(sock, SIOCGIWPOWER, &wreq) < 0) {
        return -1;
    } else {
        if (wreq.u.power.disabled == 0) {
            return PMOn;
        } else {
            return PMOff;
        }
    }
}

bool wm_isOnACPower()
{
    bool ret = true;
    char buf[64];
    FILE *f = NULL;

    DIR *acpi_ac = opendir("/proc/acpi/ac_adapter");
    if (acpi_ac != NULL) {
        struct dirent *ent = readdir(acpi_ac);
        while (ent) {
            if (strncmp(ent->d_name, ".", 1) != 0) {
                /* XXX just use the first entry */
                break;
            }
            ent = readdir(acpi_ac);
        }
        if (ent) {
            QString fname = "/proc/acpi/ac_adapter/";
            fname += ent->d_name;
            fname += "/state";
            f = fopen(fname.ascii(), "r");
        }
    }
   
    if (f != NULL) {
        fgets(buf, sizeof(buf), f);
        fclose(f);
        char *begin = buf;
        while (isgraph(*begin)) {
            begin += 1;
        }
        while (isspace(*begin)) {
            begin += 1;
        }
        if (strncmp(begin, "on", 2) != 0) {
            ret = false;
        }
    } else {
        f = fopen("/proc/apm", "r");
        if (f != NULL) {
            fgets(buf, sizeof(buf), f);
            fclose(f);
            int ac;
            sscanf(buf, "%*s %*s %*x %x ", &ac);
            ret = (ac == 1) ? true : false;
        }
    }
    return ret;
}

void wm_getTransferBytes(const char *ifname,
                         unsigned long long *rx_bytes,
                         unsigned long long *tx_bytes)
{
    FILE *f = fopen("/proc/net/dev", "r");
    if (f == NULL) {
        *rx_bytes = 0;
        *tx_bytes = 0;
        return;
    }
    
    char buf[512];
    fgets(buf, sizeof(buf), f);
    fgets(buf, sizeof(buf), f);
    char *stats = NULL;
    while (fgets(buf, sizeof(buf), f)) {
        char *begin = buf;
        while (isspace(*begin)) {
            begin += 1;
        }
        char *end = strrchr(buf, ':');
        if (end) {
            if (strncmp(begin, ifname, strlen(ifname))) {
                continue;
            }
            stats = end + 1;
            while (isspace(*stats)) {
                stats += 1;
            }
            break;
        }
    }
    fclose(f);
    
    if (stats) {
        sscanf(stats, "%llu %*u %*u %*u %*u %*u %*u %*u %llu ",
               rx_bytes, tx_bytes);
    } else {
        *rx_bytes = 0;
        *tx_bytes = 0;
    }
}

void wm_getBitrateList(int sock, const char *ifname, QValueList<int>& brList)
{
    char buffer[sizeof(struct iw_range) * 2];
    struct iwreq wreq;

    memset(buffer, 0, sizeof(buffer));
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);

    wreq.u.data.pointer = (caddr_t) buffer;
    wreq.u.data.length = sizeof(buffer);
    wreq.u.data.flags = 0;
    if (ioctl(sock, SIOCGIWRANGE, &wreq) < 0) {
        /* XXX assume 802.11b */
        brList.clear();
        brList.append(1000000);
        brList.append(2000000);
        brList.append(5500000);
        brList.append(11000000);
        return;
    }
    
    struct iw_range *wrange = (struct iw_range *) buffer;
    brList.clear();
    for (int i = 0; (i < wrange->num_bitrates) && (i < IW_MAX_BITRATES);
            i++) {
        int br = wrange->bitrate[i];
        QValueList<int>::iterator it = brList.end();
        --it;
        if (it == brList.end()) {
            brList.append(br);
        } else if ((*it) <= br) {
            brList.append(br);
        } else {
            bool inserted = false;
            while (it != brList.begin()) {
                QValueList<int>::iterator oit = it;
                --it;
                if ((*it) <= br) {
                    brList.insert(oit, br);
                    inserted = true;
                    break;
                }
            }
            if (!inserted) {
                brList.insert(it, br);
            }
        }
    }
    return;
}

int wm_setBitrate(int sock, const char *ifname, int rate)
{
    struct iwreq wreq;
    
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    if (rate < 0) { 
        /* Auto */
        wreq.u.bitrate.value = -1;
        wreq.u.bitrate.fixed = 0;
    } else {
        /* Fixed rate */
        wreq.u.bitrate.value = rate;
        wreq.u.bitrate.fixed = 1;
    }
    return ioctl(sock, SIOCSIWRATE, &wreq);
}

int wm_setPowerMgmt(int sock, const char *ifname, int mode)
{
    struct iwreq wreq;
    
    strncpy(wreq.ifr_name, ifname, IFNAMSIZ);
    if (mode == PMOn) {
        ioctl(sock, SIOCGIWPOWER, &wreq);
        wreq.u.power.disabled = 0;
    } else {
        wreq.u.power.disabled = 1;
    }
    return ioctl(sock, SIOCSIWPOWER, &wreq);
}

bool wm_capSettings()
{
    return true;
}

bool wm_capACPower()
{
    return true;
}

#endif /* defined(__linux__) */
