/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>

#define LOG_TAG "Supplicant"
#include <cutils/log.h>
#include <cutils/properties.h>

#include "private/android_filesystem_config.h"

#include <sysutils/ServiceManager.h>

#include "Supplicant.h"
#include "SupplicantListener.h"
#include "NetworkManager.h"
#include "ErrorCode.h"
#include "WifiController.h"
#include "SupplicantStatus.h"

#include "libwpa_client/wpa_ctrl.h"

#define IFACE_DIR        "/data/system/wpa_supplicant"
#define DRIVER_PROP_NAME "wlan.driver.status"
#define SUPPLICANT_SERVICE_NAME  "wpa_supplicant"
#define SUPP_CONFIG_TEMPLATE "/system/etc/wifi/wpa_supplicant.conf"
#define SUPP_CONFIG_FILE "/data/misc/wifi/wpa_supplicant.conf"

Supplicant::Supplicant(WifiController *wc, ISupplicantEventHandler *handlers) {
    mHandlers = handlers;
    mController = wc;
    mInterfaceName = NULL;
    mCtrl = NULL;
    mMonitor = NULL;
    mListener = NULL;
   
    mServiceManager = new ServiceManager();

    mNetworks = new WifiNetworkCollection();
    pthread_mutex_init(&mNetworksLock, NULL);
}

Supplicant::~Supplicant() {
    delete mServiceManager;
    if (mInterfaceName)
        free(mInterfaceName);
}

int Supplicant::start() {

    if (setupConfig()) {
        LOGW("Unable to setup supplicant.conf");
    }

    if (mServiceManager->start(SUPPLICANT_SERVICE_NAME)) {
        LOGE("Error starting supplicant (%s)", strerror(errno));
        return -1;
    }

    wpa_ctrl_cleanup();
    if (connectToSupplicant()) {
        LOGE("Error connecting to supplicant (%s)\n", strerror(errno));
        return -1;
    }
    
    if (retrieveInterfaceName()) {
        LOGE("Error retrieving interface name (%s)\n", strerror(errno));
        return -1;
    }

    return 0;
}

int Supplicant::stop() {

    if (mListener->stopListener()) {
        LOGW("Unable to stop supplicant listener (%s)", strerror(errno));
        return -1;
    }

    if (mServiceManager->stop(SUPPLICANT_SERVICE_NAME)) {
        LOGW("Error stopping supplicant (%s)", strerror(errno));
    }

    if (mCtrl) {
        wpa_ctrl_close(mCtrl);
        mCtrl = NULL;
    }
    if (mMonitor) {
        wpa_ctrl_close(mMonitor);
        mMonitor = NULL;
    }

    return 0;
}

bool Supplicant::isStarted() {
    return mServiceManager->isRunning(SUPPLICANT_SERVICE_NAME);
}

SupplicantStatus *Supplicant::getStatus() {
    char *reply;
    size_t len = 4096;

    if (!(reply = (char *) malloc(len))) {
        errno = ENOMEM;
        return NULL;
    }

    if (sendCommand("STATUS", reply, &len)) {
        free(reply);
        return NULL;
    }

    SupplicantStatus *ss = SupplicantStatus::createStatus(reply, len);
  
    free (reply);
    return ss;
}

/*
 * Retrieves the list of networks from Supplicant
 * and merge them into our current list
 */
int Supplicant::refreshNetworkList() {
    char *reply;
    size_t len = 4096;

    if (!(reply = (char *) malloc(len))) {
        errno = ENOMEM;
        return -1;
    }

    if (sendCommand("LIST_NETWORKS", reply, &len)) {
        free(reply);
        return -1;
    }

    char *linep;
    char *linep_next = NULL;

    if (!strtok_r(reply, "\n", &linep_next)) {
        LOGW("Malformatted network list\n");
        free(reply);
        errno = EIO;
        return -1;
    }

    pthread_mutex_lock(&mNetworksLock);

    int num_added = 0;
    int num_refreshed = 0;
    int num_removed = 0;
    while((linep = strtok_r(NULL, "\n", &linep_next))) {
        // TODO: Move the decode into a static method so we
        // don't create new_wn when we don't have to.
        WifiNetwork *new_wn = new WifiNetwork(mController, this, linep);
        WifiNetwork *merge_wn;

        if ((merge_wn = this->lookupNetwork_UNLOCKED(new_wn->getNetworkId()))) {
            num_refreshed++;
            if (merge_wn->refresh()) {
                LOGW("Error refreshing network %d (%s)",
                     merge_wn->getNetworkId(), strerror(errno));
                }
            delete new_wn;
        } else {
            num_added++;
            new_wn->registerProperties();
            mNetworks->push_back(new_wn);
            if (new_wn->refresh()) {
                LOGW("Unable to refresh network id %d (%s)",
                    new_wn->getNetworkId(), strerror(errno));
            }
        }
    }

    if (!mNetworks->empty()) {
        // TODO: Add support for detecting removed networks
        WifiNetworkCollection::iterator i;

        for (i = mNetworks->begin(); i != mNetworks->end(); ++i) {
            if (0) {
                num_removed++;
                (*i)->unregisterProperties();
                delete (*i);
                i = mNetworks->erase(i);
            }
        }
    }


    LOGD("Networks added %d, refreshed %d, removed %d\n", 
         num_added, num_refreshed, num_removed);
    pthread_mutex_unlock(&mNetworksLock);

    free(reply);
    return 0;
}

int Supplicant::connectToSupplicant() {
    if (!isStarted())
        LOGW("Supplicant service not running");

    mCtrl = wpa_ctrl_open("tiwlan0"); // XXX:
    if (mCtrl == NULL) {
        LOGE("Unable to open connection to supplicant on \"%s\": %s",
             "tiwlan0", strerror(errno));
        return -1;
    }
    mMonitor = wpa_ctrl_open("tiwlan0");
    if (mMonitor == NULL) {
        wpa_ctrl_close(mCtrl);
        mCtrl = NULL;
        return -1;
    }
    if (wpa_ctrl_attach(mMonitor) != 0) {
        wpa_ctrl_close(mMonitor);
        wpa_ctrl_close(mCtrl);
        mCtrl = mMonitor = NULL;
        return -1;
    }

    mListener = new SupplicantListener(mHandlers, mMonitor);

    if (mListener->startListener()) {
        LOGE("Error - unable to start supplicant listener");
        stop();
        return -1;
    }
    return 0;
}

int Supplicant::sendCommand(const char *cmd, char *reply, size_t *reply_len)
{
    if (!mCtrl) {
        errno = ENOTCONN;
        return -1;
    }

//    LOGD("sendCommand(): -> '%s'", cmd);

    int rc;
    memset(reply, 0, *reply_len);
    if ((rc = wpa_ctrl_request(mCtrl, cmd, strlen(cmd), reply, reply_len, NULL)) == -2)  {
        errno = ETIMEDOUT;
        return -1;
    } else if (rc < 0 || !strncmp(reply, "FAIL", 4)) {
        strcpy(reply, "FAIL");
        errno = EIO;
        return -1;
    }

//   LOGD("sendCommand(): <- '%s'", reply);
    return 0;
}

int Supplicant::triggerScan(bool active) {
    char reply[255];
    size_t len = sizeof(reply);

    if (sendCommand((active ? "DRIVER SCAN-ACTIVE" : "DRIVER SCAN-PASSIVE"),
                     reply, &len)) {
        LOGW("triggerScan(%d): Error setting scan mode (%s)", active,
             strerror(errno));
        return -1;
    }
    len = sizeof(reply);

    if (sendCommand("SCAN", reply, &len)) {
        LOGW("triggerScan(%d): Error initiating scan", active);
        return -1;
    }
    return 0;
}

WifiNetwork *Supplicant::createNetwork() {
    char reply[255];
    size_t len = sizeof(reply) -1;

    if (sendCommand("ADD_NETWORK", reply, &len))
        return NULL;

    if (reply[strlen(reply) -1] == '\n')
        reply[strlen(reply) -1] = '\0';

    WifiNetwork *wn = new WifiNetwork(mController, this, atoi(reply));
    pthread_mutex_lock(&mNetworksLock);
    mNetworks->push_back(wn);
    pthread_mutex_unlock(&mNetworksLock);
    return wn;
}

int Supplicant::removeNetwork(WifiNetwork *wn) {
    char req[64];

    sprintf(req, "REMOVE_NETWORK %d", wn->getNetworkId());
    char reply[32];
    size_t len = sizeof(reply) -1;

    if (sendCommand(req, reply, &len))
        return -1;

    pthread_mutex_lock(&mNetworksLock);
    WifiNetworkCollection::iterator it;
    for (it = mNetworks->begin(); it != mNetworks->end(); ++it) {
        if ((*it) == wn) {
            mNetworks->erase(it);
            break;
        }
    }
    pthread_mutex_unlock(&mNetworksLock);
    return 0;
}

WifiNetwork *Supplicant::lookupNetwork(int networkId) {
    pthread_mutex_lock(&mNetworksLock);
    WifiNetwork *wn = lookupNetwork_UNLOCKED(networkId);
    pthread_mutex_unlock(&mNetworksLock);
    return wn;
}

WifiNetwork *Supplicant::lookupNetwork_UNLOCKED(int networkId) {
    WifiNetworkCollection::iterator it;
    for (it = mNetworks->begin(); it != mNetworks->end(); ++it) {
        if ((*it)->getNetworkId() == networkId) {
            return *it;
        }
    }
    errno = ENOENT;
    return NULL;
}

WifiNetworkCollection *Supplicant::createNetworkList() {
    WifiNetworkCollection *d = new WifiNetworkCollection();
    WifiNetworkCollection::iterator i;

    pthread_mutex_lock(&mNetworksLock);
    for (i = mNetworks->begin(); i != mNetworks->end(); ++i)
        d->push_back((*i)->clone());

    pthread_mutex_unlock(&mNetworksLock);
    return d;
}

int Supplicant::setupConfig() {
    char buf[2048];
    int srcfd, destfd;
    int nread;

    if (access(SUPP_CONFIG_FILE, R_OK|W_OK) == 0) {
        return 0;
    } else if (errno != ENOENT) {
        LOGE("Cannot access \"%s\": %s", SUPP_CONFIG_FILE, strerror(errno));
        return -1;
    }

    srcfd = open(SUPP_CONFIG_TEMPLATE, O_RDONLY);
    if (srcfd < 0) {
        LOGE("Cannot open \"%s\": %s", SUPP_CONFIG_TEMPLATE, strerror(errno));
        return -1;
    }

    destfd = open(SUPP_CONFIG_FILE, O_CREAT|O_WRONLY, 0660);
    if (destfd < 0) {
        close(srcfd);
        LOGE("Cannot create \"%s\": %s", SUPP_CONFIG_FILE, strerror(errno));
        return -1;
    }

    while ((nread = read(srcfd, buf, sizeof(buf))) != 0) {
        if (nread < 0) {
            LOGE("Error reading \"%s\": %s", SUPP_CONFIG_TEMPLATE, strerror(errno));
            close(srcfd);
            close(destfd);
            unlink(SUPP_CONFIG_FILE);
            return -1;
        }
        write(destfd, buf, nread);
    }

    close(destfd);
    close(srcfd);

    if (chown(SUPP_CONFIG_FILE, AID_SYSTEM, AID_WIFI) < 0) {
        LOGE("Error changing group ownership of %s to %d: %s",
             SUPP_CONFIG_FILE, AID_WIFI, strerror(errno));
        unlink(SUPP_CONFIG_FILE);
        return -1;
    }
    return 0;
}

int Supplicant::setNetworkVar(int networkId, const char *var, const char *val) {
    char reply[255];
    size_t len = sizeof(reply) -1;

    char *tmp;
    asprintf(&tmp, "SET_NETWORK %d %s \"%s\"", networkId, var, val);
    if (sendCommand(tmp, reply, &len)) {
        free(tmp);
        return -1;
    }
    free(tmp);

    len = sizeof(reply) -1;
    if (sendCommand("SAVE_CONFIG", reply, &len)) {
        LOGE("Error saving config after %s = %s", var, val);
        return -1;
    }
    return 0;
}

const char *Supplicant::getNetworkVar(int networkId, const char *var,
                                      char *buffer, size_t max) {
    size_t len = max - 1;
    char *tmp;

    asprintf(&tmp, "GET_NETWORK %d %s", networkId, var);
    if (sendCommand(tmp, buffer, &len)) {
        free(tmp);
        return NULL;
    }
    free(tmp);
    return buffer;
}

int Supplicant::enableNetwork(int networkId, bool enabled) {
    char req[64];

    if (enabled)
        sprintf(req, "ENABLE_NETWORK %d", networkId);
    else
        sprintf(req, "DISABLE_NETWORK %d", networkId);

    char reply[16];
    size_t len = sizeof(reply) -1;

    if (sendCommand(req, reply, &len))
        return -1;
    return 0;
}


int Supplicant::retrieveInterfaceName() {
    char reply[255];
    size_t len = sizeof(reply) -1;

    if (sendCommand("INTERFACES", reply, &len))
        return -1;

    reply[strlen(reply)-1] = '\0';
    mInterfaceName = strdup(reply);
    return 0;
}