/*
 *   Copyright 1992, 1993, 1994 John Melton (G0ORX/N6LYT)
 *              All Rights Reserved
 *
 *   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 1, 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.
 *
 */

/*
	xtlm.c

	telemery decoding program for PACSATs
	
	 -           -      -

	This software was written to provide a receive only capability
	for collecting files and directories from satellites running
	the Pacsat Protocols running on the Linux Operating System.

	This program has been run using the 1.0 version of the
	Linux kernel with the patches from Alan Cox to provide AX.25
	encapsulation of SLIP.

	The TNC must be setup for KISS.

	John Melton
	G0ORX, N6LYT

	4 Charlwoods Close
	Copthorne
	West Sussex
	RH10 3QZ
	England

	INTERNET:	g0orx@amsat.org
			n6lyt@amsat.org
			john@images.demon.co.uk
			J.D.Melton@slh0613.icl.wins.co.uk

	History:
	-------

	0.1	Initial version				G4KLX
*/

#define VERSION_STRING "(version 0.1 by g4klx)"

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Viewport.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <linux/ax25.h>

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>

#include "crc.h"

Display *dpy;

XtAppContext app_context;

typedef struct
{
	XFontStruct *button_font, *text_font;
}
Resources;

Resources  resources;

Widget toplevel, compwindow, quitbutton, lognextbutton, button[10], viewport,
	pagelabel, timelabel, tlmlabel, crclabel, form, label[23];

XtResource resource_list[] =
{
	{"buttonFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, button_font), XtRString, XtDefaultFont},
	{"textFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, text_font), XtRString, XtDefaultFont}
};

Arg shell_args[] =
{
	{XtNtitle,		(XtArgVal)NULL}
};

Arg form_args[] =
{
	{XtNdefaultDistance,	(XtArgVal)0}
};

Arg button_args[] =
{
	{XtNcallback,		(XtArgVal)NULL},
	{XtNlabel,		(XtArgVal)NULL},
	{XtNfromHoriz,		(XtArgVal)NULL},
	{XtNfont,		(XtArgVal)NULL},
	{XtNresize,		(XtArgVal)False},
	{XtNvertDistance,	(XtArgVal)6},
	{XtNhorizDistance,	(XtArgVal)8},
	{XtNtop,		XtChainTop},
	{XtNbottom,		XtChainTop},
	{XtNleft,		XtChainLeft},
	{XtNright,		XtChainLeft}
};

Arg title_args[] =
{
	{XtNfromVert,		(XtArgVal)NULL},
	{XtNfromHoriz,		(XtArgVal)NULL},
	{XtNfont,		(XtArgVal)NULL},
	{XtNwidth,		(XtArgVal)70},
	{XtNvertDistance,	(XtArgVal)0},
	{XtNhorizDistance,	(XtArgVal)5},
	{XtNlabel,		(XtArgVal)""},
	{XtNheight,		(XtArgVal)25},
	{XtNresize,		False},
	{XtNborderWidth,	(XtArgVal)0},
	{XtNjustify,		XtJustifyLeft},
	{XtNtop,		XtChainTop},
	{XtNbottom,		XtChainTop},
	{XtNleft,		XtChainLeft},
	{XtNright,		XtChainRight}
};

Arg label_args[] =
{
	{XtNfromVert,		(XtArgVal)NULL},
	{XtNfont,		(XtArgVal)NULL},
	{XtNvertDistance,	(XtArgVal)6},
	{XtNhorizDistance,	(XtArgVal)8},
	{XtNwidth,		(XtArgVal)550},
	{XtNlabel,		(XtArgVal)""},
	{XtNborderWidth,	(XtArgVal)0},
	{XtNheight,		(XtArgVal)15},
	{XtNresize,		False},
	{XtNjustify,		XtJustifyLeft},
	{XtNtop,		XtChainTop},
	{XtNbottom,		XtChainTop},
	{XtNleft,		XtChainLeft},
	{XtNright,		XtChainLeft}
};

Arg viewport_args[] =
{
	{XtNfromVert,		(XtArgVal)NULL},
	{XtNwidth,		(XtArgVal)600},
	{XtNheight,		(XtArgVal)300},
	{XtNallowVert,		True},
	{XtNforceBars,		True},
	{XtNresize,		True},
	{XtNvertDistance,	(XtArgVal)0},
	{XtNhorizDistance,	(XtArgVal)0},
	{XtNtop,		XtChainTop},
	{XtNbottom,		XtChainBottom},
	{XtNleft,		XtChainLeft},
	{XtNright,		XtChainRight}
};

#define	PID_TEXT	0xF0
#define	MAXBUFFER	300

#define	ONLINE_MODE	0
#define	FILE_MODE	1

int mode = ONLINE_MODE;

char satelliteId[16];

int current_page = 0;
int tlmBytes     = 0;
int crcErrors    = 0;
time_t timestamp = 0;

int s_raw = 0;

FILE *fpraw = NULL;

struct equation
{
	struct equation *next;		/* A D S */
	char type;			/* A D S */
	int  channel;			/* A D S */
	char *title;			/* A D S */
	int  value;			/* A D S */
	int  page, row, column;		/* A D S */
	int  occurence;			/* A     */
	double a, b, c;			/* A     */
	int  status;			/*   D   */
	char *state0, *state1;		/*     S */
};

struct equation *first = NULL;

/*
 *	Convert a call from the shifted ascii form used in an
 *	AX.25 packet.
 */
int ConvertCall(char *c, char *call)
{
	char *ep = c + 6;
	int ct = 0;

	while (ct < 6)
	{
		if (((*c >> 1) & 127) == ' ') break;

		*call = (*c >> 1) & 127;
		call++;
		ct++;
		c++;
	}
	
	if ((*ep & 0x1E) != 0)
	{	
		*call = '-';
		call++;
		call += sprintf(call, "%d", (int)(((*ep) >> 1) & 0x0F));
	}

	*call = '\0';
	
	if (*ep & 1) return 0;

	return 1;
}

void UpdateStatus(void)
{
	static int lastPage    = -1;
	static time_t lastTime = -1;
	static int lastTLM     = -1;
	static int lastCRC     = -1;
	char label[40], *s;
	Arg args[1];

	if (lastPage != current_page)
	{
		sprintf(label, "Page %2d", current_page);

		XtSetArg(args[0], XtNlabel, label);
		XtSetValues(pagelabel, args, ONE);

		lastPage = current_page;
	}

	if (lastTime != timestamp)
	{
		s = ctime(&timestamp);
		s[24] = '\0';

		sprintf(label, "Timestamp %s", s);

		XtSetArg(args[0], XtNlabel, label);
		XtSetValues(timelabel, args, ONE);

		lastTime = timestamp;
	}

	if (lastTLM != tlmBytes)
	{
		sprintf(label, "TLM bytes %5d", tlmBytes);

		XtSetArg(args[0], XtNlabel, label);
		XtSetValues(tlmlabel, args, ONE);

		lastTLM = tlmBytes;
	}

	if (lastCRC != crcErrors)
	{
		sprintf(label, "CRC errors %3d", crcErrors);

		XtSetArg(args[0], XtNlabel, label);
		XtSetValues(crclabel, args, ONE);

		lastCRC = crcErrors;
	}
}

void DisplayBinary(char *s, int value)
{
	int i;

	*s = '\0';
	
	for (i = 0; i < 12; i++)
	{
		if (value & (0x800 >> i))
			strcat(s, "1");
		else
			strcat(s, "0");
	}
}

void DrawTLMPage(int page)
{
	struct equation *equation;
	char *buffer[23], *title, data[15];
	Arg args[1];
	double result;
	int i, row, column, offset, value;

	current_page = page;

	for (i = 0; i < 23; i++)
	{
		buffer[i] = XtMalloc(85);
		memset(buffer[i], ' ', 84);
		buffer[i][84] = '\0';
	}

	equation = first;

	while (equation != NULL)
	{
		if (equation->page == page)
		{
			title  = equation->title;
			column = equation->column;
			row    = equation->row;
			value  = equation->value;
		
			switch (equation->type)
			{
				case 'A':
					offset = column * 27;
					strncpy(buffer[row] + offset, title, strlen(title));
					if (value != -1)
					{
						result = equation->a * (double)value * (double)value +
							 equation->b * (double)value +
							 equation->c;
						sprintf(data, "%.2f", result);
						strncpy(buffer[row] + offset + 25 - strlen(data), data, strlen(data));
					}
					break;
				case 'D':
					offset = column * 27;
					strncpy(buffer[row] + offset, title, strlen(title));
					if (value != -1)
					{
						DisplayBinary(data, value);
						strncpy(buffer[row] + offset + 25 - strlen(data), data, strlen(data));
					}
					break;
				case 'S':
					offset = column * 42;
					strncpy(buffer[row] + offset, title, strlen(title));
					if (value != -1)
					{
						if (value)
							strncpy(buffer[row] + offset + 30, equation->state1, strlen(equation->state1));
						else
							strncpy(buffer[row] + offset + 30, equation->state0, strlen(equation->state0));
					}
					break;
				default:
					break;
			}
		}

		equation = equation->next;
	}

	for (i = 0; i < 23; i++)
	{
		XtSetArg(args[0], XtNlabel, buffer[i]);
		XtSetValues(label[i], args, ONE);
		XtFree(buffer[i]);
	}
}

void FillStates(int status, int value)
{
	struct equation *equation;
	int i, channel, bit;

	for (i = 0; i < 12; i++)
	{
		channel = status + i;
		bit     = value & (0x800 >> i);

		equation = first;
	
		while (equation != NULL)
		{
			if (equation->channel == channel &&
			    equation->type    == 'S')
				equation->value = (bit != 0);

			equation = equation->next;
		}
	}
}


void FillTLM(int value, int channel, int occurence)
{
	struct equation *equation;
	
	equation = first;
	
	while (equation != NULL)
	{
		if (equation->channel   == channel   &&
		    equation->occurence == occurence &&
		   (equation->type      == 'A'       ||
		    equation->type      == 'D'))
		{
			switch (equation->type)
			{
				case 'A':
					equation->value = value;
					break;
				case 'D':
					equation->value = value;
					FillStates(equation->status, equation->value);
					break;
				default:
					break;
			}
			
			return;
		}
	
		equation = equation->next;
	}
}

void DecodeTLM(unsigned char *buffer, int bufsize)
{
	int i;
	int type, value;
	int channel   = 0;
	int occurence = 0;

	tlmBytes += bufsize;

	if (!CheckCRC(buffer, bufsize))
	{
		crcErrors++;
		UpdateStatus();
		return;
	}

	timestamp = buffer[0] + (buffer[1] << 8) + (buffer[2] << 16) + (buffer[3] << 24);
	buffer  += 4;
	bufsize -= 4;

	UpdateStatus();

	for (i = 0; i < (bufsize - 2); i += 2)
	{
		type  = (buffer[i + 1] & 0xF0) >> 4;
		value = buffer[i] + ((buffer[i + 1] & 0x0F) << 8);

		switch (type)
		{
			case 2:
				channel   = value;
				occurence = 0;
				break;
			case 1:
				FillTLM(value, channel, occurence);
				occurence++;
				break;
			case 0:
				FillTLM(value, channel, occurence);
				occurence = 0;
				channel++;
				break;
			default:
				break;
		}
	}
}

/*
 *	decode a received frame.
 */
void ProcessFrame(unsigned char *buffer, int bufsize)
{
	int n;
	int via;
	unsigned char protocol;
	char toCall[10];
	char fromCall[10];
	char viaCall[10];

	/* check that frame is a kiss data frame */
	/* ignore control frames - should not happen */
	n = 0;

	if ((buffer[n] & 0x0F) == 0)
	{
		n++;

		/* decode the to/from address */
		/* dont expect via address, but saves last if any */
		via = ConvertCall(buffer + n, toCall);
		n += 7;

		via = ConvertCall(buffer + n, fromCall);
		n += 7;

		while (via)
		{
			via = ConvertCall(buffer + n, viaCall);
			n += 7;
		}

		/* check for a UI frame */
		if ((buffer[n] & 0xEF) == 0003)
		{
			n++;
			protocol = buffer[n++];

			/* see if the frame is a broadcast frame */	
			if (protocol == PID_TEXT && strcmp(toCall, "TLM") == 0)
			{
				DecodeTLM(buffer + n, bufsize - n);
				DrawTLMPage(current_page);
				if (fpraw != NULL)
				{
					fputc((bufsize - n) % 256, fpraw);
					fputc((bufsize - n) / 256, fpraw);
					fwrite(buffer + n, 1, bufsize - n, fpraw);
				}
			}
		}
	}
}

/*
 *	callback function when a frame is received.
 */
void GetFrame(XtPointer closure, int *s, XtInputId *Id)
{
	unsigned char buffer[MAXBUFFER];
	int bufsize;

	if ((bufsize = recv(s_raw, buffer, MAXBUFFER, 0)) == -1)
	{
		perror("recv");
		return;
	}

	ProcessFrame(buffer, bufsize);
}

/*
 *	the user wants to exit this program
 */
void QuitCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	if (fpraw != NULL) fclose(fpraw);
	if (s_raw != 0)    close(s_raw);

	XtDestroyApplicationContext(app_context);

	exit(0);
}

void LogNextCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	unsigned char buffer[300];
	int bufsize;
	Arg args[1];
	time_t t;

	if (mode == ONLINE_MODE)
	{
		if (fpraw == NULL)
		{
			time(&t);
			
			sprintf(buffer, "%x.tlm", (int)t);
			
			if ((fpraw = fopen(buffer, "w")) == NULL)
				return;

			XtSetArg(args[0], XtNlabel, "TLM Log On");
		}
		else
		{
			fclose(fpraw);
			fpraw = NULL;
			XtSetArg(args[0], XtNlabel, "TLM Log Off");
		}

		XtSetValues(lognextbutton, args, ONE);
	}
	else
	{
		if (!feof(fpraw))
		{
			bufsize = fgetc(fpraw);
			bufsize += fgetc(fpraw) * 256;

			fread(buffer, 1, bufsize, fpraw);
		
			DecodeTLM(buffer, bufsize);
			DrawTLMPage(current_page);
		}
	}
}

void PageCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	int page = (int)client_data;

	if (page != current_page)
	{
		DrawTLMPage(page);
		UpdateStatus();
	}
}

void InsertChannel(struct equation *equation)
{
	struct equation *current;
	
	current = (struct equation *)XtMalloc(sizeof(struct equation));

	*current = *equation;

	if (first == NULL)
	{
		first = current;
	}
	else
	{
		equation = first;

		while (equation->next != NULL)
			equation = equation->next;

		equation->next = current;
	}
}

int ReadConfig(void)
{
	char buffer[100];
	char title[40], buf1[15], buf2[15];
	FILE *fp;
	struct equation current;
	int page = 0, row = 0, column = 0;
	int status = 0;
	int count, i;

	if ((fp = fopen("tlm.cfg", "r")) == NULL)
	{
		fprintf(stderr, "Cannot open tlm.cfg\n");
		return(-1);
	}

	while (fgets(buffer, 100, fp) != NULL)
	{
		switch (*buffer)
		{
			case 'A':
				sscanf(buffer, "%*c %d , %[^,], %lf , %lf , %lf , %d ,",
					&current.channel, title, &current.a,
					&current.b, &current.c, &count);
				for (i = 0; i < count; i++)
				{
					current.next      = NULL;
					current.type      = *buffer;
					current.title     = XtNewString(title);
					current.occurence = i;
					current.page      = page;
					current.row       = row;
					current.column    = column;
					current.value     = -1;
					InsertChannel(&current);
					column++;
					if (column > 2)
					{
						column = 0;
						row++;
					}
					if (row > 22)
					{
						column = 0;
						row    = 0;
						page++;
					}
				}
				break;
			case 'D':
				sscanf(buffer, "%*c %d , %[^,], ",
					&current.channel, title);
				current.next      = NULL;
				current.type      = *buffer;
				current.status    = status;
				current.occurence = 0;
				current.title     = XtNewString(title);
				current.page      = page;
				current.row       = row;
				current.column    = column;
				current.value     = -1;
				InsertChannel(&current);
				status += 12;
				column++;
				if (column > 2)
				{
					column = 0;
					row++;
				}
				if (row > 22)
				{
					column = 0;
					row    = 0;
					page++;
				}
				break;
			case 'S':
				if (current.type != 'S')
				{
					page++;
					row    = 0;
					column = 0;
				}
				sscanf(buffer, "%*c %d , %49[^,], %14[^, ] , %14[^, \n]",
					&current.channel, title, buf1, buf2);
				current.next      = NULL;
				current.type      = *buffer;
				current.occurence = 0;
				current.title     = XtNewString(title);
				current.state0    = XtNewString(buf1);
				current.state1    = XtNewString(buf2);
				current.page      = page;
				current.row       = row;
				current.column    = column;
				current.value     = -1;
				InsertChannel(&current);
				column++;
				if (column > 1)
				{
					column = 0;
					row++;
				}
				if (row > 22)
				{
					column = 0;
					row    = 0;
					page++;
				}
				break;
			case '+':
				column += atoi(buffer + 1);
				while (column > 2)
				{
					column -= 3;
					row++;
				}
				if (row > 22)
				{
					column = 0;
					row    = 0;
					page++;
				}
				break;
			default:
				break;
		}
	}
	
	fclose(fp);

	return(page + 1);
}

int main(int argc, char **argv)
{
	static XtCallbackRec callback[2];
	int i, pages;
	char *s, title[80], name[10];

	if ((s = getenv("SATELLITE")) == NULL)
	{
		printf("SATELLITE environment variable not set.\n");
		return(1);
	}

	strcpy(satelliteId, s);

	if ((s = getenv("XTLM")) != NULL)
	{
		if ((fpraw = fopen(s, "r")) == NULL)
		{
			printf("Cannot open file %s\n", s);
			return(1);
		}

		mode = FILE_MODE;
	}

	sprintf(title, "xtlm:%s %s", satelliteId, VERSION_STRING);

	strcat(satelliteId, "-0");

	if ((pages = ReadConfig()) == -1) return(1);

	toplevel = XtAppInitialize(&app_context, "Xpb", NULL, 0, &argc, argv,
				NULL, shell_args, XtNumber(shell_args));
	XtVaSetValues(toplevel, XtNtitle, title, NULL);

	dpy  = XtDisplay(toplevel);

	XtGetApplicationResources(toplevel, &resources,
				resource_list, XtNumber(resource_list),
				NULL, ZERO);

	compwindow = XtCreateManagedWidget("appForm", formWidgetClass,
				toplevel, form_args, XtNumber(form_args));

	callback[0].callback = QuitCb;
	callback[0].closure  = toplevel;
	button_args[0].value = (XtArgVal)callback;
	button_args[1].value = (XtArgVal)"Quit";
	button_args[3].value = (XtArgVal)resources.button_font;
	quitbutton = XtCreateManagedWidget("quitButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	callback[0].callback = LogNextCb;
	callback[0].closure  = toplevel;
	button_args[0].value = (XtArgVal)callback;
	if (mode == ONLINE_MODE)
		button_args[1].value = (XtArgVal)"TLM Log Off";
	else
		button_args[1].value = (XtArgVal)"Next";
	button_args[2].value = (XtArgVal)quitbutton;
	button_args[3].value = (XtArgVal)resources.button_font;
	lognextbutton = XtCreateManagedWidget("lognextButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	for (i = 0; i < pages; i++)
	{
		sprintf(title, "Page %d", i);
		sprintf(name,  "page%dButton",  i);
	
		callback[0].callback = PageCb;
		callback[0].closure  = (XtPointer)i;
		button_args[0].value = (XtArgVal)callback;
		button_args[1].value = (XtArgVal)title;
		if (i == 0)
			button_args[2].value = (XtArgVal)lognextbutton;
		else
			button_args[2].value = (XtArgVal)button[i - 1];
		button[i] = XtCreateManagedWidget(name, commandWidgetClass,
					compwindow, button_args, XtNumber(button_args));
	}

	title_args[0].value = (XtArgVal)quitbutton;
	title_args[2].value = (XtArgVal)resources.button_font;
	pagelabel = XtCreateManagedWidget("pageLabel", labelWidgetClass,
				compwindow, title_args, XtNumber(title_args));

	title_args[1].value = (XtArgVal)pagelabel;
	title_args[3].value = (XtArgVal)250;
	timelabel = XtCreateManagedWidget("timeLabel", labelWidgetClass,
				compwindow, title_args, XtNumber(title_args));

	title_args[1].value = (XtArgVal)timelabel;
	title_args[3].value = (XtArgVal)130;
	tlmlabel = XtCreateManagedWidget("tlmLabel", labelWidgetClass,
				compwindow, title_args, XtNumber(title_args));

	title_args[1].value = (XtArgVal)tlmlabel;
	crclabel = XtCreateManagedWidget("crcLabel", labelWidgetClass,
				compwindow, title_args, XtNumber(title_args));

	viewport_args[0].value = (XtArgVal)pagelabel;
	viewport = XtCreateManagedWidget("tlmViewport", viewportWidgetClass,
				compwindow, viewport_args, XtNumber(viewport_args));

	form = XtCreateManagedWidget("tlmForm", formWidgetClass,
				viewport, form_args, XtNumber(form_args));

	for (i = 0; i < 23; i++)
	{
		sprintf(name,  "line%dLabel",  i);

		if (i > 0)
			label_args[0].value = (XtArgVal)label[i - 1];
		else
			label_args[0].value = (XtArgVal)NULL;
		label_args[1].value = (XtArgVal)resources.text_font;
		label_args[4].value = (XtArgVal)600;
		label[i] = XtCreateManagedWidget(name, labelWidgetClass,
				form, label_args, XtNumber(label_args));
	}

	DrawTLMPage(0);
	UpdateStatus();

	if (mode == ONLINE_MODE)
	{
		/* open up the raw socket to receive all packets */
		if ((s_raw = socket(AF_INET, SOCK_PACKET, htons(2))) == -1)
		{
			perror("socket");
			return(1);
		}

		/* we want to be notified whenever a frame is received on the raw socket */
		XtAppAddInput(app_context, s_raw, (XtPointer)XtInputReadMask, GetFrame, NULL);
	}

	XtRealizeWidget(toplevel);

	XtAppMainLoop(app_context);
	
	return(0);
}

