/*
 * $Id$
 * 
 * wmwave.c -- WindowMaker IEEE802.11 status dock app
 *             based on wmtop.c from Dan Piponi
 * 
 * This software is licensed through the GNU General Public License.
 * 
 * Authors (in reverse chronological order):
 * Hendrik Scholz <hscholz@raisdorf.net>
 * Bruce M. Simpson <bms@spc.org>
 * Carsten Schuermann <carsten@schuermann.org>
 * Dan Piponi <dan@tanelorn.demon.co.uk>
 * Dave Clark <clarkd@skynet.ca>
 * 
 * The FreeBSD version of this software is released under the GNU GPL, and
 * forms part of the Consume Project <URL: http://www.consume.net/>.
 * 
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include <sysexits.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <netdb.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/route.h>
#include <net/ethernet.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <arpa/inet.h>

#include <dev/an/if_aironet_ieee.h>

#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>

#include "wmgeneral.h"
#include "wmwave-master.xpm"

char            wmwave_mask_bits[64 * 64];
int             wmwave_mask_width = 64;
int             wmwave_mask_height = 64;

#define WMWAVE_DEFAULT_INTERFACE	"an0"
#define WMWAVE_VERSION			"0.4_FreeBSD-2"

int             update_rate = 100000;
char            *ProgName;
char		*iface = WMWAVE_DEFAULT_INTERFACE;
time_t          curtime;
time_t          prevtime;
int             mode = 0;	/* default: no card detected */
int             screen = 0;	/* default: Quality screen is displayed */

void            usage(void);
void            printversion(void);
void            BlitString(char *name, int x, int y);
void            BlitNum(int num, int x, int y);
void            BlitChan(int num, int x, int y);
void            wmwave_routine(int, char **);
void            DrawBar(float percent, int dx, int dy);
void            DrawGreenBar(float percent, int dx, int dy);

inline void
DrawBar(float percent, int dx, int dy)
{
	int             tx;

	tx = (float)((float)54 * ((float)percent / (float)100.0));
	copyXPMArea(67, 36, tx, 4, dx, dy);
	copyXPMArea(67, 43, 54 - tx, 4, dx + tx, dy);
}


inline void
DrawGreenBar(float percent, int dx, int dy)
{
	int             tx;

	tx = (float)((float)54 * ((float)percent / (float)100.0));
	copyXPMArea(67, 58, tx, 4, dx, dy);
	copyXPMArea(67, 43, 54 - tx, 4, dx + tx, dy);
}

inline void
DrawRedDot()
{
	copyXPMArea(80, 65, 6, 6, 52, 5);
}

inline void
DrawYellowDot()
{
	copyXPMArea(86, 65, 6, 6, 52, 5);
}

inline void
DrawGreenDot()
{
	copyXPMArea(92, 65, 6, 6, 52, 5);
}

inline void
DrawEmptyDot()
{
	copyXPMArea(98, 65, 6, 6, 52, 5);
}

/*
 * XXX: redefining min() to operate on floats is a bad idea;
 * changed to _fmin().
 */
float
_fmin(float x, float y)
{
	if (x < y) {
		return x;
	} else {
		return y;
	}
}

/*
 * XXX: Fetch OS-specific wireless statistics.
 * 
 * These are: quality, signal, noise. On NetBSD, the 
 * statistics kept on an AP-basis for the driver are valid.
 * On FreeBSD, it is necessary to interrogate the WICACHE.
 * For the purposes of keeping things simple, this code will only
 * look at the first slot in the WICACHE table.
 */
void
DisplayWireless(void)
{
        struct ifreq            ifr;
        struct an_req           anreq;
        struct an_sigcache	ansigs;
        struct an_sigcache      *ansigsp;
	struct an_ltv_status    *sts;
        int                     *ansigsnp, s, mode, err;
	int  	                chan, link, level, noise;
#ifdef SNR
	float			snr;
#endif	/* SNR */
	enum {
		MODE_NO_CARD = 0,
		MODE_HAVE_CARD = 1
	};

    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s == -1) 
    	errx(errno, "socket");
    bzero(&ifr, sizeof(ifr));
    strncpy(ifr.ifr_name, iface, strlen(iface));
    bzero(&anreq, sizeof(anreq));
    anreq.an_len = sizeof(anreq);
    anreq.an_type = AN_RID_STATUS;
                                    
    ifr.ifr_data = (void *) &anreq;

    err = ioctl(s, SIOCGAIRONET, (caddr_t)&ifr);

    sts = (struct an_ltv_status *)&anreq;
        
    if (err < 0) {
		mode = MODE_NO_CARD;
		goto draw;
    }

	mode  = MODE_HAVE_CARD;
	chan  = sts->an_cur_channel;
	link  = ((sts->an_cur_signal_quality * 1.76) - 100) * -1;
	level = sts->an_normalized_strength * 1.76;
	noise = sts->an_noise_prev_sec_pc;
#ifdef SNR
	snr   = level / noise;
#endif	/* SNR */	

#ifdef DEBUG
#ifdef SNR
	printf("chan = %d, sts->an_cur_signal_quality= %d, link = %d, sts->an_normalized_strength = %d, level = %d, noise = %d, snr = %f\n", chan, sts->an_cur_signal_quality, link, sts->an_normalized_strength,level, noise, snr);
#else
	printf("chan = %d, sts->an_cur_signal_quality= %d, link = %d, sts->an_normalized_strength = %d, level = %d, noise = %d\n", chan, sts->an_cur_signal_quality, link, sts->an_normalized_strength,level, noise);
#endif	/* SNR */	
#endif	/* DEBUG */

draw:
	/*
	 * Print channel information, and signal ratio
	 */
	switch (mode) {
	case MODE_HAVE_CARD:
		BlitChan(chan, 4, 4);
		if (link <= 10) {
			DrawRedDot();
		} else if (link <= 20) {
			DrawYellowDot();
		} else {
			DrawGreenDot();
		};
		BlitString("Quality  ", 4, 18);
		DrawBar(_fmin(link, 100.0), 4, 27);
		BlitString("Strength ", 4, 32);
		DrawBar(_fmin(level, 100.0), 4, 41);
#ifndef SNR
		BlitString("Noise    ", 4, 46);
		DrawGreenBar(_fmin(noise, 100.0), 4, 55);
#else
		BlitString("SNR      ", 4, 46);
		DrawGreenBar(_fmin((int)(snr * 100), 100.0), 4, 55);
#endif	/* SNR */
		break;
	case MODE_NO_CARD:
	default:
		BlitString("NO CARD", 4, 4);
		DrawEmptyDot();
		BlitString("         ", 4, 18);
		DrawBar(0.0, 4, 27);
		BlitString("         ", 4, 32);
		DrawGreenBar(0.0, 4, 41);
		BlitString("         ", 4, 46);
		DrawGreenBar(0.0, 4, 55);
		break;
	};
	close(s);
}

void
sig_chld(int signo)
{
	waitpid((pid_t) - 1, NULL, WNOHANG);
	signal(SIGCHLD, sig_chld);
}

int
main(int argc, char *argv[])
{
	int             i;


	signal(SIGCHLD, sig_chld);

	ProgName = argv[0];
	if (strlen(ProgName) >= 5)
		ProgName += (strlen(ProgName) - 5);

	for (i = 1; i < argc; i++) {
		char           *arg = argv[i];

		if (*arg == '-') {
			switch (arg[1]) {
			case 'i':
				if (argc > (i + 1)) {
					iface = argv[i+1];
					if (strncmp("an", iface, 2) != 0) {
						fprintf(stderr, "error: you "
					"must specify a anX interface.\n");
						usage();
						exit(EX_USAGE);
					}
				}
				break;
			case 'd':
				if (strcmp(arg + 1, "display")) {
					usage();
					exit(EX_USAGE);
				}
				break;
			case 'g':
				if (strcmp(arg + 1, "geometry")) {
					usage();
					exit(EX_USAGE);
				}
				break;
			case 'v':
				printversion();
				exit(EX_OK);
				break;
			case 'r':
				if (argc > (i + 1)) {
					update_rate = (atoi(argv[i+1]) * 1000);
					i++;
				}
				break;
			default:
				usage();
				exit(EX_USAGE);
				break;
			}
		}
	}

	wmwave_routine(argc, argv);

	exit(EX_OK);
}

/*
 * Main loop
 */
void
wmwave_routine(int argc, char **argv)
{
	XEvent          Event;
	struct timeval  tv = {0, 0};
	struct timeval  last = {0, 0};

	createXBMfromXPM(wmwave_mask_bits, wmwave_master_xpm, wmwave_mask_width, wmwave_mask_height);

	openXwindow(argc, argv, wmwave_master_xpm, wmwave_mask_bits, wmwave_mask_width, wmwave_mask_height);

	RedrawWindow();

	for (;;) {
		curtime = time(0);
		memcpy(&last, &tv, sizeof(tv));

		/*
		 * Update display
		 */
		DisplayWireless();
		RedrawWindow();

		/*
		 * X Events
		 */
		while (XPending(display)) {
			XNextEvent(display, &Event);
			switch (Event.type) {
			case Expose:
				RedrawWindow();
				break;
			case DestroyNotify:
				XCloseDisplay(display);
				exit(EX_OK);
			case ButtonPress:
				switch (screen) {
				case 0:
					screen = 1;
					break;
				case 1:
					screen = 0;
					break;
				};
				break;
			}
		}
		usleep(update_rate);
	}
}

/*
 * Blits a string at given co-ordinates
 */
void
BlitString(char *name, int x, int y)
{
	int             i;
	int             c;
	int             k;

	k = x;
	for (i = 0; name[i]; i++) {

		c = toupper(name[i]);
		if (c >= 'A' && c <= 'Z') {	/* its a letter */
			c -= 'A';
			copyXPMArea(c * 6, 74, 6, 8, k, y);
			k += 6;
		} else if (c >= '0' && c <= '9') {
			/* its a number or symbol */
			c -= '0';
			copyXPMArea(c * 6, 64, 6, 8, k, y);
			k += 6;
		} else {
			copyXPMArea(5, 84, 6, 8, k, y);
			k += 6;

		}
	}
}

void
BlitNum(int num, int x, int y)
{
	char            buf[1024];
	int             newx = x;

	sprintf(buf, "%03i", num);
	BlitString(buf, newx, y);
}

void
BlitChan(int num, int x, int y)
{
	char            buf[1024];
	int             newx = x;

	sprintf(buf, "Chan %02i", num);
	BlitString(buf, newx, y);
}

/*
 * Usage
 */
void
usage(void)
{
	fprintf(stderr, "\rwmwave 0.4 - by Bruce M Simpson <bms@spc.org> et al. \n");
	fprintf(stderr, "usage:\n");
	fprintf(stderr, "    -display  <display name>\n");
	fprintf(stderr, "    -i        interface to use (default to an0)\n");
	fprintf(stderr, "    -r        update rate in milliseconds (default:100)\n");
	fprintf(stderr, "\n");
}

/*
 * printversion
 */
void
printversion(void)
{
	fprintf(stderr, "wmwave v%s\n", WMWAVE_VERSION);
}
