/*
 * wireless_bss.c
 * This file is part of Network-inador
 *
 * Copyright (C) 2020 - Félix Arreola Rodríguez
 *
 * Network-inador 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.
 *
 * Network-inador 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Network-inador; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <stdint.h>

#include <netlink/genl/ctrl.h>
#include <netlink/socket.h>
#include <netlink/msg.h>
#include <netlink/genl/genl.h>

#include <linux/nl80211.h>

#include "network-inador-private.h"
#include "interfaces.h"
#include "netlink-events.h"
#include "wireless_if.h"
#include "wireless_bss.h"

#define DEFAULT_BSS_EXPIRATION_SCAN_COUNT 2

int wireless_bss_finish_scan (struct nl_msg *msg, void *arg) {
	#if 0
	FList *g;
	WirelessBSS *bss;
	printf ("Scan finish handler\n");
	
	g = 
	if (bss->last_update_idx < wpa_s->bss_update_idx)
			bss->scan_miss_count++;
	#endif
}

// Based on NetworkManager/src/platform/wifi/wifi-utils-nl80211.c
static void _wireless_bss_find_ssid (uint8_t *ies, uint32_t ies_len, uint8_t **ssid, uint32_t *ssid_len) {
#define WLAN_EID_SSID 0
	*ssid = NULL;
	*ssid_len = 0;

	while (ies_len > 2 && ies[0] != WLAN_EID_SSID) {
		ies_len -= ies[1] + 2;
		ies += ies[1] + 2;
	}
	if (ies_len < 2) return;
	if (ies_len < (uint32_t)(2 + ies[1])) return;

	*ssid_len = ies[1];
	*ssid = ies + 2;
}

static WirelessBSS * _wireless_bss_get (Interface *iface, const uint8_t *bssid, const uint8_t *ssid, int ssid_len) {
	FList *g;
	WirelessBSS *bss;
	if (iface->is_wireless == 0) return NULL;
	
	for (g = iface->wireless->aps; g != NULL; g = g->next) {
		bss = (WirelessBSS *) g->data;
		
		if (memcmp (bss->bssid, bssid, ETHER_ADDR_LEN) == 0 &&
		    bss->ssid_len == ssid_len &&
		    memcmp (bss->ssid, ssid, ssid_len) == 0) {
			return bss;
		}
	}
	
	return NULL;
}

static WirelessBSS * _wireless_bss_add_bss (Interface *iface, WirelessBSS *bss) {
	WirelessBSS *new;
	
	new = (WirelessBSS *) malloc (sizeof (WirelessBSS));
	
	if (new == NULL) return NULL;
	
	memcpy (new, bss, sizeof (WirelessBSS));
	
	new->last_update_idx = iface->wireless->bss_update_idx;
	new->scan_miss_count = 0;
	
	iface->wireless->aps = f_list_append (iface->wireless->aps, new);
}

static void _wireless_bss_update_bss (Interface *iface, WirelessBSS *bss, WirelessBSS *updated) {
	bss->last_update_idx = iface->wireless->bss_update_idx;
	bss->scan_miss_count = 0;
	
	/* Actualizar este bss con los nuevos datos */
	bss->freq = updated->freq;
}

int wireless_bss_parse_station_scan (struct nl_msg *msg, void *arg) {
	NetworkInadorHandle *handle = (NetworkInadorHandle *) arg;
	struct nlmsgerr *l_err;
	struct nlmsghdr *reply;
	struct genlmsghdr *gnlh;
	int remaining, remaining2;
	struct nlattr *attr, *bss_attr;
	Interface *iface = NULL;
	
	WirelessBSS bss, *search_bss;
	
	reply = nlmsg_hdr (msg);
	
	if (reply->nlmsg_type == NLMSG_ERROR) {
		l_err = nlmsg_data (reply);
		
		return NL_SKIP;
	}
	
	printf ("Estación recibida por escaneo\n");
	gnlh = nlmsg_data (reply);
	memset (&bss, 0, sizeof (bss));
	
	nlmsg_for_each_attr(attr, reply, sizeof (struct genlmsghdr), remaining) {
		//printf ("Estacion nueva. Attr = %i\n", nla_type (attr));
		if (nla_type (attr) == NL80211_ATTR_IFINDEX) {
			iface = _interfaces_locate_by_index (handle->interfaces, nla_get_u32 (attr));
			//printf ("Estación nueva en la interfaz %i\n", nla_get_u32 (attr));
		} else if (nla_type (attr) == NL80211_ATTR_BSS) {
			//printf ("Información BSS:\n");
			
			remaining2 = nla_len (attr);
			bss_attr = nla_data (attr);
			nla_for_each_nested(bss_attr, attr, remaining2) {
				printf ("-> BSS: %i\n", nla_type (bss_attr));
				if (nla_type (bss_attr) == NL80211_BSS_BSSID) {
					char *mac = nla_data (bss_attr);
					printf ("MAC access point: %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
					memcpy (bss.bssid, nla_data (bss_attr), ETHER_ADDR_LEN);
				} else if (nla_type (bss_attr) == NL80211_BSS_FREQUENCY) {
					printf ("Freq: %i\n", nla_get_u32 (bss_attr));
					bss.freq = nla_get_u32 (bss_attr);
				} else if (nla_type (bss_attr) == NL80211_BSS_INFORMATION_ELEMENTS) {
					char *ssid;
					int ssid_len;
					_wireless_bss_find_ssid (nla_data (bss_attr), nla_len (bss_attr), (uint8_t **) &ssid, &ssid_len);
					
					if (ssid != NULL) {
						printf ("Essid: «%.*s»\n", ssid_len, ssid);
						memcpy (bss.ssid, ssid, ssid_len);
					}
				}
			}
		}
	}
	
	if (iface == NULL) return NL_SKIP;
	
	search_bss = _wireless_bss_get (iface, bss.bssid, bss.ssid, bss.ssid_len);
	
	if (search_bss == NULL) {
		/* Agregar como nuevo */
		search_bss = _wireless_bss_add_bss (iface, &bss);
	} else {
		/* Actualizar este bss */
		_wireless_bss_update_bss (iface, search_bss, &bss);
	}
	
	return NL_SKIP;
}