/*
 *        aplink.c from Access Point SNMP Utils for Linux
 *
 * Copyright (c) 2005 Jan Rafaj <jr-aputils at cedric dot unob dot cz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 from
 * June 1991 as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ap-utils.h"

#define S_RSSI     _("RSSI:     [")
#define S_RSSI_AVG _("RSSI avg: [")
#define S_RSSI_TOP _("RSSI top: [")
#define S_RSSI_ROU _("RSSI rou: [")
#define APLINK_HELP _("P - pause, T - toggle graph view, Q - quit to menu, Other key - force update.")

#define GRWIN_LENGTH	(COLS - MCOLS - 7)
#define GRWIN_HEIGHT    (LAST_ROW - 10)

int gr_mode = 0;

extern WINDOW *main_sub;
extern short ap_type, ap_vendorext;
extern int wait_mode, poll_delay;

/*
 * TODO: Implement APClientInfo as alternative, for NetGear ME102 MIB
 * (ap_type == ATMEL12350 && ap_vendorext == NONE).
 */

void atmel_aplink()
{
    char bridgeOperationalMode[] = {
	0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A, 0x01, 0x01, 0x04, 0x01, 0x00
    };
    /*
     * ATMEL12350 EZYNET MIB and ATMEL410 SBRIDGES MIB specific OIDs:
     * for ATMEL410 SBRIDGES MIB: .1.3.6.1.4.1.410.1.2.8.1.0
     * for ATMEL12350 EZYNET MIB: .1.3.6.1.4.1.12350.1.2.7.4.0
     */
    char NetworkSettings[] = {
	0x2B, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1A, 0x01, 0x02, 0x08, 0x01, 0x00
    };

    /* APClient mode: AP link diagnostics */

    struct NetworkSettings_ATMEL410_SBRIDGES sa_sb_410;
    struct NetworkSettings_ATMEL12350_EZYNET sa_en_12350;

    /* Used common members of the 2 structures above. */
    unsigned char BSSID[6];
    /* unsigned short InfoCapability; */
    unsigned char Rssi;
    unsigned char Channel;
    /* LinkQuality for NetworkSettings_ATMEL410_SBRIDGES */
    /* CurrentRate for NetworkSettings_ATMEL12350_EZYNET */
    unsigned char LQCR;
    unsigned char ESSID[32];
    unsigned long ESSLEN; /* uchar for NetworkSettings_ATMEL410_SBRIDGES */

    int i, j = 0, linked = 0, samples = 0, sum = 0, rssi_perc;
    int rssi_avg = 0, rssi_avg_perc, rssi_top = 0, rssi_top_perc;
    int rssi_rou, rssi_rou_perc, lq_perc = 0, lq_perc_top = 0;
    int ringbuf_pos = 0, p_wait_mode = wait_mode;
    char ringbuf[20];
    char message[1024];
    char *gr_modes[5] = { _("RSSI [%]"),
			  _("RSSI average [%]"),
			  _("RSSI rounded [%]"),
			  _("Link Quality [%]"),
			  NULL };
    WINDOW *gauge_rssi;
    WINDOW *gauge_rssi_top;
    WINDOW *gauge_rssi_avg;
    WINDOW *gauge_rssi_rou;
    WINDOW *grwin_rssi;
    WINDOW *grwin_rssi_avg = NULL;
    WINDOW *grwin_rssi_rou = NULL;
    WINDOW *grwin_lq = NULL;
    varbind varbinds[1];

    if (ap_type == ATMEL12350) {
	bridgeOperationalMode[5] = 0xE0;
	bridgeOperationalMode[6] = 0x3E;

	NetworkSettings[5] = 0xE0;
	NetworkSettings[6] = 0x3E;
	NetworkSettings[9] = 0x07;
	NetworkSettings[10] = 0x04;
    }	

    gauge_rssi = derwin(main_sub, 1, 20, 2, strlen(S_RSSI));
    gauge_rssi_top = derwin(main_sub, 1, 20, 3, strlen(S_RSSI_TOP));
    gauge_rssi_avg = derwin(main_sub, 1, 20, 4, strlen(S_RSSI_AVG));
    gauge_rssi_rou = derwin(main_sub, 1, 20, 5, strlen(S_RSSI_ROU));
    grwin_rssi = derwin(main_sub, GRWIN_HEIGHT + 2, GRWIN_LENGTH + 2, 9, 3);

    /* find out mode the device is currently in */
    varbinds[0].oid = bridgeOperationalMode;
    varbinds[0].len_oid = sizeof(bridgeOperationalMode);
    varbinds[0].value = bridgeOperationalMode;
    varbinds[0].len_val = 0;
    varbinds[0].type = NULL_VALUE;
    print_help(WAIT_RET);
    if (snmp(varbinds, 1, GET) <= 0) {
	  print_helperr(ERR_RET);
	  getch();
	  goto exit;
    }

    /*
     * Rule out all modes except APClient(3) mode and WRepeater(5) mode on
     * device running firmware with necessary vendor extensions.
     */
    if(!( ((ap_type == ATMEL410 && ap_vendorext == SBRIDGES) ||
	   (ap_type == ATMEL12350 && ap_vendorext == EZYNET)) &&
	  (*(varbinds[0].value) == 3 || *(varbinds[0].value) == 5) )) {
	mvwaddstr(main_sub, 1, 1,
	    _("Not available - device must have firmware with necessary "));
	mvwaddstr(main_sub, 2, 1,
	    _("vendor extensions and be in either 'Access Point client'"));
	mvwaddstr(main_sub, 3, 1,
	    _("or 'Repeater' mode."));
	print_help(ANY_KEY);
	wrefresh(main_sub);
	getch();
	goto exit;
    }

    wait_mode = WAIT_TIMEOUT;

    print_top(POLL_ON, AP_TITLE);
    print_help(APLINK_HELP);
    noecho();

    memset(ringbuf, 0, sizeof(ringbuf));

    box(grwin_rssi, 0, 0);
    for (i = GRWIN_HEIGHT - GRWIN_HEIGHT / 3; i > 0; i -= GRWIN_HEIGHT / 3) {
	j += 33;
	mvwprintw(main_sub, 9 + i, 0, "%u", j);
	mvwaddch(grwin_rssi, i, 0, ACS_SSSS);
    }
    delwin(grwin_rssi);
    wmove(main_sub, 9, 6);
    print_bold(main_sub, gr_modes[gr_mode]);
    grwin_rssi     = newwin(GRWIN_HEIGHT, GRWIN_LENGTH, 12, MCOLS + 4);
    grwin_rssi_avg = newwin(GRWIN_HEIGHT, GRWIN_LENGTH, 12, MCOLS + 4);
    grwin_rssi_rou = newwin(GRWIN_HEIGHT, GRWIN_LENGTH, 12, MCOLS + 4);
    if (ap_type == ATMEL410)
	grwin_lq       = newwin(GRWIN_HEIGHT, GRWIN_LENGTH, 12, MCOLS + 4);
    wattrset(grwin_rssi,     COLOR_PAIR(COLOR_BLUE));
    wattrset(grwin_rssi_avg, COLOR_PAIR(COLOR_BLUE));
    wattrset(grwin_rssi_rou, COLOR_PAIR(COLOR_BLUE));
    if (ap_type == ATMEL410)
	wattrset(grwin_lq, COLOR_PAIR(COLOR_BLUE));

    while (1) {
	mvwaddstr(main_sub, 0, 0, "Associated with AP: ");

	varbinds[0].oid = NetworkSettings;
	varbinds[0].len_oid = sizeof(NetworkSettings);
	varbinds[0].value = NetworkSettings;
	varbinds[0].type = STRING_VALUE;
	varbinds[0].len_val = 0;

	if (snmp(varbinds, 1, GET) <= 0) {
	    /* do just once */
	    waddstr(main_sub, NO);
	    if (linked) {
		linked = sum = samples = ringbuf_pos = 0;
		memset(ringbuf, 0, sizeof(ringbuf));

		wclrtoeol(main_sub);
		clear_main_new(1, 9);
		/* wclrtobot(main_sub); */

		wattrset(grwin_rssi,     COLOR_PAIR(COLOR_RED));
		wattrset(grwin_rssi_avg, COLOR_PAIR(COLOR_RED));
		wattrset(grwin_rssi_rou, COLOR_PAIR(COLOR_RED));
		if (ap_type == ATMEL410)
		    wattrset(grwin_lq,   COLOR_PAIR(COLOR_RED));

		for (i = 0; i < GRWIN_HEIGHT; i++) {
		    mvwdelch(grwin_rssi,     i, 0);
		    mvwdelch(grwin_rssi_avg, i, 0);
		    mvwdelch(grwin_rssi_rou, i, 0);
		    mvwaddch(grwin_rssi,     i, GRWIN_LENGTH - 1, ACS_SBSB);
		    mvwaddch(grwin_rssi_avg, i, GRWIN_LENGTH - 1, ACS_SBSB);
		    mvwaddch(grwin_rssi_rou, i, GRWIN_LENGTH - 1, ACS_SBSB);
		    if (ap_type == ATMEL410) {
			mvwdelch(grwin_lq,   i, 0);
			mvwaddch(grwin_lq,   i, GRWIN_LENGTH - 1, ACS_SBSB);
		    }
		}
		wattrset(grwin_rssi,     COLOR_PAIR(COLOR_BLUE));
		wattrset(grwin_rssi_avg, COLOR_PAIR(COLOR_BLUE));
		wattrset(grwin_rssi_rou, COLOR_PAIR(COLOR_BLUE));
		if (ap_type == ATMEL410)
		    wattrset(grwin_lq,   COLOR_PAIR(COLOR_BLUE));
	    }

	    wnoutrefresh(main_sub);
	    wnoutrefresh(gr_mode == 0 ? grwin_rssi :
			 gr_mode == 1 ? grwin_rssi_avg :
			 gr_mode == 2 ? grwin_rssi_rou : grwin_lq);

	    doupdate();
	} else {
	    waddstr(main_sub, YES);

	    if (ap_type == ATMEL410) {
		memcpy(&sa_sb_410, varbinds[0].value, varbinds[0].len_val);

		memcpy(BSSID, sa_sb_410.BSSID, 6);
		/* InfoCapability = sa_sb_410.InfoCapability; */
		Rssi = sa_sb_410.Rssi;
		Channel = sa_sb_410.Channel;
		LQCR = sa_sb_410.LinkQuality;
		memcpy(ESSID, sa_sb_410.ESSID, 32);
		ESSLEN = sa_sb_410.ESSLEN;
	    } else { /* ap_type == ATMEL12350 */
		memcpy(&sa_en_12350, varbinds[0].value, varbinds[0].len_val);

		memcpy(BSSID, sa_en_12350.BSSID, 6);
		/* InfoCapability = sa_en_12350.InfoCapability; */
		Rssi = sa_en_12350.Rssi;
		Channel = sa_en_12350.Channel;
		LQCR = sa_en_12350.CurrentRate;
		memcpy(ESSID, sa_en_12350.ESSID, 32);
		ESSLEN = sa_en_12350.ESSLEN;
	    }

	    sprintf(message, "Joined BSSID: %02X%02X%02X%02X%02X%02X",
		BSSID[0] & 0xFF, BSSID[1] & 0xFF, BSSID[2] & 0xFF,
		BSSID[3] & 0xFF, BSSID[4] & 0xFF, BSSID[5] & 0xFF);
	    mvwaddstr(main_sub, 1, 0, message);

	    mvwaddstr(main_sub, 2, 0, S_RSSI);
	    rssi_perc = (int)((minimum (Rssi, 40)) * (float)2.5);
	    werase(gauge_rssi);
	    for (i = 0; i < (20 * rssi_perc / 100); i++) {
		if (i == 0)
		    wattrset(gauge_rssi, COLOR_PAIR(COLOR_RED));
		if (i == 7)
		    wattrset(gauge_rssi, COLOR_PAIR(COLOR_YELLOW) | A_BOLD);
		if (i == 14)
		    wattrset(gauge_rssi, COLOR_PAIR(COLOR_GREEN));
		waddch(gauge_rssi, ACS_BLOCK);
	    }
	    wnoutrefresh(gauge_rssi);
	    sprintf(message, "] [%4ddBm] [%3u%%] [%3u]",
		dbmconv(Rssi), rssi_perc, Rssi);
	    mvwaddstr(main_sub, 2, strlen(S_RSSI) + 20, message);

	    mvwaddstr(main_sub, 3, 0, S_RSSI_TOP);
	    if (Rssi > rssi_top)
		rssi_top = Rssi;
	    rssi_top_perc = (int)((minimum (rssi_top, 40)) * (float)2.5);
	    werase(gauge_rssi_top);
	    for (i = 0; i < (20 * rssi_top_perc / 100); i++) {
		if (i == 0)
		    wattrset(gauge_rssi_top, COLOR_PAIR(COLOR_RED));
		if (i == 7)
		    wattrset(gauge_rssi_top, COLOR_PAIR(COLOR_YELLOW) | A_BOLD);
		if (i == 14)
		    wattrset(gauge_rssi_top, COLOR_PAIR(COLOR_GREEN));
		waddch(gauge_rssi_top, ACS_BLOCK);
	    }
	    wnoutrefresh(gauge_rssi_top);
	    sprintf(message, "] [%4ddBm] [%3u%%] [%3u]",
		dbmconv(rssi_top), rssi_top_perc, rssi_top);
	    mvwaddstr(main_sub, 3, strlen(S_RSSI_TOP) + 20, message);

	    mvwaddstr(main_sub, 4, 0, S_RSSI_AVG);
	    sum += Rssi;
	    rssi_avg = sum / ++samples;
	    if (samples == 100000) {
		samples = 1;
		sum = Rssi;
	    }
	    rssi_avg_perc = (int)((minimum (rssi_avg, 40)) * (float)2.5);
	    werase(gauge_rssi_avg);
	    for (i = 0; i < (20 * rssi_avg_perc / 100); i++) {
		if (i == 0)
		    wattrset(gauge_rssi_avg, COLOR_PAIR(COLOR_RED));
		if (i == 7)
		    wattrset(gauge_rssi_avg, COLOR_PAIR(COLOR_YELLOW) | A_BOLD);
		if (i == 14)
		    wattrset(gauge_rssi_avg, COLOR_PAIR(COLOR_GREEN));
		waddch(gauge_rssi_avg, ACS_BLOCK);
	    }
	    wnoutrefresh(gauge_rssi_avg);
	    sprintf(message, "] [%4ddBm] [%3u%%] / %5u sampl.",
		dbmconv(rssi_avg), rssi_avg_perc, samples);
	    mvwaddstr(main_sub, 4, strlen(S_RSSI_AVG) + 20, message);

	    mvwaddstr(main_sub, 5, 0, S_RSSI_ROU);
	    ringbuf[ringbuf_pos++] = Rssi;
	    if (ringbuf_pos == sizeof(ringbuf))
		ringbuf_pos = 0;
	    rssi_rou = 0;
	    for (i = 0; i < (int)sizeof(ringbuf); rssi_rou += ringbuf[i], i++);
	    rssi_rou /= sizeof(ringbuf);
	    rssi_rou_perc = (int)((minimum (rssi_rou, 40)) * (float)2.5);
	    werase(gauge_rssi_rou);
	    for (i = 0; i < (20 * rssi_rou_perc / 100); i++) {
		if (i == 0)
		    wattrset(gauge_rssi_rou, COLOR_PAIR(COLOR_RED));
		if (i == 7)
		    wattrset(gauge_rssi_rou, COLOR_PAIR(COLOR_YELLOW) | A_BOLD);
		if (i == 14)
		    wattrset(gauge_rssi_rou, COLOR_PAIR(COLOR_GREEN));
		waddch(gauge_rssi_rou, ACS_BLOCK);
	    }
	    wnoutrefresh(gauge_rssi_rou);
	    sprintf(message, "] [%4ddBm] [%3u%%] / last %2u sam.",
		dbmconv(rssi_rou), rssi_rou_perc, sizeof(ringbuf));
	    mvwaddstr(main_sub, 5, strlen(S_RSSI_ROU) + 20, message);

	    if (ap_type == ATMEL410) {
		lq_perc = (int)(100 - (minimum (LQCR, 40)) * (float)2.5);
		if (lq_perc > lq_perc_top)
		    lq_perc_top = lq_perc;
		sprintf(message, "Link quality: %3u%% (top: %3u%%)",
		    lq_perc, lq_perc_top);
	    } else { /* ap_type == ATMEL12350 */
		sprintf(message, "Current rate: ");
		switch(LQCR) {
		   case 0:
			strcat(message, "1 Mbps  ");
			break;
		   case 1:
			strcat(message, "2 Mbps  ");
			break;
		   case 2:
			strcat(message, "5.5 Mbps");
			break;
		   case 3:
			strcat(message, "11 Mbps ");
		}
	    }
	    mvwaddstr(main_sub, 6, 0, message);

	    sprintf(message, "Channel: %2u", Channel);
	    mvwaddstr(main_sub, 7, 0, message);

	    mvwaddstr(main_sub, 8, 0, "ESSID: ");
	    waddnstr(main_sub, ESSID, ESSLEN);
/*
	    sprintf(message, "InfoCapability: %3u", InfoCapability & 0xFF);
	    mvwaddstr(main_sub, 9, 0, message);
*/
	    wnoutrefresh(main_sub);

	    for (i = 0; i < GRWIN_HEIGHT; i++) {
		mvwdelch(grwin_rssi,     i, 0);
		mvwdelch(grwin_rssi_avg, i, 0);
		mvwdelch(grwin_rssi_rou, i, 0);
		if (ap_type == ATMEL410)
		    mvwdelch(grwin_lq,   i, 0);
	    }
	    for (i = 0; i < GRWIN_HEIGHT * rssi_perc / 100; i++)
		mvwaddch(grwin_rssi,
		    GRWIN_HEIGHT - 1 - i, GRWIN_LENGTH - 1, ACS_BLOCK);
	    for (i = 0; i < GRWIN_HEIGHT * rssi_avg_perc / 100; i++)
		mvwaddch(grwin_rssi_avg,
		    GRWIN_HEIGHT - 1 - i, GRWIN_LENGTH - 1, ACS_BLOCK);
	    for (i = 0; i < GRWIN_HEIGHT * rssi_rou_perc / 100; i++)
		mvwaddch(grwin_rssi_rou,
		    GRWIN_HEIGHT - 1 - i, GRWIN_LENGTH - 1, ACS_BLOCK);
	    if (ap_type == ATMEL410)
		for (i = 0; i < GRWIN_HEIGHT * lq_perc / 100; i++)
		    mvwaddch(grwin_lq,
			GRWIN_HEIGHT - 1 - i, GRWIN_LENGTH - 1, ACS_BLOCK);

	    wnoutrefresh(gr_mode == 0 ? grwin_rssi :
			 gr_mode == 1 ? grwin_rssi_avg :
			 gr_mode == 2 ? grwin_rssi_rou : grwin_lq);

	    doupdate();

	    linked = 1;
	}

	i = wait_key(poll_delay);
	switch(i) {
	    case 'p':
	    case 'P':
		getch();
		break;
	    case 'q':
	    case 'Q':
		goto exit;
	    case 't':
	    case 'T':
		gr_mode++;
		if (gr_mode == (ap_type == ATMEL410 ? 4 : 3))
		    gr_mode = 0;

		wmove(main_sub, 9, 6);
		print_bold(main_sub, gr_modes[gr_mode]);
		for (i = 0; i < 10; i++)
		    waddch(main_sub, ACS_BSBS);
	}
	/* either timeout for user input (i == 0) or invalid key => cont. */
    }

exit:
    wait_mode = p_wait_mode;

    delwin(gauge_rssi);
    delwin(gauge_rssi_top);
    delwin(gauge_rssi_avg);
    delwin(gauge_rssi_rou);

    delwin(grwin_rssi);
    delwin(grwin_rssi_avg);
    delwin(grwin_rssi_rou);
    if (ap_type == ATMEL410)
	delwin(grwin_lq);

    print_top(NULL, NULL);
    clear_main(0);
}
