/*
 * Copyright (c) 2015 Jan Kolarik
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup libieee80211
 * @{
 */

/** @file ieee80211_iface_impl.c
 *
 * IEEE 802.11 default interface functions implementation.
 */

#include <str.h>
#include <errno.h>
#include <ieee80211_private.h>
#include <ieee80211_iface_impl.h>

/** Implementation of fetching scan results.
 *
 * @param fun     Device function.
 * @param results Structure where should be stored scan results.
 *
 * @return EOK if everything went OK,
 *         EREFUSED when device is not ready yet.
 *
 */
errno_t ieee80211_get_scan_results_impl(ddf_fun_t *fun,
    ieee80211_scan_results_t *results, bool now)
{
	nic_t *nic_data = nic_get_from_ddf_fun(fun);
	ieee80211_dev_t *ieee80211_dev = nic_get_specific(nic_data);
	
	if (!ieee80211_is_ready(ieee80211_dev))
		return EREFUSED;
	
	if (now)
		ieee80211_dev->ops->scan(ieee80211_dev);
	
	fibril_mutex_lock(&ieee80211_dev->ap_list.results_mutex);
	
	if (results) {
		ieee80211_scan_result_list_t *result_list =
		    &ieee80211_dev->ap_list;
		
		unsigned int i = 0;
		ieee80211_scan_result_list_foreach(*result_list, result) {
			memcpy(&results->results[i],
			    &result->scan_result,
			    sizeof(ieee80211_scan_result_t));
			i++;
		}
		
		results->length = i;
	}
	
	fibril_mutex_unlock(&ieee80211_dev->ap_list.results_mutex);
	
	return EOK;
}

static uint16_t ieee80211_channel_to_freq(uint8_t channel)
{
	return IEEE80211_CHANNEL_GAP * (channel - 1) + IEEE80211_FIRST_FREQ;
}

/** Working procedure of connect function.
 *
 * @param ieee80211_dev Pointer to IEEE 802.11 device.
 * @param auth_data     Selected AP data we want to connect to.
 *
 * @return EOK if everything OK,
 *         ETIMEOUT when timeout during authenticating.
 *
 */
static errno_t ieee80211_connect_proc(ieee80211_dev_t *ieee80211_dev,
    ieee80211_scan_result_link_t *auth_data, char *password)
{
	ieee80211_dev->bssid_info.res_link = auth_data;
	
	/* Set channel. */
	errno_t rc = ieee80211_dev->ops->set_freq(ieee80211_dev,
	    ieee80211_channel_to_freq(auth_data->scan_result.channel));
	if (rc != EOK)
		return rc;
	
	/* Try to authenticate. */
	ieee80211_authenticate(ieee80211_dev);
	
	fibril_mutex_lock(&ieee80211_dev->gen_mutex);
	rc = fibril_condvar_wait_timeout(&ieee80211_dev->gen_cond,
	    &ieee80211_dev->gen_mutex, AUTH_TIMEOUT);
	fibril_mutex_unlock(&ieee80211_dev->gen_mutex);
	
	if (rc != EOK)
		return rc;
	
	if (ieee80211_get_auth_phase(ieee80211_dev) !=
	    IEEE80211_AUTH_AUTHENTICATED) {
		ieee80211_set_auth_phase(ieee80211_dev,
		    IEEE80211_AUTH_DISCONNECTED);
		return EINVAL;
	}
	
	/* Try to associate. */
	ieee80211_associate(ieee80211_dev, password);
	
	fibril_mutex_lock(&ieee80211_dev->gen_mutex);
	rc = fibril_condvar_wait_timeout(&ieee80211_dev->gen_cond,
	    &ieee80211_dev->gen_mutex, AUTH_TIMEOUT);
	fibril_mutex_unlock(&ieee80211_dev->gen_mutex);
	
	if (rc != EOK)
		return rc;
	
	if (ieee80211_get_auth_phase(ieee80211_dev) !=
	    IEEE80211_AUTH_ASSOCIATED) {
		ieee80211_set_auth_phase(ieee80211_dev,
		    IEEE80211_AUTH_DISCONNECTED);
		return EINVAL;
	}
	
	/* On open network, we are finished. */
	if (auth_data->scan_result.security.type !=
	    IEEE80211_SECURITY_OPEN) {
		/* Otherwise wait for 4-way handshake to complete. */
		
		fibril_mutex_lock(&ieee80211_dev->gen_mutex);
		rc = fibril_condvar_wait_timeout(&ieee80211_dev->gen_cond,
		    &ieee80211_dev->gen_mutex, HANDSHAKE_TIMEOUT);
		fibril_mutex_unlock(&ieee80211_dev->gen_mutex);
		
		if (rc != EOK) {
			ieee80211_deauthenticate(ieee80211_dev);
			return rc;
		}
	}
	
	ieee80211_set_auth_phase(ieee80211_dev, IEEE80211_AUTH_CONNECTED);
	
	return EOK;
}

/** Implementation of connecting to specified SSID.
 *
 * @param fun        Device function.
 * @param ssid_start SSID prefix of access point we want to connect to.
 *
 * @return EOK if everything OK,
 *         ETIMEOUT when timeout during authenticating,
 *         EINVAL when SSID not in scan results list,
 *         EPERM when incorrect password passed,
 *         EREFUSED when device is not ready yet.
 *
 */
errno_t ieee80211_connect_impl(ddf_fun_t *fun, char *ssid_start, char *password)
{
	assert(ssid_start);
	assert(password);
	
	nic_t *nic_data = nic_get_from_ddf_fun(fun);
	ieee80211_dev_t *ieee80211_dev = nic_get_specific(nic_data);
	
	if (!ieee80211_is_ready(ieee80211_dev))
		return EREFUSED;
	
	if (ieee80211_is_connected(ieee80211_dev)) {
		errno_t rc = ieee80211_dev->iface->disconnect(fun);
		if (rc != EOK)
			return rc;
	}
	
	ieee80211_set_connect_request(ieee80211_dev);
	
	errno_t rc = ENOENT;
	fibril_mutex_lock(&ieee80211_dev->scan_mutex);
	
	ieee80211_dev->pending_conn_req = false;
	
	ieee80211_scan_result_list_foreach(ieee80211_dev->ap_list, result) {
		if (!str_lcmp(ssid_start, result->scan_result.ssid,
		    str_size(ssid_start))) {
			rc = ieee80211_connect_proc(ieee80211_dev, result,
			    password);
			break;
		}
	}
	
	fibril_mutex_unlock(&ieee80211_dev->scan_mutex);
	
	return rc;
}

/** Implementation of disconnecting device from network.
 *
 * @param fun Device function.
 *
 * @return EOK if everything OK,
 *         EREFUSED if device is not ready yet.
 *
 */
errno_t ieee80211_disconnect_impl(ddf_fun_t *fun)
{
	nic_t *nic_data = nic_get_from_ddf_fun(fun);
	ieee80211_dev_t *ieee80211_dev = nic_get_specific(nic_data);
	
	if (!ieee80211_is_ready(ieee80211_dev))
		return EREFUSED;
	
	if (!ieee80211_is_connected(ieee80211_dev))
		return EOK;
	
	fibril_mutex_lock(&ieee80211_dev->ap_list.results_mutex);
	errno_t rc = ieee80211_deauthenticate(ieee80211_dev);
	fibril_mutex_unlock(&ieee80211_dev->ap_list.results_mutex);
	
	return rc;
}

/** @}
 */
