/*****************************************************************************/

/*
 *	xfsmdiag.c  -- kernel soundcard radio modem driver diagnostics utility.
 *
 *	Copyright (C) 1996  Thomas Sailer (sailer@ife.ee.ethz.ch)
 *
 *	This program 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.
 *
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Please note that the GPL allows you to use the driver, NOT the radio.
 *  In order to use the radio, you need a license from the communications
 *  authority of your country.
 *
 *
 * History:
 *   0.1  14.12.96  Started
 *   0.2  11.05.97  introduced hdrvcomm.h
 */

/*****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <linux/if.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <forms.h>
#include "hdrvcomm.h"
#include "xfsmdiag.h"

/* ---------------------------------------------------------------------- */

static char *progname;
static Pixmap pixmap;
static unsigned long col_zeroline;
static unsigned long col_background;
static unsigned long col_trace;
static GC gr_context;
static int xmul;
static int drawmode = SM_DIAGMODE_OFF;
static unsigned int drawflags = 0;
static FD_scope *fd_scope;

/* ---------------------------------------------------------------------- */

#define WIDTH 512
#define HEIGHT (drawmode == SM_DIAGMODE_CONSTELLATION ? 512 : 256)
#define MAXHEIGHT 512

/* ---------------------------------------------------------------------- */


#define XCOORD(x) ((x) * xm)
#define YCOORD(y) ((SHRT_MAX - (int)(y)) * winattrs.height / USHRT_MAX)

static void drawdata(FL_OBJECT *scdisp, short *data, int len, int replace, int xm)
{
	int cnt;
        GC gc;
        XGCValues gcv;
        XWindowAttributes winattrs;	

        XGetWindowAttributes(fl_get_display(), fl_get_canvas_id(scdisp), &winattrs);
	gcv.line_width = 1;
        gcv.line_style = LineSolid;
        gc = XCreateGC(fl_get_display(), pixmap, GCLineWidth | GCLineStyle, &gcv);
        XSetState(fl_get_display(), gc, col_background, col_background, GXcopy, 
		  AllPlanes);
	if (replace)
		XFillRectangle(fl_get_display(), pixmap, gc, 0, 0, 
			       winattrs.width, winattrs.height);
#if 0
	else
		XCopyArea(fl_get_display(), fl_get_canvas_id(scdisp), pixmap, gr_context, 0, 0, 
			  winattrs.width, winattrs.height, 0, 0);
#endif
        XSetForeground(fl_get_display(), gc, col_trace);
	for (cnt = 0; cnt < len-1; cnt++)
		XDrawLine(fl_get_display(), pixmap, gc, XCOORD(cnt), YCOORD(data[cnt]),
			  XCOORD(cnt+1), YCOORD(data[cnt+1]));
        XSetForeground(fl_get_display(), gc, col_zeroline);
	XDrawLine(fl_get_display(), pixmap, gc, 0, YCOORD(0), winattrs.width,
		  YCOORD(0));
	XCopyArea(fl_get_display(), pixmap, fl_get_canvas_id(scdisp), gr_context, 0, 0, 
		  winattrs.width, winattrs.height, 0, 0);
        XFreeGC(fl_get_display(), gc);
	XSync(fl_get_display(), 0);
}

#undef XCOORD
#undef YCOORD

/* ---------------------------------------------------------------------- */

#define XCOORD(x) ((SHRT_MAX - (int)(x)) * winattrs.width / USHRT_MAX)
#define YCOORD(y) ((SHRT_MAX - (int)(y)) * winattrs.height / USHRT_MAX)

static void drawconstell(FL_OBJECT *scdisp, short *data, int len)
{
	int cnt;
        GC gc;
        XGCValues gcv;
        XWindowAttributes winattrs;	

        XGetWindowAttributes(fl_get_display(), fl_get_canvas_id(scdisp), &winattrs);
	gcv.line_width = 1;
        gcv.line_style = LineSolid;
	gcv.fill_style = FillSolid;
        gc = XCreateGC(fl_get_display(), pixmap, GCLineWidth | GCLineStyle | GCFillStyle, &gcv);
        XSetState(fl_get_display(), gc, col_background, col_background, GXcopy, 
		  AllPlanes);
#if 0
	XCopyArea(fl_get_display(), fl_get_canvas_id(scdisp), pixmap, gr_context, 0, 0, 
		  winattrs.width, winattrs.height, 0, 0);
#endif
        XSetForeground(fl_get_display(), gc, col_trace);
	for (cnt = 0; cnt < len-1; cnt += 2)
		XDrawPoint(fl_get_display(), pixmap, gc, 
			   XCOORD(data[cnt]), YCOORD(data[cnt+1]));
        XSetForeground(fl_get_display(), gc, col_zeroline);
	XDrawLine(fl_get_display(), pixmap, gc, 0, YCOORD(0), winattrs.width, YCOORD(0));
	XDrawLine(fl_get_display(), pixmap, gc, XCOORD(0), 0, XCOORD(0), winattrs.height);
	XCopyArea(fl_get_display(), pixmap, fl_get_canvas_id(scdisp), gr_context, 0, 0, winattrs.width,
                  winattrs.height, 0, 0);
        XFreeGC(fl_get_display(), gc);
	XSync(fl_get_display(), 0);
}

#undef XCOORD
#undef YCOORD

/* ---------------------------------------------------------------------- */

static void clearwindow(FL_OBJECT *scdisp)
{
        XWindowAttributes winattrs;	
	GC gc;
        XGCValues gcv;

        XGetWindowAttributes(fl_get_display(), fl_get_canvas_id(scdisp), &winattrs);
	gcv.line_width = 1;
        gcv.line_style = LineSolid;
	gcv.fill_style = FillSolid;
        gc = XCreateGC(fl_get_display(), pixmap, GCLineWidth | GCLineStyle | GCFillStyle, &gcv);
        XSetState(fl_get_display(), gc, col_background, col_background, GXcopy, 
		  AllPlanes);
	XFillRectangle(fl_get_display(), pixmap, gc, 0, 0, 
		       winattrs.width, winattrs.height);
        XSetForeground(fl_get_display(), gc, col_zeroline);
        XClearArea(fl_get_display(), fl_get_canvas_id(scdisp), 0, 0, 0, 0, False);
	XCopyArea(fl_get_display(), pixmap, fl_get_canvas_id(scdisp), gr_context, 0, 0, 
		  winattrs.width, winattrs.height, 0, 0);
        XFreeGC(fl_get_display(), gc);	
}

/* ---------------------------------------------------------------------- */

void cb_cleargr(FL_OBJECT *ob, long data)
{
	XWindowAttributes winattrs;	
	GC gc;
        XGCValues gcv;

	if (drawmode == HDRVC_DIAGMODE_OFF)
		return;
	clearwindow(fd_scope->scdisp);
        XGetWindowAttributes(fl_get_display(), fl_get_canvas_id(fd_scope->scdisp), &winattrs);
	gcv.line_width = 1;
        gcv.line_style = LineSolid;
	gcv.fill_style = FillSolid;
        gc = XCreateGC(fl_get_display(), pixmap, GCLineWidth | GCLineStyle | GCFillStyle, &gcv);
        XSetState(fl_get_display(), gc, col_background, col_background, GXcopy, AllPlanes);
	XFillRectangle(fl_get_display(), pixmap, gc, 0, 0, winattrs.width, winattrs.height);
}

/* ---------------------------------------------------------------------- */

int scdisp_expose(FL_OBJECT *ob, Window win, int w, int h, XEvent *ev, void *d)
{
	XWindowAttributes winattrs;	
	GC gc;
        XGCValues gcv;

	if (drawmode == HDRVC_DIAGMODE_OFF)
		return 0;
        XGetWindowAttributes(fl_get_display(), fl_get_canvas_id(fd_scope->scdisp), &winattrs);
	gcv.line_width = 1;
        gcv.line_style = LineSolid;
	gcv.fill_style = FillSolid;
        gc = XCreateGC(fl_get_display(), pixmap, GCLineWidth | GCLineStyle | GCFillStyle, &gcv);
        XSetState(fl_get_display(), gc, col_background, col_background, GXcopy, AllPlanes);
	XCopyArea(fl_get_display(), pixmap, fl_get_canvas_id(fd_scope->scdisp), gr_context, 
		  0, 0, winattrs.width, winattrs.height, 0, 0);
	return 0;
}

/* ---------------------------------------------------------------------- */

void cb_mode(FL_OBJECT *ob, long dat)
{
	struct sm_diag_data diag;
	short data;

	diag.mode = HDRVC_DIAGMODE_OFF;
	diag.flags = 0;
	diag.datalen = 1;
	diag.data = &data;
	hdrvc_diag(&diag);
	if (ob == fd_scope->sm_dcd) {
		drawflags ^= HDRVC_DIAGFLAG_DCDGATE;
		return;
	}
	if (ob == fd_scope->sm_off) {
		drawmode = HDRVC_DIAGMODE_OFF;
		fl_hide_object(fd_scope->scdisp);
		return;
	}
	if (ob == fd_scope->sm_input) {
		drawmode = HDRVC_DIAGMODE_INPUT;
		drawflags = 0;
	} else if (ob == fd_scope->sm_demod) {
		drawmode = HDRVC_DIAGMODE_DEMOD;
		drawflags = SM_DIAGFLAG_DCDGATE;
	} else if (ob == fd_scope->sm_constell) {
		drawmode = HDRVC_DIAGMODE_CONSTELLATION;
		drawflags = HDRVC_DIAGFLAG_DCDGATE;
	}
	fl_set_button(fd_scope->sm_dcd, !!(drawflags & HDRVC_DIAGFLAG_DCDGATE));
	fl_set_object_size(fd_scope->scdisp, WIDTH, HEIGHT);
	fl_show_object(fd_scope->scdisp);
	clearwindow(fd_scope->scdisp);
}

/* ---------------------------------------------------------------------- */

void cb_quit(FL_OBJECT *ob, long dat)
{
	struct sm_diag_data diag;
	short data;
	
	diag.mode = SM_DIAGMODE_OFF;
	diag.flags = 0;
	diag.datalen = 1;
	diag.data = &data;
	hdrvc_diag(&diag);
	exit(0);
}

/* ---------------------------------------------------------------------- */

void cb_timer(FL_OBJECT *ob, long dat)
{
	struct hdrvc_channel_state cs;
	int ret;
	short data[256];
	unsigned int samplesperbit;

	fl_set_timer(fd_scope->timer, 0.2);
	fl_freeze_form(fd_scope->scope);
	/*
	 * display state
	 */
	ret = hdrvc_get_channel_state(&cs);
	if (ret < 0) {
		perror("hdrvc_get_channel_state");
	} else {
		fl_set_button(fd_scope->st_ptt, cs.ptt);
		fl_set_button(fd_scope->st_dcd, cs.dcd);
	}
	/*
	 * draw scope
	 */

	if ((ret = hdrvc_diag2(drawmode, drawflags, data, sizeof(data) / sizeof(short), 
			       &samplesperbit)) < 0) {
		perror("hdrvc_diag2");
		exit(1);
	}
	if (ret > 0) {
		xmul = WIDTH / (2*(samplesperbit > 0 ? samplesperbit : 1));
		if (drawmode == SM_DIAGMODE_CONSTELLATION)
			drawconstell(fd_scope->scdisp, data, ret);
		else
			drawdata(fd_scope->scdisp, data, ret, drawmode == SM_DIAGMODE_INPUT,
				 drawmode == SM_DIAGMODE_INPUT ? 5:xmul);
	}
	fl_unfreeze_form(fd_scope->scope);
}

/* ---------------------------------------------------------------------- */

static const char *usage_str = 
"[-i smif]\n"
"  -i: specify the name of the baycom kernel driver interface\n\n";

/* ---------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
	int ret;
	unsigned int ifflags;
	char name[64];

	progname = *argv;
	printf("%s: Version 0.2; (C) 1996-1997 by Thomas Sailer HB9JNX/AE4WA\n", *argv);

	fl_initialize(&argc, argv, 0, 0, 0);
	hdrvc_args(&argc, argv, "sm0");
	while ((ret = getopt(argc, argv, "")) != -1) {
		switch (ret) {
		default:
			printf("usage: %s %s", *argv, usage_str);
			exit(-1);
		}
	}
	hdrvc_init();
	ifflags = hdrvc_get_ifflags();
	if (!(ifflags & IFF_UP)) {
		fprintf(stderr, "interface %s down\n", hdrvc_ifname());
		exit(1);
	}
	if (!(ifflags & IFF_RUNNING)) {
		fprintf(stderr, "interface %s not running\n", hdrvc_ifname());
		exit(1);
	}
	fd_scope = create_form_scope();
	gr_context = XCreateGC(fl_get_display(), 
			       fl_state[fl_vmode].trailblazer, 0, 0);
	col_zeroline = fl_get_flcolor(FL_RED);
	col_background = fl_get_flcolor(FL_WHITE);
	col_trace = fl_get_flcolor(FL_BLACK);
	fl_hide_object(fd_scope->scdisp);
	/*
	 * set driver and modem name
	 */
	ret = hdrvc_get_mode_name(name, sizeof(name));
	if (ret < 0) {
		perror("hdrvc_get_mode_name");
		fl_hide_object(fd_scope->modename);
	} else 
		fl_set_object_label(fd_scope->modename, name);
	ret = hdrvc_get_driver_name(name, sizeof(name));
	if (ret < 0) {
		perror("hdrvc_get_driver_name");
		fl_hide_object(fd_scope->drivername);
	} else 
		fl_set_object_label(fd_scope->drivername, name);
	fl_show_form(fd_scope->scope, FL_PLACE_CENTER, FL_FULLBORDER, 
		     "SoundCard Modem Driver Diagnose Tool");
	if (!(pixmap = XCreatePixmap(fl_get_display(), 
				     fl_get_canvas_id(fd_scope->scdisp), WIDTH, MAXHEIGHT,
				     fl_get_canvas_depth(fd_scope->scdisp)))) {
		fprintf(stderr, "%s: unable to open offscreen pixmap\n", progname);
		exit(1);
	}
	fl_add_canvas_handler(fd_scope->scdisp, Expose, scdisp_expose, NULL);
	fl_set_timer(fd_scope->timer, 0.1);
	for (;;)
		fl_do_forms();
	cb_quit(fd_scope->quit, 0);
	exit(0);
}
