/***************************************************************************
 *                                                                         *
 *   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(__FreeBSD__)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <net80211/ieee80211_ioctl.h>
#include <dev/wi/if_wavelan_ieee.h>
#include <machine/apm_bios.h>

#include <map>

#include <qstringlist.h>
#include <qthread.h>
#include <qmutex.h>

#include "kwirelessmonitor.h"

int wm_getWirelessInterfaces(QStringList& ifList)
{
    int sock;
    if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
        return -1;
    }

    int mib[6];
    size_t oldsize;
    char *buf, *next;
    memset(mib, 0, sizeof(int) * 6);
    mib[0] = CTL_NET;
    mib[1] = PF_ROUTE;
    mib[4] = NET_RT_IFLIST;

    if (sysctl(mib, 6, NULL, &oldsize, NULL, 0) < 0) {
        return -1;
    }
    if (!(buf = (char *) malloc(oldsize))) {
        return -1;
    }
    if (sysctl(mib, 6, buf, &oldsize, NULL, 0) < 0) {
        free(buf);
        return -1;
    }
    next = buf;
    while (next < (buf + oldsize)) {
        struct if_msghdr *ifm = (struct if_msghdr *) next;
        if (ifm->ifm_type != RTM_IFINFO) {
            free(buf);
            return -1;
        }
        struct sockaddr_dl *sdl = (struct sockaddr_dl *) (ifm + 1);
        char ifname[IFNAMSIZ];
        strncpy(ifname, sdl->sdl_data, sdl->sdl_nlen);
        ifname[sdl->sdl_nlen] = 0;
       
        struct ifmediareq imreq;
        memset(&imreq, 0, sizeof(imreq));
        strncpy(imreq.ifm_name, ifname, sizeof(imreq.ifm_name));
        if (ioctl(sock, SIOCGIFMEDIA, &imreq) >= 0) {
            if (IFM_TYPE(imreq.ifm_active) == IFM_IEEE80211) {
                ifList.append(ifname);
            }
        }
        
        next += ifm->ifm_msglen;
        while (next < (buf + oldsize)) {
            struct if_msghdr *tmp = (struct if_msghdr *) next;
            if (tmp->ifm_type != RTM_NEWADDR) {
                break;
            }
            next += tmp->ifm_msglen;
        }
    }
    free(buf);
    return 0;
}

std::map<int,int> bitrateMap;

int wm_openIFSocket(const char *ifname)
{
    if (strlen(ifname) < 1) {
        return -1;
    }
    bitrateMap.clear();
    bitrateMap[-1] = IFM_AUTO;
    bitrateMap[0] = IFM_AUTO;
    bitrateMap[1000000] = IFM_IEEE80211_DS1;
    bitrateMap[2000000] = IFM_IEEE80211_DS2;
    bitrateMap[5500000] = IFM_IEEE80211_DS5;
    bitrateMap[11000000] = IFM_IEEE80211_DS11;

    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 ieee80211req wreq;
    memset(&wreq, 0, sizeof(wreq));
    strncpy(wreq.i_name, ifname, sizeof(wreq.i_name));
    // XXX buf must be large enough
    wreq.i_data = buf;
    wreq.i_type = IEEE80211_IOC_SSID;
    if (ioctl(sock, SIOCG80211, &wreq) < 0) {
        strncpy(buf, "unknown", len -1);
        buf[len] = 0;
    } else {
        buf[wreq.i_len] = 0;
    }
}

int wm_bitrate_list[] = { 0, 0, 0,
        1000000, /* IFM_IEEE80211_FH1   3   1Mbps */
        2000000, /* IFM_IEEE80211_FH2   4   2Mbps */
        1000000, /* IFM_IEEE80211_DS1   5   1Mbps */
        2000000, /* IFM_IEEE80211_DS2   6   2Mbps */
        5500000, /* IFM_IEEE80211_DS5   7   5.5Mbps */
       11000000, /* IFM_IEEE80211_DS11  8   11Mbps */
       22000000, /* IFM_IEEE80211_DS22  9   22Mbps */
        6000000, /* IFM_IEEE80211_OFDM6 10  6Mbps */
        9000000, /* IFM_IEEE80211_OFDM9 11  9Mbps */
       12000000, /* IFM_IEEE80211_OFDM12    12  12Mbps */
       18000000, /* IFM_IEEE80211_OFDM18    13  18Mbps */
       24000000, /* IFM_IEEE80211_OFDM24    14  24Mbps */
       36000000, /* IFM_IEEE80211_OFDM36    15  36Mbps */
       48000000, /* IFM_IEEE80211_OFDM48    16  48Mbps */
       54000000, /* IFM_IEEE80211_OFDM54    17  54Mbps */
       72000000  /* IFM_IEEE80211_OFDM72    18  72Mbps */
    };

int wm_getBitrate(int sock, const char *ifname)
{
    struct ifmediareq imreq;
    memset(&imreq, 0, sizeof(imreq));
    strncpy(imreq.ifm_name, ifname, sizeof(imreq.ifm_name));
    if (ioctl(sock, SIOCGIFMEDIA, &imreq) < 0) {
        return -1;
    }
    int index = IFM_SUBTYPE(imreq.ifm_active);
    if ((index < IFM_IEEE80211_FH1) || (index > IFM_IEEE80211_OFDM72)) {
        index = IFM_IEEE80211_DS11;
    }
    return wm_bitrate_list[index];
}

int getMaxQuality(int sock, const char *ifname)
{
    // XXX bogus
    return 1;
}

int wm_getQuality(int sock, const char *ifname)
{
    struct wi_req wireq;
    struct ifreq ireq;
    memset(&wireq, 0, sizeof(wireq));
    wireq.wi_len = WI_MAX_DATALEN;
    wireq.wi_type = WI_RID_COMMS_QUALITY;
    memset(&ireq, 0, sizeof(ireq));
    strncpy(ireq.ifr_name, ifname, sizeof(ireq.ifr_name));
    ireq.ifr_data = (caddr_t) &wireq; 
    if (ioctl(sock, SIOCGWAVELAN, &ireq) < 0) {
        return -1;
    }
    if (wireq.wi_val[0] <= 0) {
        return 0;
    }
    return wireq.wi_val[0];
}

int wm_getPowerMgmt(int sock, const char *ifname)
{
    struct ieee80211req wreq;
    memset(&wreq, 0, sizeof(wreq));
    strncpy(wreq.i_name, ifname, sizeof(wreq.i_name));
    wreq.i_type = IEEE80211_IOC_POWERSAVE;
    if (ioctl(sock, SIOCG80211, &wreq) < 0) {
        return -1;
    } else {
        if (wreq.i_val != 0) {
            return PMOn;
        } else {
            return PMOff;
        }
    }
}

bool wm_isOnACPower()
{
    /* XXX APM only */
    int apmfd;

    if ((apmfd = open("/dev/apm", O_RDONLY)) < 0) {
        return true;
    }
    
    struct apm_info info;
    if (ioctl(apmfd, APMIO_GETINFO, &info) < 0) {
        return true;
    }

    if (info.ai_acline == 0) {
        return false;
    }
    return true;
}

int wm_setBitrate(int sock, const char *ifname, int rate)
{
    struct ifreq ireq;
    memset(&ireq, 0, sizeof(ireq));
    strncpy(ireq.ifr_name, ifname, sizeof(ireq.ifr_name));

    int rate_type = IFM_AUTO;
    if (rate >= 0) {
        rate_type = bitrateMap[rate];
    }
    ireq.ifr_media = IFM_IEEE80211 | rate_type;

    return ioctl(sock, SIOCSIFMEDIA, &ireq);
}

int wm_setPowerMgmt(int sock, const char *ifname, int mode)
{
    struct ieee80211req wreq;

    memset(&wreq, 0, sizeof(wreq));
    strncpy(wreq.i_name, ifname, sizeof(wreq.i_name));
    wreq.i_type = IEEE80211_IOC_POWERSAVE;
    if (mode  == PMOn) {
        wreq.i_val = IEEE80211_POWERSAVE_ON;
    } else {
        wreq.i_val = IEEE80211_POWERSAVE_OFF;
    }
    wreq.i_len = 0;
    wreq.i_data = NULL;
    return ioctl(sock, SIOCS80211, &wreq);
}

bool wm_capSettings()
{
    return true;
}

bool wm_capACPower()
{
    return true;
}

/* XXX ugly hack to get tx bytes */
/* (why does it require root (kvm_read) to get rx/tx pkts/bytes?!) */
class OBytesUpdateThread : public QThread
{
public:
    OBytesUpdateThread();
    void setIFName(const char *ifname);
    virtual void run();
    unsigned long long getOBytes();

private:
    FILE *mNetStat;
    QString mIFName;
    unsigned long long mOBytes;
    unsigned long long mAddBytes;
    QMutex mMx;
};

OBytesUpdateThread::OBytesUpdateThread() : QThread()
{
    mNetStat = NULL;
    mIFName = "";
    mOBytes = 0;
    mAddBytes = 0;
}

void OBytesUpdateThread::setIFName(const char *ifname)
{
    if (strncmp(ifname, mIFName.ascii(), strlen(ifname))) {
        mIFName = ifname;
        if (mNetStat) {
            pclose(mNetStat);
            mNetStat = NULL;
        }
        QString cmd = "netstat -w 1 -I " + mIFName;
        mNetStat = popen(cmd.ascii(), "r");
    }
}

void OBytesUpdateThread::run()
{
    char buf[256];
    while (1) {
        if (!mNetStat) {
            msleep(500);
            continue;
        }

        int ret;
        unsigned long long obytes;
        while (1) {
            fgets(buf, 256, mNetStat);
            ret = sscanf(buf, " %*d %*d %*d %*d %*d %llu ", &obytes);
            if (ret == 1) {
                unsigned long long add = obytes / 2;
            
                mMx.lock();
                mOBytes += add;
                mAddBytes += add;
                mMx.unlock();
                
                break;
            }
        }
    }
}

unsigned long long OBytesUpdateThread::getOBytes()
{
    mMx.lock();
    unsigned long long ret = mOBytes;
    if (mAddBytes) {
        mOBytes += mAddBytes;
        mAddBytes = 0;
    }
    mMx.unlock();

    return ret;
}

void wm_getTransferBytes(const char *ifname,
                         unsigned long long *rx, unsigned long long *tx)
{
    static OBytesUpdateThread * obytesTh = NULL;
    if (!obytesTh) {
        obytesTh = new OBytesUpdateThread();
        obytesTh->start();
    }
    obytesTh->setIFName(ifname);
    *rx = 0;
    *tx = obytesTh->getOBytes();
}

void wm_getBitrateList(int sock, const char *ifname, QValueList<int>& brList)
{
    /* XXX assume 802.11b */
    brList.clear();
    brList.append(1000000);
    brList.append(2000000);
    brList.append(5500000);
    brList.append(11000000);
}

#endif /* defined(__FreeBSD__) */
