/*
 * wavemon - a wireless network monitoring aplication
 *
 * Copyright (c) 2001-2002 Jan Morgenstern <jan@jm-music.de>
 *
 * wavemon 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, or (at your option) any later
 * version.
 *
 * wavemon 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 wavemon; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include "iw_if.h"

#define START_LINE	2	/* where to begin the screen */

/* GLOBALS */
static struct scan_result sr;
static pthread_t scan_thread;
static WINDOW *w_aplst;

/**
 * Sanitize and format single scan entry as a string.
 * @cur: entry to format
 * @buf: buffer to put results into
 * @buflen: length of @buf
 */
static void fmt_scan_entry(struct scan_entry *cur, char buf[], size_t buflen)
{
	size_t len = 0;

	if (cur->bss_signal) {
		float sig_qual, sig_qual_max;

		if (cur->bss_signal_qual) {
			/* BSS_SIGNAL_UNSPEC is scaled 0..100 */
			sig_qual     = cur->bss_signal_qual;
			sig_qual_max = 100;
		} else {
			if (cur->bss_signal < -110)
				sig_qual = 0;
			else if (cur->bss_signal > -40)
				sig_qual = 70;
			else
				sig_qual = cur->bss_signal + 110;
			sig_qual_max = 70;
		}
		len += snprintf(buf + len, buflen - len, "%3.0f%%, %d dBm",
				(1E2 * sig_qual)/ sig_qual_max, cur->bss_signal);
	} else if (cur->bss_signal_qual) {
		len += snprintf(buf + len, buflen - len, "%2d/%d",
				cur->bss_signal_qual, 100);
	} else {
		len += snprintf(buf + len, buflen - len, "? dBm");
	}

	if (cur->chan >= 0)
		len += snprintf(buf + len, buflen - len, ", %s %3d, %d MHz",
				cur->freq < 5e6 ? "ch" : "CH",
				cur->chan, cur->freq);
	else
		len += snprintf(buf + len, buflen - len, ", %g GHz",
				cur->freq / 1e3);

	if (cur->bss_capa & WLAN_CAPABILITY_ESS) {
		if (cur->bss_sta_count || cur->bss_chan_usage > 2) {
			if (cur->bss_sta_count)
				len += snprintf(buf + len, buflen - len, " %u sta", cur->bss_sta_count);
			if (cur->bss_chan_usage > 2) /* 1% is 2.55 */
				len += snprintf(buf + len, buflen - len, "%s %.0f%% chan",
						cur->bss_sta_count? "," : "", (1e2 * cur->bss_chan_usage)/2.55e2);
		} else {
			len += snprintf(buf + len, buflen - len, " ESS");
		}
		if (cur->bss_capa & WLAN_CAPABILITY_RADIO_MEASURE)
			len += snprintf(buf + len, buflen - len, ", Radio Measure");
		if (cur->bss_capa & WLAN_CAPABILITY_SPECTRUM_MGMT)
			len += snprintf(buf + len, buflen - len, ", Spectrum Mgmt");
	} else if (cur->bss_capa & WLAN_CAPABILITY_IBSS) {
		len += snprintf(buf + len, buflen - len, " IBSS");
	}
}

static void display_aplist(WINDOW *w_aplst)
{
	char s[IW_ESSID_MAX_SIZE << 3];
	const char *sort_type[] = {
		[SO_CHAN]	= "Chan",
		[SO_SIGNAL]	= "Sig",
		[SO_MAC]        = "Mac",
		[SO_ESSID]	= "Essid",
		[SO_OPEN]	= "Open",
		[SO_CHAN_SIG]	= "Ch/Sg",
		[SO_OPEN_SIG]	= "Op/Sg"
	};
	int i, col, line = START_LINE;
	struct scan_entry *cur;

	/* Scanning can take several seconds - do not refresh if locked. */
	if (pthread_mutex_trylock(&sr.mutex))
		return;

	if (sr.head || *sr.msg)
		for (i = 1; i <= MAXYLEN; i++)
			mvwclrtoborder(w_aplst, i, 1);

	if (!sr.head)
		waddstr_center(w_aplst, WAV_HEIGHT/2 - 1, sr.msg);

	sort_scan_list(&sr.head);

	/* Truncate overly long access point lists to match screen height. */
	for (cur = sr.head; cur && line < MAXYLEN; line++, cur = cur->next) {
		col = CP_SCAN_NON_AP;

		if (!WLAN_CAPABILITY_IS_STA_BSS(cur->bss_capa) && (cur->bss_capa & WLAN_CAPABILITY_ESS)) {
			col = cur->has_key ? CP_SCAN_CRYPT : CP_SCAN_UNENC;
		}

		wmove(w_aplst, line, 1);
		if (!*cur->essid) {
			sprintf(s, "%-*s ", sr.max_essid_len, "<hidden ESSID>");
			wattron(w_aplst, COLOR_PAIR(col));
			waddstr(w_aplst, s);
		} else if (str_is_ascii(cur->essid)) {
			sprintf(s, "%-*s ", sr.max_essid_len, cur->essid);
			waddstr_b(w_aplst, s);
			wattron(w_aplst, COLOR_PAIR(col));
		} else {
			sprintf(s, "%-*s ", sr.max_essid_len, "<cryptic ESSID>");
			wattron(w_aplst, COLOR_PAIR(col));
			waddstr(w_aplst, s);
		}
		waddstr(w_aplst, ether_addr(&cur->ap_addr));

		wattroff(w_aplst, COLOR_PAIR(col));

		fmt_scan_entry(cur, s, sizeof(s));
		waddstr(w_aplst, " ");
		waddstr(w_aplst, s);
	}

	if (sr.num.entries < MAX_CH_STATS)
		goto done;

	wmove(w_aplst, MAXYLEN, 1);
	wadd_attr_str(w_aplst, A_REVERSE, "total:");
	sprintf(s, " %d ", sr.num.entries);
	waddstr(w_aplst, s);

	sprintf(s, "%s %ssc", sort_type[conf.scan_sort_order], conf.scan_sort_asc ? "a" : "de");
	wadd_attr_str(w_aplst, A_REVERSE, s);

	if (sr.num.entries + START_LINE > line) {
		sprintf(s, ", %d not shown", sr.num.entries + START_LINE - line);
		waddstr(w_aplst, s);
	}
	if (sr.num.open) {
		sprintf(s, ", %d open", sr.num.open);
		waddstr(w_aplst, s);
	}

	if (sr.num.two_gig && sr.num.five_gig) {
		waddch(w_aplst, ' ');
		wadd_attr_str(w_aplst, A_REVERSE, "5/2GHz:");
		sprintf(s, " %d/%d", sr.num.five_gig, sr.num.two_gig);
		waddstr(w_aplst, s);
	}

	if (sr.channel_stats) {
		waddch(w_aplst, ' ');
		if (conf.scan_sort_order == SO_CHAN && !conf.scan_sort_asc)
			sprintf(s, "bottom-%d:", (int)sr.num.ch_stats);
		else
			sprintf(s, "top-%d:", (int)sr.num.ch_stats);
		wadd_attr_str(w_aplst, A_REVERSE, s);

		for (i = 0; i < sr.num.ch_stats; i++) {
			waddstr(w_aplst, i ? ", " : " ");
			sprintf(s, "ch#%d", sr.channel_stats[i].val);
			wadd_attr_str(w_aplst, A_BOLD, s);
			sprintf(s, " (%d)", sr.channel_stats[i].count);
			waddstr(w_aplst, s);
		}
	}
done:
	pthread_mutex_unlock(&sr.mutex);
	wrefresh(w_aplst);
}

void scr_aplst_init(void)
{
	w_aplst = newwin_title(0, WAV_HEIGHT, "Scan window", false);

	/* Gathering scan data can take seconds. Inform user. */
	mvwaddstr(w_aplst, START_LINE, 1, "Waiting for scan data ...");
	wrefresh(w_aplst);

	init_scan_list(&sr);
	pthread_create(&scan_thread, NULL, do_scan, &sr);
}

int scr_aplst_loop(WINDOW *w_menu)
{
	int key;

	display_aplist(w_aplst);

	key = wgetch(w_menu);
	switch (key) {
	case 'a':	/* ascending */
		conf.scan_sort_asc = true;
		return -1;
	case 'c':	/* channel */
		conf.scan_sort_order = SO_CHAN;
		return -1;
	case 'C':	/* channel and signal */
		conf.scan_sort_order = SO_CHAN_SIG;
		return -1;
	case 'd':	/* descending */
		conf.scan_sort_asc = false;
		return -1;
	case 'e':	/* ESSID */
		conf.scan_sort_order = SO_ESSID;
		return -1;
	case 'm':	/* MAC address */
		conf.scan_sort_order = SO_MAC;
		return -1;
	case 'o':	/* open (descending is default) */
		conf.scan_sort_order = SO_OPEN;
		conf.scan_sort_asc = false;
		return -1;
	case 'O':	/* open and signal (descending) */
		conf.scan_sort_order = SO_OPEN_SIG;
		conf.scan_sort_asc = false;
		return -1;
	case 's':	/* signal */
		conf.scan_sort_order = SO_SIGNAL;
		return -1;
	}
	return key;
}

void scr_aplst_fini(void)
{
	pthread_cancel(scan_thread);
	free_scan_list(sr.head);
	free(sr.channel_stats);

	delwin(w_aplst);
}
