/*
  WiFi.cpp - Library for Arduino Wifi shield.
  Copyright (c) 2011-2014 Arduino.  All right reserved.

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

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifdef ARDUINO_ARCH_AVR
#include <avr/version.h>
#if (__AVR_LIBC_MAJOR__ < 2)
#define WIFI_101_NO_TIME_H
#endif
#endif

#ifndef WIFI_101_NO_TIME_H
#include <time.h>
#endif

#if !defined(_TIME_H_) && !defined(TIME_H)
// another library overrided the time.h header
#define WIFI_101_NO_TIME_H
#endif

#include "utility/WiFiSocket.h"

#include "WiFi101.h"

extern "C" {
  #include "bsp/include/nm_bsp.h"
  #include "bsp/include/nm_bsp_arduino.h"
  #include "driver/include/m2m_periph.h"
  #include "driver/include/m2m_ssl.h"
  #include "driver/include/m2m_wifi.h"
}

static void wifi_cb(uint8_t u8MsgType, void *pvMsg)
{
	WiFi.handleEvent(u8MsgType, pvMsg);
}

void WiFiClass::handleEvent(uint8_t u8MsgType, void *pvMsg)
{
	switch (u8MsgType) {
		case M2M_WIFI_RESP_DEFAULT_CONNECT:
		{
			tstrM2MDefaultConnResp *pstrDefaultConnResp = (tstrM2MDefaultConnResp *)pvMsg;
			if (pstrDefaultConnResp->s8ErrorCode) {
				_status = WL_DISCONNECTED;
			}
		}
		break;

		case M2M_WIFI_RESP_CON_STATE_CHANGED:
		{
			tstrM2mWifiStateChanged *pstrWifiState = (tstrM2mWifiStateChanged *)pvMsg;
			if (pstrWifiState->u8CurrState == M2M_WIFI_CONNECTED) {
				//SERIAL_PORT_MONITOR.println("wifi_cb: M2M_WIFI_RESP_CON_STATE_CHANGED: CONNECTED");
				if (_mode == WL_STA_MODE && !_dhcp) {
					_status = WL_CONNECTED;

#ifdef CONF_PERIPH
					// WiFi led ON.
					m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 0);
#endif
				} else if (_mode == WL_AP_MODE) {
					_status = WL_AP_CONNECTED;
				}
			} else if (pstrWifiState->u8CurrState == M2M_WIFI_DISCONNECTED) {
				//SERIAL_PORT_MONITOR.println("wifi_cb: M2M_WIFI_RESP_CON_STATE_CHANGED: DISCONNECTED");
				if (_mode == WL_STA_MODE) {
					_status = WL_DISCONNECTED;
					if (_dhcp) {
						_localip = 0;
						_submask = 0;
						_gateway = 0;
					}
					// Close sockets to clean state
					// Clients will need to reconnect once the physical link will be re-established
					for (int i = 0; i < MAX_SOCKET; i++) {
						WiFiSocket.close(i);
					}
				} else if (_mode == WL_AP_MODE) {
					_status = WL_AP_LISTENING;
				}
#ifdef CONF_PERIPH
				// WiFi led OFF (rev A then rev B).
				m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 1);
				m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 1);
#endif
			}
		}
		break;

		case M2M_WIFI_REQ_DHCP_CONF:
		{
			if (_mode == WL_STA_MODE) {
				tstrM2MIPConfig *pstrIPCfg = (tstrM2MIPConfig *)pvMsg;
				_localip = pstrIPCfg->u32StaticIP;
				_submask = pstrIPCfg->u32SubnetMask;
				_gateway = pstrIPCfg->u32Gateway;
				
				_status = WL_CONNECTED;

#ifdef CONF_PERIPH
				// WiFi led ON (rev A then rev B).
				m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 0);
				m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 0);
#endif
			}
			/*uint8_t *pu8IPAddress = (uint8_t *)pvMsg;
			SERIAL_PORT_MONITOR.print("wifi_cb: M2M_WIFI_REQ_DHCP_CONF: IP is ");
			SERIAL_PORT_MONITOR.print(pu8IPAddress[0], 10);
			SERIAL_PORT_MONITOR.print(".");
			SERIAL_PORT_MONITOR.print(pu8IPAddress[1], 10);
			SERIAL_PORT_MONITOR.print(".");
			SERIAL_PORT_MONITOR.print(pu8IPAddress[2], 10);
			SERIAL_PORT_MONITOR.print(".");
			SERIAL_PORT_MONITOR.print(pu8IPAddress[3], 10);
			SERIAL_PORT_MONITOR.println("");*/
		}
		break;

		case M2M_WIFI_RESP_CURRENT_RSSI:
		{
			_resolve = *((int8_t *)pvMsg);
		}
		break;

		case M2M_WIFI_RESP_PROVISION_INFO:
		{
			tstrM2MProvisionInfo *pstrProvInfo = (tstrM2MProvisionInfo *)pvMsg;
			//SERIAL_PORT_MONITOR.println("wifi_cb: M2M_WIFI_RESP_PROVISION_INFO");

			if (pstrProvInfo->u8Status == M2M_SUCCESS) {
				memset(_ssid, 0, M2M_MAX_SSID_LEN);
				memcpy(_ssid, (char *)pstrProvInfo->au8SSID, strlen((char *)pstrProvInfo->au8SSID));
				_mode = WL_STA_MODE;
				_localip = 0;
				_submask = 0;
				_gateway = 0;
				m2m_wifi_connect((char *)pstrProvInfo->au8SSID, strlen((char *)pstrProvInfo->au8SSID),
						pstrProvInfo->u8SecType, pstrProvInfo->au8Password, M2M_WIFI_CH_ALL);
			} else {
				_status = WL_PROVISIONING_FAILED;
				//SERIAL_PORT_MONITOR.println("wifi_cb: Provision failed.\r\n");
				beginProvision();
			}
		}
		break;

		case M2M_WIFI_RESP_SCAN_DONE:
		{
			tstrM2mScanDone *pstrInfo = (tstrM2mScanDone *)pvMsg;
			if (pstrInfo->u8NumofCh >= 1) {
				_status = WL_SCAN_COMPLETED;
			}
		}
		break;

		case M2M_WIFI_RESP_SCAN_RESULT:
		{
			tstrM2mWifiscanResult *pstrScanResult = (tstrM2mWifiscanResult *)pvMsg;
			uint16_t scan_ssid_len = strlen((const char *)pstrScanResult->au8SSID);
			memset(_scan_ssid, 0, M2M_MAX_SSID_LEN);
			if (scan_ssid_len) {
				memcpy(_scan_ssid, (const char *)pstrScanResult->au8SSID, scan_ssid_len);
			}
			if (_remoteMacAddress) {
				// reverse copy the remote MAC
				for(int i = 0; i < 6; i++) {
					_remoteMacAddress[i] = pstrScanResult->au8BSSID[5-i];
				}
			}
			_resolve = pstrScanResult->s8rssi;
			_scan_auth = pstrScanResult->u8AuthType;
			_scan_channel = pstrScanResult->u8ch;
			_status = WL_SCAN_COMPLETED;
		}
		break;

		case M2M_WIFI_RESP_CONN_INFO:
		{
			tstrM2MConnInfo	*pstrConnInfo = (tstrM2MConnInfo*)pvMsg;

			if (_remoteMacAddress) {
				// reverse copy the remote MAC
				for(int i = 0; i < 6; i++) {
					_remoteMacAddress[i] = pstrConnInfo->au8MACAddress[5-i];
				}
				_remoteMacAddress = 0;
			}

			strcpy((char *)_ssid, pstrConnInfo->acSSID);
		}
		break;

		case M2M_WIFI_RESP_GET_SYS_TIME:
		{
			if (_resolve != 0) {
				memcpy((tstrSystemTime *)_resolve, pvMsg, sizeof(tstrSystemTime));

				_resolve = 0;
			}
		}
		break;

		default:
		break;
	}
}

static void resolve_cb(uint8_t * hostName, uint32_t hostIp)
{
	WiFi.handleResolve(hostName, hostIp);
}

void WiFiClass::handleResolve(uint8_t * /*hostName*/, uint32_t hostIp)
{
	_resolve = hostIp;
}

static void socket_cb(SOCKET sock, uint8 u8Msg, void *pvMsg)
{
	WiFiSocket.eventCallback(sock, u8Msg, pvMsg);
}

void ping_cb(uint32 u32IPAddr, uint32 u32RTT, uint8 u8ErrorCode)
{
	WiFi.handlePingResponse(u32IPAddr, u32RTT, u8ErrorCode);
}

void WiFiClass::handlePingResponse(uint32 u32IPAddr, uint32 u32RTT, uint8 u8ErrorCode)
{
	if (PING_ERR_SUCCESS == u8ErrorCode) {
		// Ensure this ICMP reply comes from requested IP address
		if (_resolve == u32IPAddr) {
			_resolve = (uint32_t)u32RTT;
		} else {
			// Another network device replied to the our ICMP request
			_resolve = (uint32_t)WL_PING_DEST_UNREACHABLE;
		}
	} else if (PING_ERR_DEST_UNREACH == u8ErrorCode) {
		_resolve = (uint32_t)WL_PING_DEST_UNREACHABLE;
	} else if (PING_ERR_TIMEOUT == u8ErrorCode) {
		_resolve = (uint32_t)WL_PING_TIMEOUT;
	} else {
		_resolve = (uint32_t)WL_PING_ERROR;
	}
}

WiFiClass::WiFiClass() :
  _init(0),
  _mode(WL_RESET_MODE),
  _status(WL_NO_SHIELD),
  _timeout(60000)
{
}

void WiFiClass::setPins(int8_t cs, int8_t irq, int8_t rst, int8_t en)
{
	gi8Winc1501CsPin = cs;
	gi8Winc1501IntnPin = irq;
	gi8Winc1501ResetPin = rst;
	gi8Winc1501ChipEnPin = en;
}

int WiFiClass::init()
{
	tstrWifiInitParam param;
	int8_t ret;

	// Initialize the WiFi BSP:
	nm_bsp_init();

	// Initialize WiFi module and register status callback:
	param.pfAppWifiCb = wifi_cb;
	ret = m2m_wifi_init(&param);
	if (M2M_SUCCESS != ret && M2M_ERR_FW_VER_MISMATCH != ret) {
#ifdef CONF_PERIPH
		if (ret != M2M_ERR_INVALID) {
			// Error led ON (rev A then rev B).
			m2m_periph_gpio_set_val(M2M_PERIPH_GPIO18, 0);
			m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO6, 1);
		}
#endif
		return ret;
	}

	// Initialize socket API and register socket callback:
	socketInit();
	registerSocketCallback(socket_cb, resolve_cb);
	_init = 1;
	_status = WL_IDLE_STATUS;
	_localip = 0;
	_submask = 0;
	_gateway = 0;
	_dhcp = 1;
	_resolve = 0;
	_remoteMacAddress = 0;

	extern uint32 nmdrv_firm_ver;

	if (nmdrv_firm_ver >= M2M_MAKE_VERSION(19, 5, 0)) {
		// enable AES-128 and AES-256 Ciphers, if firmware is 19.5.0 or higher
		m2m_ssl_set_active_ciphersuites(SSL_NON_ECC_CIPHERS_AES_128 | SSL_NON_ECC_CIPHERS_AES_256);
	}

#ifdef CONF_PERIPH
	// Initialize IO expander LED control (rev A then rev B)..
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO18, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO15, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO16, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO18, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO6, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO4, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO5, 1);
	m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO6, 1);
#endif

	return ret;
}

extern "C" {
	sint8 nm_get_firmware_info(tstrM2mRev* M2mRev);
}

char* WiFiClass::firmwareVersion()
{
	tstrM2mRev rev;
	
	if (!_init) {
		init();
	}
	nm_get_firmware_info(&rev);
	memset(_version, 0, 9);
	if (rev.u8FirmwareMajor != rev.u8DriverMajor && rev.u8FirmwareMinor != rev.u8DriverMinor) {
		sprintf(_version, "-Err-");
	}
	else {
		sprintf(_version, "%d.%d.%d", rev.u8FirmwareMajor, rev.u8FirmwareMinor, rev.u8FirmwarePatch);
	}
	return _version;
}

uint8_t WiFiClass::begin()
{
	if (!_init) {
		init();
	}
	
	// Connect to router:
	if (_dhcp) {
		_localip = 0;
		_submask = 0;
		_gateway = 0;
	}
	if (m2m_wifi_default_connect() < 0) {
		_status = WL_CONNECT_FAILED;
		return _status;
	}
	_status = WL_IDLE_STATUS;
	_mode = WL_STA_MODE;

	// Wait for connection or timeout:
	for (unsigned long start = millis(); millis() - start < _timeout;)
	{
		m2m_wifi_handle_events(NULL);
		if ((_status & WL_CONNECTED) || (_status & WL_DISCONNECTED)) {
			break;
		}
	}

	memset(_ssid, 0, M2M_MAX_SSID_LEN);

	if (!(_status & WL_CONNECTED)) {
		_mode = WL_RESET_MODE;
	} else {
		m2m_wifi_get_connection_info();

		m2m_wifi_handle_events(NULL);
	}

	return _status;
}

uint8_t WiFiClass::begin(const char *ssid)
{
	return startConnect(ssid, M2M_WIFI_SEC_OPEN, (void *)0);
}

uint8_t WiFiClass::begin(const char *ssid, uint8_t key_idx, const char* key)
{
	tstrM2mWifiWepParams wep_params;

	memset(&wep_params, 0, sizeof(tstrM2mWifiWepParams));
	wep_params.u8KeyIndx = key_idx;
	wep_params.u8KeySz = strlen(key);
	strcpy((char *)&wep_params.au8WepKey[0], key);
	return startConnect(ssid, M2M_WIFI_SEC_WEP, &wep_params);
}

uint8_t WiFiClass::begin(const char *ssid, const char *key)
{
	return startConnect(ssid, M2M_WIFI_SEC_WPA_PSK, key);
}

uint8_t WiFiClass::startConnect(const char *ssid, uint8_t u8SecType, const void *pvAuthInfo)
{
	if (!_init) {
		init();
	}
	
	// Connect to router:
	if (_dhcp) {
		_localip = 0;
		_submask = 0;
		_gateway = 0;
	}
	if (m2m_wifi_connect((char*)ssid, strlen(ssid), u8SecType, (void*)pvAuthInfo, M2M_WIFI_CH_ALL) < 0) {
		_status = WL_CONNECT_FAILED;
		return _status;
	}
	_status = WL_IDLE_STATUS;
	_mode = WL_STA_MODE;

	// Wait for connection or timeout:
	for (unsigned long start = millis(); millis() - start < _timeout;)
	{
		m2m_wifi_handle_events(NULL);
		if ((_status & WL_CONNECTED) || (_status & WL_DISCONNECTED)) {
			break;
		}
	}
	if (!(_status & WL_CONNECTED)) {
		_mode = WL_RESET_MODE;
	}

	memset(_ssid, 0, M2M_MAX_SSID_LEN);
	memcpy(_ssid, ssid, strlen(ssid));
	return _status;
}

uint8_t WiFiClass::beginAP(const char *ssid)
{
	return beginAP(ssid, 1);
}

uint8_t WiFiClass::beginAP(const char *ssid, uint8_t channel)
{
	return startAP(ssid, M2M_WIFI_SEC_OPEN, NULL, channel);
}

uint8_t WiFiClass::beginAP(const char *ssid, uint8_t key_idx, const char* key)
{
	return beginAP(ssid, key_idx, key, 1);
}

uint8_t WiFiClass::beginAP(const char *ssid, uint8_t key_idx, const char* key, uint8_t channel)
{
	tstrM2mWifiWepParams wep_params;

	if (key_idx == 0) {
		key_idx = 1; // 1 is the minimum key index
	}

	memset(&wep_params, 0, sizeof(tstrM2mWifiWepParams));
	wep_params.u8KeyIndx = key_idx;
	wep_params.u8KeySz = strlen(key);
	strcpy((char *)&wep_params.au8WepKey[0], key);

	return startAP(ssid, M2M_WIFI_SEC_WEP, &wep_params, channel);
}

uint8_t WiFiClass::beginAP(const char *ssid, const char* key)
{
	return beginAP(ssid, key, 1);
}

uint8_t WiFiClass::beginAP(const char *ssid, const char* key, uint8_t channel)
{
	return startAP(ssid, M2M_WIFI_SEC_WPA_PSK, key, channel);
}

uint8_t WiFiClass::startAP(const char *ssid, uint8_t u8SecType, const void *pvAuthInfo, uint8_t channel)
{
	tstrM2MAPConfig strM2MAPConfig;

	if (!_init) {
		init();
	}

	if (channel == 0) {
		channel = 1; // channel 1 is the minium channel
	}

	// Enter Access Point mode:
	memset(&strM2MAPConfig, 0x00, sizeof(tstrM2MAPConfig));
	strcpy((char *)&strM2MAPConfig.au8SSID, ssid);
	strM2MAPConfig.u8ListenChannel = channel;
	strM2MAPConfig.u8SecType = u8SecType;
	if (_localip == 0) { 
		strM2MAPConfig.au8DHCPServerIP[0] = 192;
		strM2MAPConfig.au8DHCPServerIP[1] = 168;
		strM2MAPConfig.au8DHCPServerIP[2] = 1;
		strM2MAPConfig.au8DHCPServerIP[3] = 1;
	} else {
		memcpy(strM2MAPConfig.au8DHCPServerIP, &_localip, sizeof(_localip));
		if (strM2MAPConfig.au8DHCPServerIP[3] == 100) {
			// limitation of WINC1500 firmware, IP address of client is always x.x.x.100
			_status = WL_AP_FAILED;
			return _status;
		}
	}

	if (u8SecType == M2M_WIFI_SEC_WEP) {
		tstrM2mWifiWepParams* wep_params = (tstrM2mWifiWepParams*)pvAuthInfo;

		strM2MAPConfig.u8KeyIndx = wep_params->u8KeyIndx;
		strM2MAPConfig.u8KeySz = wep_params->u8KeySz;
		strcpy((char*)strM2MAPConfig.au8WepKey, (char *)wep_params->au8WepKey);
	}

	if (u8SecType == M2M_WIFI_SEC_WPA_PSK) {
		strM2MAPConfig.u8KeySz = strlen((char*)pvAuthInfo);
		strcpy((char*)strM2MAPConfig.au8Key, (char *)pvAuthInfo);
	}

	if (m2m_wifi_enable_ap(&strM2MAPConfig) < 0) {
		_status = WL_AP_FAILED;
		return _status;
	}
	_status = WL_AP_LISTENING;
	_mode = WL_AP_MODE;

	memset(_ssid, 0, M2M_MAX_SSID_LEN);
	memcpy(_ssid, ssid, strlen(ssid));
	m2m_memcpy((uint8 *)&_localip, (uint8 *)&strM2MAPConfig.au8DHCPServerIP[0], 4);
	_submask = 0x00FFFFFF;
	_gateway = _localip;

#ifdef CONF_PERIPH
	// WiFi led ON (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 0);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 0);
#endif

	return _status;
}

uint8_t WiFiClass::beginProvision()
{
	return beginProvision(1);
}

uint8_t WiFiClass::beginProvision(uint8_t channel)
{
	// try to connect using begin
	if (begin() != WL_CONNECTED) {
		// failed, enter provisioning mode

		uint8_t mac[6];
		char provSsid[13];

		// get MAC address for provisioning SSID
		macAddress(mac);
		sprintf(provSsid, "wifi101-%.2X%.2X", mac[1], mac[0]);

		// start provisioning mode
		startProvision(provSsid, "wifi101", channel);
	}

	return status();
}

uint8_t WiFiClass::startProvision(const char *ssid, const char *url, uint8_t channel)
{
	tstrM2MAPConfig strM2MAPConfig;

	if (!_init) {
		init();
	}

	// Enter Provision mode:
	memset(&strM2MAPConfig, 0x00, sizeof(tstrM2MAPConfig));
	strcpy((char *)&strM2MAPConfig.au8SSID, ssid);
	strM2MAPConfig.u8ListenChannel = channel;
	strM2MAPConfig.u8SecType = M2M_WIFI_SEC_OPEN;
	strM2MAPConfig.u8SsidHide = SSID_MODE_VISIBLE;
	strM2MAPConfig.au8DHCPServerIP[0] = 192;
	strM2MAPConfig.au8DHCPServerIP[1] = 168;
	strM2MAPConfig.au8DHCPServerIP[2] = 1;
	strM2MAPConfig.au8DHCPServerIP[3] = 1;

	if (m2m_wifi_start_provision_mode((tstrM2MAPConfig *)&strM2MAPConfig, (char*)url, 1) < 0) {
		_status = WL_PROVISIONING_FAILED;
		return _status;
	}
	_status = WL_PROVISIONING;
	_mode = WL_PROV_MODE;

	memset(_ssid, 0, M2M_MAX_SSID_LEN);
	memcpy(_ssid, ssid, strlen(ssid));
	m2m_memcpy((uint8 *)&_localip, (uint8 *)&strM2MAPConfig.au8DHCPServerIP[0], 4);
	_submask = 0x00FFFFFF;
	_gateway = _localip;

#ifdef CONF_PERIPH
	// WiFi led ON (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 0);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 0);
#endif

	return _status;
}

uint32_t WiFiClass::provisioned()
{
	m2m_wifi_handle_events(NULL);

	if (_mode == WL_STA_MODE) {
		return 1;
	}
	else {
		return 0;
	}
}

void WiFiClass::config(IPAddress local_ip)
{
	config(local_ip, (uint32_t)0);
}

void WiFiClass::config(IPAddress local_ip, IPAddress dns_server)
{
	config(local_ip, dns_server, (uint32_t)0);
}

void WiFiClass::config(IPAddress local_ip, IPAddress dns_server, IPAddress gateway)
{
	config(local_ip, dns_server, gateway, (uint32_t)0);
}

void WiFiClass::config(IPAddress local_ip, IPAddress dns_server, IPAddress gateway, IPAddress subnet)
{
	tstrM2MIPConfig conf;

	if (!_init) {
		init();
	}

	conf.u32DNS = (uint32_t)dns_server;
	conf.u32Gateway = (uint32_t)gateway;
	conf.u32StaticIP = (uint32_t)local_ip;
	conf.u32SubnetMask = (uint32_t)subnet;
	_dhcp = 0;
	m2m_wifi_enable_dhcp(0); // disable DHCP
	m2m_wifi_set_static_ip(&conf);
	_localip = conf.u32StaticIP;
	_submask = conf.u32SubnetMask;
	_gateway = conf.u32Gateway;
}

void WiFiClass::hostname(const char* name)
{
	if (!_init) {
		init();
	}

	m2m_wifi_set_device_name((uint8 *)name, strlen(name));
}

void WiFiClass::disconnect()
{
	// Close sockets to clean state
	for (int i = 0; i < MAX_SOCKET; i++) {
		WiFiSocket.close(i);
	}

	m2m_wifi_disconnect();

#ifdef CONF_PERIPH
	// WiFi led OFF (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 1);
#endif
}

void WiFiClass::end()
{
	// Close sockets to clean state
	for (int i = 0; i < MAX_SOCKET; i++) {
		WiFiSocket.close(i);
	}

	if (_mode == WL_AP_MODE) {
		m2m_wifi_disable_ap();
	} else {
		if (_mode == WL_PROV_MODE) {
			m2m_wifi_stop_provision_mode();
		}

		m2m_wifi_disconnect();
	}

#ifdef CONF_PERIPH
	// WiFi led OFF (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO15, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4, 1);
#endif

	socketDeinit();

	m2m_wifi_deinit(NULL);

	nm_bsp_deinit();

	_mode = WL_RESET_MODE;
	_status = WL_NO_SHIELD;
	_init = 0;
}

uint8_t *WiFiClass::macAddress(uint8_t *mac)
{
	m2m_wifi_get_mac_address(mac);
	byte tmpMac[6], i;
	
	m2m_wifi_get_mac_address(tmpMac);
	
	for(i = 0; i < 6; i++)
		mac[i] = tmpMac[5-i];
		
	return mac;
}

uint32_t WiFiClass::localIP()
{
	return _localip;
}

uint32_t WiFiClass::subnetMask()
{
	return _submask;
}

uint32_t WiFiClass::gatewayIP()
{
	return _gateway;
}

char* WiFiClass::SSID()
{
	if (_status == WL_CONNECTED || _status == WL_AP_LISTENING || _status == WL_AP_CONNECTED) {
		return _ssid;
	}
	else {
		return 0;
	}
}

uint8_t* WiFiClass::BSSID(uint8_t* bssid)
{
	if (_mode == WL_AP_MODE) {
		return macAddress(bssid);
	} else {
		return remoteMacAddress(bssid);
	}
}

uint8_t* WiFiClass::APClientMacAddress(uint8_t* mac)
{
	if (_mode == WL_AP_MODE) {
		return remoteMacAddress(mac);
	} else {
		memset(mac, 0, 6);
		return mac;
	}
}

uint8_t* WiFiClass::remoteMacAddress(uint8_t* remoteMacAddress)
{
	_remoteMacAddress = remoteMacAddress;
	memset(remoteMacAddress, 0, 6);

	m2m_wifi_get_connection_info();

	// Wait for connection or timeout:
	unsigned long start = millis();
	while (_remoteMacAddress != 0 && millis() - start < 1000) {
		m2m_wifi_handle_events(NULL);
	}

	_remoteMacAddress = 0;
	return remoteMacAddress;
}

int32_t WiFiClass::RSSI()
{
	if (_mode == WL_RESET_MODE) {
		return -100;
	}

	// Clear pending events:
	m2m_wifi_handle_events(NULL);

	// Send RSSI request:
	_resolve = 0;
	if (m2m_wifi_req_curr_rssi() < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	unsigned long start = millis();
	while (_resolve == 0 && millis() - start < 1000) {
		m2m_wifi_handle_events(NULL);
	}

	int32_t rssi = _resolve;

	_resolve = 0;

	return rssi;
}

int8_t WiFiClass::scanNetworks()
{
	wl_status_t tmp = _status;

	if (!_init) {
		init();
	}

	// Start scan:
	if (m2m_wifi_request_scan(M2M_WIFI_CH_ALL) < 0) {
		return 0;
	}

	// Wait for scan result or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 5000) {
		m2m_wifi_handle_events(NULL);
	}
	_status = tmp;
	return m2m_wifi_get_num_ap_found();
}

char* WiFiClass::SSID(uint8_t pos)
{
	wl_status_t tmp = _status;

	// Get scan SSID result:
	memset(_scan_ssid, 0, M2M_MAX_SSID_LEN);
	if (m2m_wifi_req_scan_result(pos) < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 2000) {
		m2m_wifi_handle_events(NULL);
	}

	_status = tmp;
	_resolve = 0;

	return _scan_ssid;
}

int32_t WiFiClass::RSSI(uint8_t pos)
{
	wl_status_t tmp = _status;

	// Get scan RSSI result:
	if (m2m_wifi_req_scan_result(pos) < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 2000) {
		m2m_wifi_handle_events(NULL);
	}

	_status = tmp;

	int32_t rssi = _resolve;

	_resolve = 0;

	return rssi;
}

uint8_t WiFiClass::encryptionType()
{ 
	int8_t net = scanNetworks();

	for (uint8_t i = 0; i < net; ++i) {
		SSID(i);
		if (strcmp(_scan_ssid, _ssid) == 0) {
			break;
		}
	}

	return _scan_auth;
}

uint8_t WiFiClass::encryptionType(uint8_t pos)
{
	wl_status_t tmp = _status;

	// Get scan auth result:
	if (m2m_wifi_req_scan_result(pos) < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 2000) {
		m2m_wifi_handle_events(NULL);
	}

	_status = tmp;
	_resolve = 0;

	return _scan_auth;
}

uint8_t* WiFiClass::BSSID(uint8_t pos, uint8_t* bssid)
{
	wl_status_t tmp = _status;

	_remoteMacAddress = bssid;

	// Get scan auth result:
	if (m2m_wifi_req_scan_result(pos) < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 2000) {
		m2m_wifi_handle_events(NULL);
	}

	_status = tmp;
	_resolve = 0;
	_remoteMacAddress = 0;

	return bssid;
}

uint8_t WiFiClass::channel(uint8_t pos)
{
	wl_status_t tmp = _status;

	// Get scan auth result:
	if (m2m_wifi_req_scan_result(pos) < 0) {
		return 0;
	}

	// Wait for connection or timeout:
	_status = WL_IDLE_STATUS;
	unsigned long start = millis();
	while (!(_status & WL_SCAN_COMPLETED) && millis() - start < 2000) {
		m2m_wifi_handle_events(NULL);
	}

	_status = tmp;
	_resolve = 0;

	return _scan_channel;
}

uint8_t WiFiClass::status()
{
	if (!_init) {
		init();
	}

	m2m_wifi_handle_events(NULL);

	return _status;
}

int WiFiClass::hostByName(const char* aHostname, IPAddress& aResult)
{
	
	// check if aHostname is already an ipaddress
	if (aResult.fromString(aHostname)) {
		// if fromString returns true we have an IP address ready 
		return 1;

	} else {
#ifdef CONF_PERIPH
		// Network led ON (rev A then rev B).
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 0);
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 0);
#endif

		// Send DNS request:
		_resolve = 0;
		if (gethostbyname((uint8 *)aHostname) < 0) {
#ifdef CONF_PERIPH
			// Network led OFF (rev A then rev B).
			m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 1);
			m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 1);
#endif
			return 0;
		}

		// Wait for connection or timeout:
		unsigned long start = millis();
		while (_resolve == 0 && millis() - start < 20000) {
			m2m_wifi_handle_events(NULL);
		}

#ifdef CONF_PERIPH
		// Network led OFF (rev A then rev B).
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 1);
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 1);
#endif

		if (_resolve == 0) {
			return 0;
		}

		aResult = _resolve;
		_resolve = 0;
		return 1;
	}
}

void WiFiClass::refresh(void)
{
	// Update state machine:
	m2m_wifi_handle_events(NULL);
}

void WiFiClass::lowPowerMode(void)
{
	m2m_wifi_set_sleep_mode(M2M_PS_H_AUTOMATIC, true);
}

void WiFiClass::maxLowPowerMode(void)
{
	m2m_wifi_set_sleep_mode(M2M_PS_DEEP_AUTOMATIC, true);
}

void WiFiClass::noLowPowerMode(void)
{
	m2m_wifi_set_sleep_mode(M2M_NO_PS, false);
}

int WiFiClass::ping(const char* hostname, uint8_t ttl)
{
	IPAddress ip;

	if (hostByName(hostname, ip) > 0) {
		return ping(ip, ttl);
	} else {
		return WL_PING_UNKNOWN_HOST;
	}
}

int WiFiClass::ping(const String &hostname, uint8_t ttl)
{
	return ping(hostname.c_str(), ttl);
}

int WiFiClass::ping(IPAddress host, uint8_t ttl)
{
#ifdef CONF_PERIPH
	// Network led ON (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 0);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 0);
#endif

	uint32_t dstHost = (uint32_t)host;
	_resolve = dstHost;

	if (m2m_ping_req((uint32_t)host, ttl, &ping_cb) < 0) {
#ifdef CONF_PERIPH
		// Network led OFF (rev A then rev B).
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 1);
		m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 1);
#endif
		//  Error sending ping request
		return WL_PING_ERROR;
	}

	// Wait for success or timeout:
	unsigned long start = millis();
	while (_resolve == dstHost && millis() - start < 5000) {
		m2m_wifi_handle_events(NULL);
	}

#ifdef CONF_PERIPH
	// Network led OFF (rev A then rev B).
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO16, 1);
	m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5, 1);
#endif

	if (_resolve == dstHost) {
		_resolve = 0;
		return WL_PING_TIMEOUT;
	} else {
		int rtt = (int)_resolve;
		_resolve = 0;
		return rtt;
	}
}

uint32_t WiFiClass::getTime()
{
#ifdef WIFI_101_NO_TIME_H
	#warning "No system <time.h> header included, WiFi.getTime() will always return 0"
	return 0;
#else
	tstrSystemTime systemTime;

	_resolve = (uint32_t)&systemTime;

	m2m_wifi_get_sytem_time();

	unsigned long start = millis();
	while (_resolve != 0 && millis() - start < 5000) {
		m2m_wifi_handle_events(NULL);
	}

	time_t t = 0;

	if (_resolve == 0 && systemTime.u16Year > 0) {
		struct tm tm;

		tm.tm_year = systemTime.u16Year - 1900;
		tm.tm_mon = systemTime.u8Month - 1;
		tm.tm_mday = systemTime.u8Day;
		tm.tm_hour = systemTime.u8Hour;
		tm.tm_min = systemTime.u8Minute;
		tm.tm_sec = systemTime.u8Second;
		tm.tm_isdst = -1;

		t = mktime(&tm);
	}

	_resolve = 0;

	return t;
#endif
}

void WiFiClass::setTimeout(unsigned long timeout)
{
	_timeout = timeout;
}

WiFiClass WiFi;
