/*
 * Copyright 2008, The Android Open Source Project
 * Copyright 2011, RO178 is01rebuid
 *
 * 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 <fcntl.h>
#include <errno.h>
#include <string.h>

#include "hardware_legacy/wifi.h"
#include "libwpa_client/wpa_ctrl.h"

#define LOG_TAG "WifiHW"
#include "cutils/log.h"
#include "cutils/memory.h"
#include "cutils/misc.h"
#include "cutils/properties.h"
#include "private/android_filesystem_config.h"
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
#include <sys/_system_properties.h>
#endif

static struct wpa_ctrl *ctrl_conn;
static struct wpa_ctrl *monitor_conn;

extern int do_dhcp();
extern int ifc_init();
extern void ifc_close();
extern char *dhcp_lasterror();
extern void get_dhcp_info();
extern int init_module(void *, unsigned long, const char *);
extern int delete_module(const char *, unsigned int);

static char iface[PROPERTY_VALUE_MAX];
// TODO: use new ANDROID_SOCKET mechanism, once support for multiple
// sockets is in

#ifndef WIFI_DRIVER_MODULE_PATH
#define WIFI_DRIVER_MODULE_PATH         "/system/lib/modules/wlan.ko"
#endif
#ifndef WIFI_DRIVER_MODULE_NAME
#define WIFI_DRIVER_MODULE_NAME         "wlan"
#endif
#ifndef WIFI_DRIVER_MODULE_ARG
#define WIFI_DRIVER_MODULE_ARG          ""
#endif
#ifndef WIFI_FIRMWARE_LOADER
#define WIFI_FIRMWARE_LOADER		""
#endif
#define WIFI_TEST_INTERFACE		"sta"

#define WIFI_DRIVER_LOADER_DELAY	1000000

#define WIFI_INSMOD_SLEEP	1000000

static const char WLAN0_DEVICE[]         = "/sys/devices/platform/msm_sdcc.1/mmc_host/mmc1/mmc1:0001/mmc1:0001:1/net/wlan0/wireless/status";

static const char IFACE_DIR[]           = "/data/system/wpa_supplicant";
static const char DRIVER_MODULE_NAME[]  = WIFI_DRIVER_MODULE_NAME;
static const char DRIVER_MODULE_TAG[]   = WIFI_DRIVER_MODULE_NAME;
static const char DRIVER_MODULE_PATH[]  = WIFI_DRIVER_MODULE_PATH;
static const char DRIVER_MODULE_ARG[]   = WIFI_DRIVER_MODULE_ARG;
static const char FIRMWARE_LOADER[]     = WIFI_FIRMWARE_LOADER;
static const char DRIVER_PROP_NAME[]    = "wlan.driver.status";
static const char SUPPLICANT_NAME[]     = "wpa_supplicant";
static const char SUPP_PROP_NAME[]      = "init.svc.wpa_supplicant";
static const char SUPP_CONFIG_TEMPLATE[]= "/system/etc/wifi/wpa_supplicant.conf";
static const char SUPP_CONFIG_FILE[]    = "/data/misc/wifi/wpa_supplicant.conf";
static const char MODULE_FILE[]         = "/proc/modules";

static int insmod(const char *filename, const char *args)
{
  property_set( "ctl.stop"   , "dhcpcd" );
  sched_yield();

  int ret;
  char command1[]="echo 1 > /sys/devices/platform/bwpm/wifi";
  char command2[]="echo 1 > /sys/bus/platform/drivers/msm_sdcc/msm_sdcc.1/polling";
  char command3[]="echo 0 > /sys/devices/platform/bwpm/wifi";
  char command4[]="echo 0 > /sys/bus/platform/drivers/msm_sdcc/msm_sdcc.1/polling";
  char command5[]="stop synergy_service";
  char command6[]="start synergy_service";
  char command7[]="start wpa_supplicant";
  char command8[]="start dhcpcd";
  char command9[]="stop wpa_supplicant";

  ret=system(command5);
  sched_yield();
  usleep(WIFI_INSMOD_SLEEP);

  ret=system(command3);
  sched_yield();

  ret=system(command4);
  sched_yield();

  ret=system(command1);
  sched_yield();

  ret=system(command2);
  sched_yield();
  usleep(WIFI_INSMOD_SLEEP);

  ret=system(command6);
  sched_yield();
  usleep(WIFI_INSMOD_SLEEP);

  ret=system(command9);
  sched_yield();
  usleep(WIFI_INSMOD_SLEEP);  

  ret=system(command7);
  sched_yield();
  usleep(WIFI_INSMOD_SLEEP);

  ret=system(command8);
  sched_yield();

  int maxtry = 10;
  char text[256];

  while (maxtry-- > 0) {
    if (access( WLAN0_DEVICE , R_OK ) == 0) {
      FILE *fp;
      if ((fp = fopen( WLAN0_DEVICE , "r")) != NULL ) {
         fgets(text, 256, fp);
         if ( strncmp( text , "0x0" , 3 ) == 0) {
              return 0;
         }
         fclose(fp);
      }
    }  
    else
    {
      ret=system(command5);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);

      ret=system(command3);
      sched_yield();

      ret=system(command4);
      sched_yield();

      ret=system(command1);
      sched_yield();

      ret=system(command2);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);

      ret=system(command6);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);

      ret=system(command9);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);  

      ret=system(command7);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);

      ret=system(command8);
      sched_yield();
      usleep(WIFI_INSMOD_SLEEP);
    }
  }
  return -1;
}

static int rmmod(const char *modname)
{
  int ret=-1;
  char command[]="echo 0 > /sys/devices/platform/bwpm/wifi";
  char command2[]="echo 0 > /sys/bus/platform/drivers/msm_sdcc/msm_sdcc.1/polling";

  ret=system(command);
  usleep(200000);

  ret=system(command2);

  int maxtry = 50;
  while (maxtry-- > 0) {
    if ( (ret=access( WLAN0_DEVICE , R_OK )) == 0) {
      usleep(100000);
    }  
    else {
      return 0;
    }
  }

  if (ret == 0)
    LOGE("wifi: %s(): Unable to disable wlan0 driver return 0;", __FUNCTION__);

  return -1;
}

int do_dhcp_request(int *ipaddr, int *gateway, int *mask,
                    int *dns1, int *dns2, int *server, int *lease) {
    /* For test driver, always report success */
   if (strcmp(iface, WIFI_TEST_INTERFACE) == 0)
     return 0;

    if (ifc_init() < 0)
        return -1;

    usleep(1000000);
   
    if (do_dhcp(iface) < 0) {
        ifc_close();
        return -1;
    }
    ifc_close();
    get_dhcp_info(ipaddr, gateway, mask, dns1, dns2, server, lease);
    return 0;
}

const char *get_dhcp_error_string() {
    return dhcp_lasterror();
}

static int check_driver_loaded() {
    char driver_status[PROPERTY_VALUE_MAX];
    FILE *proc;
    char line[sizeof(DRIVER_MODULE_TAG)+10];

    if (!property_get(DRIVER_PROP_NAME, driver_status, NULL)
            || strcmp(driver_status, "ok") != 0) {
        return 0;  /* driver not loaded */
    }

    /*
     * If the property says the driver is loaded, check to
     * make sure that the property setting isn't just left
     * over from a previous manual shutdown or a runtime
     * crash.
     */
    if ((proc = fopen(MODULE_FILE, "r")) == NULL) {
      LOGW("wifi: %s(): Could not open %s: %s", __FUNCTION__ , MODULE_FILE, strerror(errno));
      property_set(DRIVER_PROP_NAME, "unloaded");
        return 0;
    }
    while ((fgets(line, sizeof(line), proc)) != NULL) {
        if (strncmp(line, DRIVER_MODULE_TAG, strlen(DRIVER_MODULE_TAG)) == 0) {
            fclose(proc);
            return 1;
        }
    }
    fclose(proc);
    property_set(DRIVER_PROP_NAME, "unloaded");
    return 0;
}

int wifi_load_driver()
{
    char driver_status[PROPERTY_VALUE_MAX];
    int count = 100; /* wait at most 20 seconds for completion */

    if (check_driver_loaded()) {
        return 0;
    }

    if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0) {
        return -1;
    }

    if (strcmp(FIRMWARE_LOADER,"") == 0) {
      usleep(WIFI_DRIVER_LOADER_DELAY);
      property_set(DRIVER_PROP_NAME, "ok");
    }
    else {
      property_set("ctl.start", FIRMWARE_LOADER);
    }
    sched_yield();
    while (count-- > 0) {
        usleep(200000);
        if (property_get(DRIVER_PROP_NAME, driver_status, NULL)) {
            if (strcmp(driver_status, "ok") == 0) {
	      return 0;
            }
            else if (strcmp(DRIVER_PROP_NAME, "failed") == 0) {
	      wifi_unload_driver();
	      return -1;
            }
        }
    }
    property_set(DRIVER_PROP_NAME, "timeout");
    wifi_unload_driver();
    return -1;
}

int wifi_unload_driver()
{
    int count = 20; /* wait at most 10 seconds for completion */

    if (rmmod(DRIVER_MODULE_NAME) == 0) {
	while (count-- > 0) {
	    if (!check_driver_loaded())
		break;
    	    usleep(500000);
	}
	if (count) {
    	    return 0;
	}
	return -1;
    } else
        return -1;

    return 0;
}

int ensure_config_file_exists()
{
    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("wifi: %s(): Cannot access \"%s\": %s", __FUNCTION__ , SUPP_CONFIG_FILE, strerror(errno));
        return -1;
    }

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

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

    while ((nread = read(srcfd, buf, sizeof(buf))) != 0) {
        if (nread < 0) {
            LOGE("wifi: %s(): Error reading \"%s\": %s", __FUNCTION__ , 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("wifi: %s(): Error changing group ownership of %s to %d: %s",
             __FUNCTION__ , SUPP_CONFIG_FILE, AID_WIFI, strerror(errno));
        unlink(SUPP_CONFIG_FILE);
        return -1;
    }
    return 0;
}

int wifi_start_supplicant()
{
    char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
    int count = 200; /* wait at most 20 seconds for completion */
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
    const prop_info *pi;
    unsigned serial = 0;
#endif

    /* Check whether already running */
    if (property_get(SUPP_PROP_NAME, supp_status, NULL)
            && strcmp(supp_status, "running") == 0) {
        return 0;
    }

    /* Before starting the daemon, make sure its config file exists */
   if (ensure_config_file_exists() < 0) {
        LOGE("wifi: %s(): Wi-Fi will not be enabled", __FUNCTION__ );
        return -1;
	}

    /* Clear out any stale socket files that might be left over. */
    wpa_ctrl_cleanup();

#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
    /*
     * Get a reference to the status property, so we can distinguish
     * the case where it goes stopped => running => stopped (i.e.,
     * it start up, but fails right away) from the case in which
     * it starts in the stopped state and never manages to start
     * running at all.
     */
    pi = __system_property_find(SUPP_PROP_NAME);
    if (pi != NULL) {
        serial = pi->serial;
    }
#endif
    property_set("ctl.start", SUPPLICANT_NAME);
    sched_yield();
    while (count-- > 0) {
        usleep(100000);
#ifdef HAVE_LIBC_SYSTEM_PROPERTIES
        if (pi == NULL) {
            pi = __system_property_find(SUPP_PROP_NAME);
        }
        if (pi != NULL) {
            __system_property_read(pi, NULL, supp_status);
            if (strcmp(supp_status, "running") == 0) {
                return 0;
            } else if (pi->serial != serial &&
                    strcmp(supp_status, "stopped") == 0) {
                return -1;
            }
        }
#else
        if (property_get(SUPP_PROP_NAME, supp_status, NULL)) {
        	if (strcmp(supp_status, "running") == 0) {
        		return 0;
	  }
        }
#endif
    }

    LOGE("wifi: %s(): timeout!" , __FUNCTION__ );
    return -1;
}

int wifi_stop_supplicant()
{
    char supp_status[PROPERTY_VALUE_MAX] = {'\0'};
    int count = 50; /* wait at most 5 seconds for completion */

    /* Check whether supplicant already stopped */
    if (property_get(SUPP_PROP_NAME, supp_status, NULL)
        && strcmp(supp_status, "stopped") == 0) {
        return 0;
    }

    property_set("ctl.stop", SUPPLICANT_NAME);
    sched_yield();
    while (count-- > 0) {
        usleep(100000);
        if (property_get(SUPP_PROP_NAME, supp_status, NULL)) {
            if (strcmp(supp_status, "stopped") == 0)
                return 0;
        }
    }
    return -1;
}

int wifi_connect_to_supplicant()
{
    char ifname[] = "/dev/socket/wpa_wlan0";
    char supp_status[PROPERTY_VALUE_MAX] = {'\0'};

    /* Make sure supplicant is running */
    if (!property_get(SUPP_PROP_NAME, supp_status, NULL)
            || strcmp(supp_status, "running") != 0) {
        LOGE("wifi: %s(): Supplicant not running, cannot connect", __FUNCTION__);
        return -1;
    }

    property_get("wifi.interface", iface, WIFI_TEST_INTERFACE);

    ctrl_conn = wpa_ctrl_open(ifname);
    if (ctrl_conn == NULL) {
        LOGE("wifi: %s(): Unable to open connection to supplicant on \"%s\": %s",
             __FUNCTION__ , ifname, strerror(errno));
        return -1;
    }
    monitor_conn = wpa_ctrl_open(ifname);
    if (monitor_conn == NULL) {
        wpa_ctrl_close(ctrl_conn);
        ctrl_conn = NULL;
        return -1;
    }
    if (wpa_ctrl_attach(monitor_conn) != 0) {
        wpa_ctrl_close(monitor_conn);
        wpa_ctrl_close(ctrl_conn);
        ctrl_conn = monitor_conn = NULL;
        return -1;
    }
    return 0;
}

int wifi_send_command(struct wpa_ctrl *ctrl, const char *cmd, char *reply, size_t *reply_len)
{
  static const char *zero="";
    int ret;
    char text[256];

    if (ctrl_conn == NULL) {
      LOGV("wifi: %s(): Not connected to wpa_supplicant - \"%s\" command dropped.\n", __FUNCTION__ , cmd);
        return -1;
    }

    ret = wpa_ctrl_request(ctrl, cmd, strlen(cmd), reply, reply_len, NULL);
    snprintf( text , 254 , "%s" , reply );
    text[((*reply_len<256)?*reply_len:255)]='\0';

    if (ret == -2) {
      LOGE("wifi: %s(): '%s' command timed out.", __FUNCTION__ , cmd);
        return -2;
    }

    else if (ret < 0 || strncmp(reply, "FAIL", 4 ) == 0 ) {
      LOGE( "wifi: %s(): function     command=%s (reply=FAIL || ret<0) reply=%s length=%d ret=%d return -1;",
	    __FUNCTION__ , cmd, text, *reply_len , ret );
      return -1;
    }

    if (strncmp(cmd, "PING", 4) == 0) {
        reply[*reply_len] = '\0';
    }

    return 0;
}

int wifi_wait_for_event(char *buf, size_t buflen)
{
    size_t nread = buflen - 1;
    int fd;
    fd_set rfds;
    int result;
    struct timeval tval;
    struct timeval *tptr;

    if (monitor_conn == NULL) {
      LOGE("wifi: %s(): Connection closed\n" , __FUNCTION__ );
        strncpy(buf, WPA_EVENT_TERMINATING " - connection closed", buflen-1);
        buf[buflen-1] = '\0';
        return strlen(buf);
    }

    result = wpa_ctrl_recv(monitor_conn, buf, &nread);
    if (result < 0) {
      LOGE("wifi: %s(): wpa_ctrl_recv failed: %s\n", __FUNCTION__ , strerror(errno));
        strncpy(buf, WPA_EVENT_TERMINATING " - recv error", buflen-1);
        buf[buflen-1] = '\0';
        return strlen(buf);
    }
    buf[nread] = '\0';
    /* LOGD("wait_for_event: result=%d nread=%d string=\"%s\"\n", result, nread, buf); */
    /* Check for EOF on the socket */
    if (result == 0 && nread == 0) {
        /* Fabricate an event to pass up */
      LOGD("wifi: %s(): Received EOF on supplicant socket" , __FUNCTION__ );
        strncpy(buf, WPA_EVENT_TERMINATING " - signal 0 received", buflen-1);
        buf[buflen-1] = '\0';
        return strlen(buf);
    }
    /*
     * Events strings are in the format
     *
     *     <N>CTRL-EVENT-XXX 
     *
     * where N is the message level in numerical form (0=VERBOSE, 1=DEBUG,
     * etc.) and XXX is the event name. The level information is not useful
     * to us, so strip it off.
     */
    if (buf[0] == '<') {
        char *match = strchr(buf, '>');
        if (match != NULL) {
            nread -= (match+1-buf);
            memmove(buf, match+1, nread+1);
        }
    }
    return nread;
}

void wifi_close_supplicant_connection()
{
    if (ctrl_conn != NULL) {
        wpa_ctrl_close(ctrl_conn);
        ctrl_conn = NULL;
    }
    if (monitor_conn != NULL) {
        wpa_ctrl_close(monitor_conn);
        monitor_conn = NULL;
    }
}

int wifi_command(const char *command, char *reply, size_t *reply_len)
{
    return wifi_send_command(ctrl_conn, command, reply, reply_len);
}

