/*
 * 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"
#include "iw_nl80211.h"

/* GLOBALS */
static WINDOW *w_levels, *w_stats, *w_if, *w_info, *w_net;
static pthread_t sampling_thread;
static time_t last_update;
// Global linkstat data, populated by sampling thread.
static struct {
	bool              run;	 // enable/disable sampling
	pthread_mutex_t   mutex; // producer/consumer lock for @data
	struct iw_nl80211_linkstat data;
} linkstat;

/** Sampling pthread shared by info and histogram screen. */
static void *sampling_loop(void *arg)
{
	sigset_t blockmask;

	/* See comment in scan_scr.c for rationale. */
	sigemptyset(&blockmask);
	sigaddset(&blockmask, SIGWINCH);
	pthread_sigmask(SIG_BLOCK, &blockmask, NULL);

	do {
		pthread_mutex_lock(&linkstat.mutex);
		iw_nl80211_get_linkstat(&linkstat.data);
		pthread_mutex_unlock(&linkstat.mutex);

		iw_cache_update(&linkstat.data);
	} while (linkstat.run && usleep(conf.stat_iv * 1000) == 0);
	return NULL;
}

void sampling_init(void)
{
	pthread_mutex_init(&linkstat.mutex, NULL);
	linkstat.run = true;
	pthread_create(&sampling_thread, NULL, sampling_loop, NULL);
}

void sampling_stop(void)
{
	linkstat.run = false;
	pthread_join(sampling_thread, NULL);
	pthread_mutex_destroy(&linkstat.mutex);
}

static void display_levels(void)
{
	static float qual, signal, noise, ssnr;
	/*
	 * FIXME: revise the scale implementation. It does not work
	 *        satisfactorily, maybe it is better to have a simple
	 *        solution using 3 levels of different colour.
	 */
	int8_t nscale[2] = { conf.noise_min, conf.noise_max },
	     lvlscale[2] = { -40, -20};
	char tmp[0x100];
	int line;
	bool noise_data_valid;
	int sig_qual = -1, sig_qual_max = 0, sig_level = 0;

	noise_data_valid = iw_nl80211_have_survey_data(&linkstat.data);
	sig_level = linkstat.data.signal;

	/* See comments in iw_cache_update */
	if (sig_level == 0)
		sig_level = linkstat.data.signal_avg;
	if (sig_level == 0)
		sig_level = linkstat.data.bss_signal;

	for (line = 1; line <= WH_LEVEL; line++)
		mvwclrtoborder(w_levels, line, 1);

	if (linkstat.data.bss_signal_qual) {
		/* BSS_SIGNAL_UNSPEC is scaled 0..100 */
		sig_qual     = linkstat.data.bss_signal_qual;
		sig_qual_max = 100;
	} else if (sig_level) {
		if (sig_level < -110)
			sig_qual = 0;
		else if (sig_level > -40)
			sig_qual = 70;
		else
			sig_qual = sig_level + 110;
		sig_qual_max = 70;
	}

	line = 1;

	/* Noise data is rare. Use the space for spreading out. */
	if (!noise_data_valid)
		line++;

	if (sig_qual == -1) {
		line++;
	} else {
		qual = ewma(qual, sig_qual, conf.meter_decay / 100.0);

		mvwaddstr(w_levels, line++, 1, "link quality: ");
		sprintf(tmp, "%0.f%%  ", (1e2 * qual)/sig_qual_max);
		waddstr_b(w_levels, tmp);
		sprintf(tmp, "(%0.f/%d)  ", qual, sig_qual_max);
		waddstr(w_levels, tmp);

		waddbar(w_levels, line++, qual, 0, sig_qual_max, lvlscale, true);
	}

	/* Spacer */
	line++;
	if (!noise_data_valid)
		line++;

	if (sig_level != 0) {
		signal = ewma(signal, sig_level, conf.meter_decay / 100.0);

		mvwaddstr(w_levels, line++, 1, "signal level: ");
		sprintf(tmp, "%.0f dBm (%s)", signal, dbm2units(signal));
		waddstr_b(w_levels, tmp);

		waddbar(w_levels, line, signal, conf.sig_min, conf.sig_max,
			lvlscale, true);
		if (conf.lthreshold_action)
			waddthreshold(w_levels, line, signal, conf.lthreshold,
				      conf.sig_min, conf.sig_max, lvlscale, '>');
		if (conf.hthreshold_action)
			waddthreshold(w_levels, line, signal, conf.hthreshold,
				      conf.sig_min, conf.sig_max, lvlscale, '<');
	}

	line++;

	if (noise_data_valid) {
		noise = ewma(noise, linkstat.data.survey.noise, conf.meter_decay / 100.0);

		mvwaddstr(w_levels, line++, 1, "noise level:  ");
		sprintf(tmp, "%.0f dBm (%s)", noise, dbm2units(noise));
		waddstr_b(w_levels, tmp);

		waddbar(w_levels, line++, noise, conf.noise_min, conf.noise_max,
			nscale, false);
	}

	if (noise_data_valid && sig_level) {
		ssnr = ewma(ssnr, sig_level - linkstat.data.survey.noise,
				  conf.meter_decay / 100.0);

		mvwaddstr(w_levels, line++, 1, "SNR:           ");
		sprintf(tmp, "%.0f dB", ssnr);
		waddstr_b(w_levels, tmp);
	}

	wrefresh(w_levels);
}

static void display_stats(void)
{
	char tmp[0x120];

	/*
	 * Interface RX stats
	 */
	mvwaddstr(w_stats, 1, 1, "RX: ");

	if (linkstat.data.rx_packets) {
		sprintf(tmp, "%s (%s)", int_counts(linkstat.data.rx_packets),
			byte_units(linkstat.data.rx_bytes));
		waddstr_b(w_stats, tmp);
	} else {
		waddstr(w_stats, "n/a");
	}

	if (iw_nl80211_have_survey_data(&linkstat.data)) {
		if (linkstat.data.rx_bitrate[0]) {
			waddstr(w_stats, ", rate: ");
			waddstr_b(w_stats, linkstat.data.rx_bitrate);
		}

		if (linkstat.data.expected_thru) {
			if (linkstat.data.expected_thru >= 1024)
				sprintf(tmp, " (expected: %.1f MB/s)",  linkstat.data.expected_thru/1024.0);
			else
				sprintf(tmp, " (expected: %u kB/s)",  linkstat.data.expected_thru);
			waddstr(w_stats, tmp);
		}
	}

	if (linkstat.data.rx_drop_misc) {
		waddstr(w_stats, ", drop: ");
		sprintf(tmp, "%'llu (%.1f%%)", (unsigned long long)linkstat.data.rx_drop_misc,
				(1e2 * linkstat.data.rx_drop_misc)/linkstat.data.rx_packets);
		waddstr_b(w_stats, tmp);
	}

	wclrtoborder(w_stats);

	/*
	 * Interface TX stats
	 */
	mvwaddstr(w_stats, 2, 1, "TX: ");

	if (linkstat.data.tx_packets) {
		sprintf(tmp, "%s (%s)", int_counts(linkstat.data.tx_packets),
			byte_units(linkstat.data.tx_bytes));
		waddstr_b(w_stats, tmp);
	} else {
		waddstr(w_stats, "n/a");
	}

	if (iw_nl80211_have_survey_data(&linkstat.data) && linkstat.data.tx_bitrate[0]) {
		waddstr(w_stats, ", rate: ");
		waddstr_b(w_stats, linkstat.data.tx_bitrate);
	}

	if (linkstat.data.tx_retries) {
		waddstr(w_stats, ", retries: ");
		sprintf(tmp, "%s (%.1f%%)", int_counts(linkstat.data.tx_retries),
			(1e2 * linkstat.data.tx_retries)/linkstat.data.tx_packets);
		waddstr_b(w_stats, tmp);
	}

	if (linkstat.data.tx_failed) {
		waddstr(w_stats, ", failed: ");
		waddstr_b(w_stats, int_counts(linkstat.data.tx_failed));
	}
	wclrtoborder(w_stats);
	wrefresh(w_stats);
}

static void display_info(WINDOW *w_if, WINDOW *w_info)
{
	struct iw_dyn_info info;
	struct iw_range	range;
	struct iw_nl80211_ifstat ifs;
	struct iw_nl80211_reg ir;
	char tmp[0x100];

	iw_getinf_range(conf_ifname(), &range);
	dyn_info_get(&info, conf_ifname());
	iw_nl80211_getifstat(&ifs);
	iw_nl80211_getreg(&ir);

	/*
	 * Interface Part
	 */
	wmove(w_if, 1, 1);
	waddstr_b(w_if, conf_ifname());
	sprintf(tmp, " (%s)", info.name);
	waddstr(w_if, tmp);

	/* PHY */
	waddstr(w_if, ", phy ");
	sprintf(tmp, "%d", ifs.phy);
	waddstr_b(w_if, tmp);

	/* Regulatory domain */
	waddstr(w_if, ", reg: ");
	if (ir.region > 0) {
		waddstr_b(w_if, ir.country);
		sprintf(tmp, " (%s)", dfs_domain_name(ir.region));
		waddstr(w_if, tmp);
	} else {
		waddstr_b(w_if, "n/a");
	}

	if (ifs.ssid[0]) {
		waddstr(w_if, ", SSID: ");
		waddstr_b(w_if, ifs.ssid);
	}

	wclrtoborder(w_if);
	wrefresh(w_if);

	/*
	 * Info window:
	 */
	wmove(w_info, 1, 1);
	waddstr(w_info, "mode: ");
	waddstr_b(w_info, iftype_name(ifs.iftype));

	if (!ether_addr_is_zero(&linkstat.data.bssid)) {
		waddstr_b(w_info, ", ");

		switch (linkstat.data.status) {
		case NL80211_BSS_STATUS_ASSOCIATED:
			waddstr(w_info, "connected to: ");
			break;
		case NL80211_BSS_STATUS_AUTHENTICATED:
			waddstr(w_info, "authenticated with: ");
			break;
		case NL80211_BSS_STATUS_IBSS_JOINED:
			waddstr(w_info, "joined IBSS: ");
			break;
		default:
			waddstr(w_info, "station: ");
		}
		waddstr_b(w_info, ether_lookup(&linkstat.data.bssid));

		if (linkstat.data.status == NL80211_BSS_STATUS_ASSOCIATED) {
			waddstr_b(w_info, ",");
			waddstr(w_info, " time: ");
			waddstr_b(w_info, pretty_time(linkstat.data.connected_time));

			waddstr(w_info, ", inactive: ");
			sprintf(tmp, "%.1fs", (float)linkstat.data.inactive_time/1e3);
			waddstr_b(w_info, tmp);
		}
	}
	wclrtoborder(w_info);

	wmove(w_info, 2, 1);
	/* Frequency / channel */
	if (ifs.freq) {
		waddstr(w_info, "freq: ");
		sprintf(tmp, "%d MHz", ifs.freq);
		waddstr_b(w_info, tmp);

		/* The following condition should in theory never happen */
		if (linkstat.data.survey.freq && linkstat.data.survey.freq != ifs.freq) {
			sprintf(tmp, " [survey freq: %d MHz]", linkstat.data.survey.freq);
			waddstr(w_info, tmp);
		}

		if (ifs.freq_ctr1 && ifs.freq_ctr1 != ifs.freq) {
			waddstr(w_info, ", ctr1: ");
			sprintf(tmp, "%d MHz", ifs.freq_ctr1);
			waddstr_b(w_info, tmp);
		}
		if (ifs.freq_ctr2 && ifs.freq_ctr2 != ifs.freq_ctr1 && ifs.freq_ctr2 != ifs.freq) {
			waddstr(w_info, ", ctr2: ");
			sprintf(tmp, "%d MHz", ifs.freq_ctr2);
			waddstr_b(w_info, tmp);
		}

		waddstr(w_info, ", channel: ");
		sprintf(tmp, "%d", ieee80211_frequency_to_channel(ifs.freq));
		waddstr_b(w_info, tmp);

		if (ifs.chan_width >= 0) {
			sprintf(tmp, " (width: %s)", channel_width_name(ifs.chan_width));
			waddstr(w_info, tmp);
		} else if (ifs.chan_type >= 0) {
			sprintf(tmp, " (%s)", channel_type_name(ifs.chan_type));
			waddstr(w_info, tmp);
		}
	} else if (iw_nl80211_have_survey_data(&linkstat.data)) {
		waddstr(w_info, "freq: ");
		sprintf(tmp, "%d MHz", linkstat.data.survey.freq);
		waddstr_b(w_info, tmp);
	} else {
		waddstr(w_info, "frequency/channel: n/a");
	}
	wclrtoborder(w_info);

	/* Channel data */
	wmove(w_info, 3, 1);
	if (iw_nl80211_have_survey_data(&linkstat.data)) {
		waddstr(w_info, "channel ");
		waddstr(w_info, "active: ");
		waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.active));

		waddstr(w_info, ", busy: ");
		waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.busy));

		if (linkstat.data.survey.time.ext_busy) {
			waddstr(w_info, ", ext-busy: ");
			waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.ext_busy));
		}

		waddstr(w_info, ", rx: ");
		waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.rx));

		waddstr(w_info, ", tx: ");
		waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.tx));

		if (linkstat.data.survey.time.scan) {
			waddstr(w_info, ", scan: ");
			waddstr_b(w_info, pretty_time_ms(linkstat.data.survey.time.scan));
		}
	} else if (linkstat.data.tx_bitrate[0] && linkstat.data.rx_bitrate[0]) {
		waddstr(w_info, "rx rate: ");
		waddstr_b(w_info, linkstat.data.rx_bitrate);

		if (linkstat.data.expected_thru) {
			if (linkstat.data.expected_thru >= 1024)
				sprintf(tmp, " (exp: %.1f MB/s)",  linkstat.data.expected_thru/1024.0);
			else
				sprintf(tmp, " (exp: %u kB/s)",  linkstat.data.expected_thru);
			waddstr(w_info, tmp);
		}
		waddstr(w_info, ", tx rate: ");
		waddstr_b(w_info, linkstat.data.tx_bitrate);
	}

	/* Beacons */
	wmove(w_info, 4, 1);

	if (linkstat.data.beacons) {
		waddstr(w_info, "beacons: ");
		sprintf(tmp, "%'llu", (unsigned long long)linkstat.data.beacons);
		waddstr_b(w_info, tmp);

		if (linkstat.data.beacon_loss) {
			waddstr(w_info, ", lost: ");
			waddstr_b(w_info, int_counts(linkstat.data.beacon_loss));
		}
		waddstr(w_info, ", avg sig: ");
		sprintf(tmp, "%d dBm", (int8_t)linkstat.data.beacon_avg_sig);
		waddstr_b(w_info, tmp);

		waddstr(w_info, ", interval: ");
		sprintf(tmp, "%.1fs", (linkstat.data.beacon_int * 1024.0)/1e6);
		waddstr_b(w_info, tmp);

		waddstr(w_info, ", DTIM: ");
		sprintf(tmp, "%u", linkstat.data.dtim_period);
		waddstr_b(w_info, tmp);
	} else {
		waddstr(w_info, "station flags:");
		if (linkstat.data.cts_protection)
			waddstr_b(w_info, " CTS");
		if (linkstat.data.wme)
			waddstr_b(w_info, " WME");
		if (linkstat.data.tdls)
			waddstr_b(w_info, " TDLS");
		if (linkstat.data.mfp)
			waddstr_b(w_info, " MFP");
		if (!(linkstat.data.cts_protection | linkstat.data.wme | linkstat.data.tdls | linkstat.data.mfp))
			waddstr_b(w_info, " (none)");
		waddstr(w_info, ", preamble:");
		if (linkstat.data.long_preamble)
			waddstr_b(w_info, " long");
		else
			waddstr_b(w_info, " short");
		waddstr(w_info, ", slot:");
		if (linkstat.data.short_slot_time)
			waddstr_b(w_info, " short");
		else
			waddstr_b(w_info, " long");
	}

	if (info.cap_sens) {
		waddstr(w_info, ",  sensitivity: ");
		if (info.sens < 0)
			sprintf(tmp, "%d dBm", info.sens);
		else
			sprintf(tmp, "%d/%d", info.sens,
				range.sensitivity);
		waddstr_b(w_info, tmp);
	}

	wclrtoborder(w_info);

	wmove(w_info, 5, 1);
	waddstr(w_info, "power mgt: ");
	if (info.cap_power)
		waddstr_b(w_info, format_power(&info.power, &range));
	else
		waddstr(w_info, "n/a");

	if (info.cap_txpower && info.txpower.disabled) {
		waddstr(w_info, ",  tx-power: off");
	} else if (info.cap_txpower) {
		/*
		 * Convention: auto-selected values start with a capital
		 *             letter, otherwise with a small letter.
		 */
		if (info.txpower.fixed)
			waddstr(w_info, ",  tx-power: ");
		else
			waddstr(w_info, ",  TX-power: ");
		waddstr_b(w_info, format_txpower(&info.txpower));
	}
	wclrtoborder(w_info);

	wmove(w_info, 6, 1);
	waddstr(w_info, "retry: ");
	if (info.cap_retry)
		waddstr_b(w_info, format_retry(&info.retry, &range));
	else
		waddstr(w_info, "n/a");

	waddstr(w_info, ",  ");
	if (info.cap_rts) {
		waddstr(w_info, info.rts.fixed ? "rts/cts: " : "RTS/cts: ");
		if (info.rts.disabled)
			sprintf(tmp, "off");
		else
			sprintf(tmp, "%d B", info.rts.value);
		waddstr_b(w_info, tmp);
	} else {
		waddstr(w_info, "rts/cts: n/a");
	}

	waddstr(w_info, ",  ");
	if (info.cap_frag) {
		waddstr(w_info, info.frag.fixed ? "frag: " : "Frag: ");
		if (info.frag.disabled)
			sprintf(tmp, "off");
		else
			sprintf(tmp, "%d B", info.frag.value);
		waddstr_b(w_info, tmp);
	} else {
		waddstr(w_info, "frag: n/a");
	}
	wclrtoborder(w_info);

	/* FIXME: re-enable encryption information (issue #8)
	wmove(w_info, 7, 1);
	waddstr(w_info, "encryption: ");
	*/

	wclrtoborder(w_info);
	wrefresh(w_info);
}

static void display_netinfo(WINDOW *w_net)
{
	struct if_info info;
	char tmp[0x40];

	if_getinf(conf_ifname(), &info);

	wmove(w_net, 1, 1);
	wclrtoborder(w_net);
	if (getmaxy(w_net) == WH_NET_MAX) {
		waddstr(w_net, conf_ifname());

		waddstr_b(w_net, " (");
		waddstr(w_net, info.flags & IFF_UP ? "UP" : "DOWN");
		if (info.flags & IFF_RUNNING)		/* Interface RFC2863 OPER_UP	*/
			waddstr(w_net, " RUNNING");
#ifdef IFF_LOWER_UP	/* Linux 2.6.17 */
		if (info.flags & IFF_LOWER_UP)		/* Driver signals L1 up		*/
			waddstr(w_net, " LOWER_UP");
#endif
#ifdef IFF_DORMANT	/* Linux 2.6.17 */
		if (info.flags & IFF_DORMANT)		/* Driver signals dormant	*/
			waddstr(w_net, " DORMANT");
#endif
		if (info.flags & IFF_MASTER)		/* Master of a load balancer 	*/
			waddstr(w_net, " MASTER");
		if (info.flags & IFF_SLAVE)		/* Slave of a load balancer 	*/
			waddstr(w_net, " SLAVE");
		if (info.flags & IFF_POINTOPOINT)	/* Is a point-to-point link	*/
			waddstr(w_net, " POINTOPOINT");
		if (info.flags & IFF_DYNAMIC)		/* Address is volatile		*/
			waddstr(w_net, " DYNAMIC");
		if (info.flags & IFF_BROADCAST)		/* Valid broadcast address set	*/
			waddstr(w_net, " BROADCAST");
		if (info.flags & IFF_MULTICAST)		/* Supports multicast		*/
			waddstr(w_net, " MULTICAST");
		if (info.flags & IFF_ALLMULTI)		/* Receive all mcast  packets	*/
			waddstr(w_net, " ALLMULTI");
		if (info.flags & IFF_NOARP)		/* No ARP protocol		*/
			waddstr(w_net, " NOARP");
		if (info.flags & IFF_NOTRAILERS)	/* Avoid use of trailers	*/
			waddstr(w_net, " NOTRAILERS");
		if (info.flags & IFF_PROMISC)		/* Is in promiscuous mode	*/
			waddstr(w_net, " PROMISC");
		if (info.flags & IFF_DEBUG)		/* Internal debugging flag	*/
			waddstr(w_net, " DEBUG");
		waddstr_b(w_net, ")");

		wmove(w_net, 2, 1);
		wclrtoborder(w_net);
	}
	waddstr(w_net, "mac: ");
	waddstr_b(w_net, ether_lookup(&info.hwaddr));

	if (getmaxy(w_net) == WH_NET_MAX) {
		waddstr(w_net, ", qlen: ");
		sprintf(tmp, "%u", info.txqlen);
		waddstr_b(w_net, tmp);

		wmove(w_net, 3, 1);
		wclrtoborder(w_net);
	} else {
		waddstr(w_net, ", ");
	}
	waddstr(w_net, "ip: ");

	if (!info.addr.s_addr) {
		waddstr(w_net, "n/a");
	} else {
		sprintf(tmp, "%s/%u", inet_ntoa(info.addr),
				      prefix_len(&info.netmask));
		waddstr_b(w_net, tmp);

		/* only show bcast address if not set to the obvious default */
		if (info.bcast.s_addr !=
		    (info.addr.s_addr | ~info.netmask.s_addr)) {
			waddstr(w_net, ",  bcast: ");
			waddstr_b(w_net, inet_ntoa(info.bcast));
		}
	}
	wclrtoborder(w_net);

	/* 802.11 MTU may be greater than Ethernet MTU (1500) */
	if (info.mtu && info.mtu != ETH_DATA_LEN) {
		waddstr(w_net, ",  mtu: ");
		sprintf(tmp, "%u", info.mtu);
		waddstr_b(w_net, tmp);
	}

	wrefresh(w_net);
}

void scr_info_init(void)
{
	int line = 0;

	w_if	 = newwin_title(line, WH_IFACE, "Interface", true);
	line += WH_IFACE;
	w_levels = newwin_title(line, WH_LEVEL, "Levels", true);
	line += WH_LEVEL;
	w_stats	 = newwin_title(line, WH_STATS, "Statistics", true);
	line += WH_STATS;
	w_info	 = newwin_title(line, WH_INFO_MIN, "Info", true);
	line += WH_INFO_MIN;
	if (LINES >= WH_INFO_SCR_MIN + (WH_NET_MAX - WH_NET_MIN))
		w_net = newwin_title(line, WH_NET_MAX, "Network", false);
	else
		w_net = newwin_title(line, WH_NET_MAX, "Network", false);

	sampling_init();
}

int scr_info_loop(WINDOW *w_menu)
{
	time_t now = time(NULL);

	if (!pthread_mutex_trylock(&linkstat.mutex)) {
		display_levels();
		display_stats();
		pthread_mutex_unlock(&linkstat.mutex);
	}

	if (now - last_update >= conf.info_iv) {
		last_update = now;
		display_info(w_if, w_info);
		display_netinfo(w_net);
	}
	return wgetch(w_menu);
}

void scr_info_fini(void)
{
	sampling_stop();

	delwin(w_net);
	delwin(w_info);
	delwin(w_stats);
	delwin(w_levels);
	delwin(w_if);
}
