/*
 *   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.
 *
 */

/*
	downloaded.c

	List downloaded pacsat files.

	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. 			G0ORX
	0.2	Added Extract.				G0ORX
	0.3	Added view selection			G0ORX
	0.4	Fixed selected entry after update.	G0ORX
	0.5	Converted to Xaw.			G4KLX
	0.6	Added double click for view.		G4KLX
		Added "Logs" to view menu.
		Added viewlogs.
	0.7	Added Order button.			G4KLX
		Added WOD logs to logs viewing.
*/

#define VERSION_STRING "(version 0.7 by g0orx/n6lyt/g4klx)"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>

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

#include "xawutils.h"
#include "header.h"
#include "ftl0.h"

Display *dpy;
int scrn;

XtAppContext app_context;

typedef struct
{
	XFontStruct *bold_font, *button_font, *menu_font, *label_font, *list_font;
}
Resources;

Resources  resources;

Widget toplevel, compwindow, quitbutton, updatebutton, extractbutton,
	replybutton, viewtypebutton, viewtypelabel, orderlabel, orderbutton,
	listlabel, viewport, downloadlist;

static XtResource resource_list[] =
{
	{"boldFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, bold_font), XtRString, XtDefaultFont},
	{"buttonFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, button_font), XtRString, XtDefaultFont},
	{"menuFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, menu_font), XtRString, XtDefaultFont},
	{"labelFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, label_font), XtRString, XtDefaultFont},
	{"listFont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
		XtOffsetOf(Resources, list_font), XtRString, XtDefaultFont}
};

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

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

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

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

static Arg menu_button_args[] =
{
	{XtNlabel,		(XtArgVal)NULL},
	{XtNfont,		(XtArgVal)NULL},
	{XtNfromHoriz,		(XtArgVal)NULL},
	{XtNwidth,		(XtArgVal)80},
	{XtNresize,		False},
	{XtNvertDistance,	(XtArgVal)6},
	{XtNhorizDistance,	(XtArgVal)8},
	{XtNtop,		XtChainTop},
	{XtNbottom,		XtChainTop},
	{XtNleft,		XtChainLeft},
	{XtNright,		XtChainLeft},
	{XtNjustify,		XtJustifyCenter}
};

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

static Arg list_args[] =
{
	{XtNcallback,		(XtArgVal)NULL},
	{XtNfont,		(XtArgVal)NULL}
};

MenuEntry type_menu[] =
{
	{"All",		"type1",	0},
	{"My Mail",	"type2",	1},
	{"Broadcast",	"type3",	2},
	{"Logs",	"type4",	3},
	{"EIS",		"type5",	4}
};

MenuEntry order_menu[] =
{
	{"Date/Time",	"order1",	0},
	{"Id",		"order2",	1},
	{"From",	"order3",	2},
	{"To",		"order4",	3},
	{"Size",	"order5",	4},
	{"Title",	"order6",	5}
};

#define	MAX_DIR_LIST_SIZE	2000

char myCall[16];
char satelliteId[16];

int view  = 0;
int order = 0;

String dList[MAX_DIR_LIST_SIZE + 1];
int    nList = 0;

int lastIndex = 0;

void LoadFile(char * fileName);

void signal_child(int s)
{
	int pid, pstatus;

	signal(SIGCHLD, signal_child);

	pid = wait(&pstatus);
}

int downloadcomp(const void *first, const void *second)
{
	char **a = (char **)first;
	char **b = (char **)second;
	int n;

	switch (order)
	{
		case 0:	/* Date/Time */
			break;
		case 1: /* Id */
			if ((n = strncmp(*b, *a, 8)) != 0)
				return(n);
			break;
		case 2: /* From */
			if ((n = strncasecmp(*b + 19, *a + 19, 8)) != 0)
				return(n);
			break;
		case 3: /* To */
			if ((n = strncasecmp(*b + 28, *a + 28, 8)) != 0)
				return(n);
			break;
		case 4: /* Size */
			if ((n = strncmp(*b + 37, *a + 37, 8)) != 0)
				return(n);
			break;
		case 5: /* Title */
			if ((n = strncasecmp(*b + 44, *a + 44, 40)) != 0)
				return(n);
			break;
	}

	return(strncmp(*b + 9, *a + 9, 9));
}

void Update(void)
{
	DIR		*pDir;
	struct dirent	*pDirent;
	int i;
	Arg args[2];

	for (i = 0; i < nList; i++)
		if (dList[i] != NULL)
			XtFree((char *)dList[i]);

	nList     = 0;
	lastIndex = -1;

	/* walk through the directory of files */
	pDir = opendir(".");

	while ((pDirent = readdir(pDir)) != NULL && nList < MAX_DIR_LIST_SIZE)
	{
		/* see if it is a .dl file */
		if (strstr(pDirent->d_name, ".dl") != NULL)
			LoadFile(pDirent->d_name);
	}

	closedir(pDir);

	dList[nList] = NULL;

	qsort(dList, nList, sizeof(String), downloadcomp);

	XawListChange(downloadlist, dList, nList, 0, False);

	if (nList > 0) XawListHighlight(downloadlist, 0);

	XtSetArg(args[0], XtNheight, 0);
	XtSetArg(args[1], XtNwidth,  0);
	XtSetValues(downloadlist, args, TWO);
}

void UpdateCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	Update();
}

void OrderCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	order = (int)client_data;

	XtVaSetValues(orderbutton, XtNlabel, order_menu[order].Label, NULL);

	qsort(dList, nList, sizeof(String), downloadcomp);

	XawListChange(downloadlist, dList, nList, 0, False);

	if (nList > 0) XawListHighlight(downloadlist, 0);
}

void ViewTypeCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	view = (int)client_data;

	XtVaSetValues(viewtypebutton, XtNlabel, type_menu[view].Label, NULL);

	Update();
}

void LoadFile(char *fileName)
{
	FILE *hFile;
	int nBytes;
	char *pBuffer, *source, *destination;
	static struct tm *GMT;
	char szTemp[128];
	int headerSize;
	HEADER *pHeader;
	int j;
	char *p;
	int display;

	/* open the file */
	if ((hFile = fopen(fileName, "r")) == NULL)
	{
		sprintf(szTemp, "Cannot open file %s", fileName);
		MessageBox(szTemp);
		return;
	}

	/* get the header */
	pBuffer = XtMalloc(1024);

	/* read in the 1K */
	nBytes = fread(pBuffer, 1, 1024, hFile);

	/* close the file */
	fclose(hFile);

	/* extracting the header */
	if ((pHeader = ExtractHeader(pBuffer, nBytes, &headerSize)) == NULL)
	{
		MessageBox("Invalid header entry");
		XtFree(pBuffer);
		return;
	}

	source      = XtNewString(pHeader->source);
	destination = XtNewString(pHeader->destination);

	/* truncate the source and destination */
	j = 0;
	while (isalnum(pHeader->source[j]) != '\0') j++;
	pHeader->source[j] = '\0';

	j = 0;
	while (isalnum(pHeader->destination[j]) != '\0') j++;
	pHeader->destination[j] = '\0';

	GMT = gmtime(&pHeader->uploadTime);

	if (strlen(pHeader->title) == 0)
		sprintf(szTemp, "%8lx %02d%02d/%02d%02d %-8s %-8s %6ld %-40s",
			pHeader->fileId,
			GMT->tm_mon + 1, GMT->tm_mday, GMT->tm_hour, GMT->tm_min,
			pHeader->source,
			pHeader->destination,
			pHeader->fileSize,
			pHeader->fileName);
	else
		sprintf(szTemp, "%8lx %02d%02d/%02d%02d %-8s %-8s %6ld %-40s",
			pHeader->fileId,
			GMT->tm_mon + 1, GMT->tm_mday, GMT->tm_hour, GMT->tm_min,
			pHeader->source,
			pHeader->destination,
			pHeader->fileSize,
			pHeader->title);

	for (p = source; *p != '\0';p++)
		if (islower(*p)) *p = toupper(*p);

	for (p = destination; *p != '\0'; p++)
		if (islower(*p)) *p = toupper(*p);

	display = FALSE;

	switch (view)
	{
		case 0:
			display = TRUE;
			break;
		case 1:
			if (strstr(source, myCall)      != NULL || 
			    strstr(destination, myCall) != NULL)
				display = TRUE;
			break;
		case 2:
			if (strstr(destination, "ALL") != NULL)
				display = TRUE;
			break;
		case 3:
			if (pHeader->fileType >= 100 &&
			    pHeader->fileType != 221)
				display = TRUE;
			break;
		case 4:
			if (pHeader->fileType == 220 ||
			    pHeader->fileType == 221)
			    	display = TRUE;
			break;
		default:
			break;
	}

	if (display)
	{
		dList[nList] = XtNewString(szTemp);
		nList++;
	}
		
	XtFree((char *)pHeader);
	XtFree(source);
	XtFree(destination);	
	XtFree(pBuffer);
}

void ExtractCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	XawListReturnStruct *selected;
	unsigned long FileId;
	char fileName[128];
	FILE *hFile, *hOut;
	char *pBuffer;
	int headerSize;
	HEADER *pHeader;
	char tmp[128];
	int i, nBytes;

	selected = XawListShowCurrent(downloadlist);

	if (selected->list_index == XAW_LIST_NONE)
		return;

	sscanf(selected->string, "%lx", &FileId);
	sprintf(fileName, "%lx.dl", FileId);

	if ((hFile = fopen(fileName, "r")) == NULL)
	{
		MessageBox("Open failed");
		return;
	}

	pBuffer = XtMalloc(1024);

	nBytes = fread(pBuffer, 1, 1024, hFile);

	if ((pHeader = ExtractHeader(pBuffer, nBytes, &headerSize)) == NULL)
	{
		MessageBox("Bad header");
		XtFree(pBuffer);
		fclose(hFile);
		return;
	}

	mkdir("msgs", 0755);

	i = strlen(pHeader->userFileName) - 1;

	while (i > 0)
	{
		if (pHeader->userFileName[i] == '\\')
		{
			i++;
			break;
		}
			
		i--;
	}

	if (i < 0)
		if (strcmp(pHeader->fileExt, "   ") != 0)
			sprintf(fileName, "msgs/%s.%s", pHeader->fileName, pHeader->fileExt);
		else
			sprintf(fileName, "msgs/%s", pHeader->fileName);
	else
		sprintf(fileName, "msgs/%s", pHeader->userFileName + i);

	if ((hOut = fopen(fileName, "w")) == NULL)
	{
		sprintf(tmp, "Cannot create %s", fileName);
		MessageBox(tmp);
		XtFree((char *)pHeader);
		XtFree(pBuffer);
		fclose(hFile);
		return;
	}

	fseek(hFile, pHeader->bodyOffset, SEEK_SET);

	while ((nBytes = fread(pBuffer, 1, 1024, hFile)) > 0)
		fwrite(pBuffer, 1, nBytes, hOut);

	fclose(hFile);
	fclose(hOut);

	sprintf(tmp, "Message extracted to file %s", fileName);
	MessageBox(tmp);

	XtFree((char *)pHeader);
	XtFree(pBuffer);
}

void ViewText(char *fileName)
{
	int pid;
	
	if ((pid = fork()) == 0)
	{
		/* the child process */
		execlp("viewtext", "viewtext", fileName, NULL);
	}
	else if (pid > 0)
	{
		/* success */
	}
	else
	{
		MessageBox("Cannot fork viewer");
	}
}

void ViewImage(char *fileName)
{
	int pid;
	
	if ((pid = fork()) == 0)
	{
		/* the child process */
		execlp("phs", "phs", fileName, "xloadimage", "-quiet", NULL);
	}
	else if (pid > 0)
	{
		/* success */
	}
	else
	{
		MessageBox("Cannot fork viewer");
	}
}

void ViewLog(char *fileName, char *title)
{
	int pid;
	
	if ((pid = fork()) == 0)
	{
		/* the child process */
		execlp("phs", "phs", fileName, "viewlog", title, NULL);
	}
	else if (pid > 0)
	{
		/* success */
	}
	else
	{
		MessageBox("Cannot fork viewer");
	}
}

void SelectCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	XawListReturnStruct *selected;
	unsigned long FileId;
	char fileName[128];
	FILE *hFile;
	int nBytes;
	char * pBuffer;
	int headerSize;
	HEADER *pHeader;
	char szCommand[128];

	selected = (XawListReturnStruct *)call_data;

	if (selected->list_index != lastIndex)
	{
		lastIndex = selected->list_index;
		return;
	}

	lastIndex = -1;

	sscanf(selected->string, "%lx", &FileId);

	/* open the file */
	sprintf(fileName, "%lx.dl", FileId);

	if ((hFile = fopen(fileName, "r")) == NULL)
	{
		sprintf(szCommand, "Cannot open file %s", fileName);
		MessageBox(szCommand);
		return;
	}

	pBuffer = XtMalloc(1024);

	/* read in the header */
	nBytes = fread(pBuffer, 1, 1024, hFile);

	/* close the file */
	fclose(hFile);

	/* extracting the header */
	pHeader = ExtractHeader(pBuffer, nBytes, &headerSize);

	XtFree(pBuffer);

	strcpy(szCommand, "");

	switch (pHeader->fileType)
	{
	case 0:
	case 1:
	case 2:
	case 8:
	case 9:
	case 12:
	case 13:
		switch (pHeader->compression)
		{
		case 0:
		case 2:
		case 3:
			ViewText(fileName);
			break;
		case 1:
			MessageBox("Sorry don't know how to do PKARC");
			break;
		default:
			MessageBox("Sorry do not know how to uncompress.");
			break;
		}
		break;
	case 14:
	case 15:
	case 16:
	case 221:
		ViewImage(fileName);
		break;
	case 201:
	case 202:
	case 203:
	case 208:
	case 217:
		ViewLog(fileName, pHeader->fileName);
		break;
	default:
		MessageBox("Do not know how to view message");
		break;
	}

	XtFree((char *)pHeader);
}

void ReplyCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	XawListReturnStruct *selected;
	unsigned long FileId;
	char fileName[128];
	int pid;

	selected = XawListShowCurrent(downloadlist);

	if (selected->list_index == XAW_LIST_NONE)
		return;

	sscanf(selected->string, "%lx", &FileId);

	/* open the file */
	sprintf(fileName, "%lx.dl", FileId);
	
	if ((pid=fork()) == 0)
	{
		/* the child process */
		execlp("message", "message", fileName, NULL);
	}
	else if (pid > 0)
	{
		/* success */
	}
	else
	{
		MessageBox("Cannot fork message");
	}
}

void QuitCb(Widget w, XtPointer client_data, XtPointer call_data)
{
	XtDestroyApplicationContext(app_context);

	exit(0);
}

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

	signal(SIGCHLD, signal_child);

	if ((s = getenv("MYCALL")) == NULL)
		strcpy(myCall, "NOCALL");
	else
		strcpy(myCall, s);

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

	strcpy(satelliteId , s);

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

	sprintf( title, "downloaded:%s %s", satelliteId, VERSION_STRING );
	XtVaSetValues(toplevel, XtNtitle, title, NULL);

	dpy  = XtDisplay(toplevel);
	scrn = DefaultScreen(dpy);

	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[2].value = (XtArgVal)resources.button_font;
	quitbutton = XtCreateManagedWidget("quitButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	callback[0].callback = UpdateCb;
	callback[0].closure  = toplevel;
	button_args[0].value = (XtArgVal)callback;
	button_args[1].value = (XtArgVal)"Update";
	button_args[3].value = (XtArgVal)quitbutton;
	updatebutton = XtCreateManagedWidget("updateButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	callback[0].callback = ExtractCb;
	callback[0].closure  = toplevel;
	button_args[0].value = (XtArgVal)callback;
	button_args[1].value = (XtArgVal)"Extract";
	button_args[3].value = (XtArgVal)updatebutton;
	extractbutton = XtCreateManagedWidget("extractButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	callback[0].callback = ReplyCb;
	callback[0].closure  = toplevel;
	button_args[0].value = (XtArgVal)callback;
	button_args[1].value = (XtArgVal)"Reply";
	button_args[3].value = (XtArgVal)extractbutton;
	replybutton = XtCreateManagedWidget("replyButton", commandWidgetClass,
				compwindow, button_args, XtNumber(button_args));

	label_args[0].value = (XtArgVal)NULL;
	label_args[1].value = (XtArgVal)replybutton;
	label_args[2].value = (XtArgVal)"View";
	label_args[4].value = (XtArgVal)resources.label_font;
	viewtypelabel = XtCreateManagedWidget("viewtypeLabel", labelWidgetClass,
				compwindow, label_args, XtNumber(label_args));

	menu_button_args[0].value = (XtArgVal)type_menu[0].Label;
	menu_button_args[1].value = (XtArgVal)resources.button_font;
	menu_button_args[2].value = (XtArgVal)viewtypelabel;
	viewtypebutton = XtCreateManagedWidget("viewtypeButton", menuButtonWidgetClass,
				compwindow, menu_button_args, XtNumber(menu_button_args));

	createMenuPopup(viewtypebutton, resources.menu_font, type_menu,
			XtNumber(type_menu), ViewTypeCb);

	label_args[0].value = (XtArgVal)NULL;
	label_args[1].value = (XtArgVal)viewtypebutton;
	label_args[2].value = (XtArgVal)"Order";
	label_args[4].value = (XtArgVal)resources.label_font;
	orderlabel = XtCreateManagedWidget("orderLabel", labelWidgetClass,
				compwindow, label_args, XtNumber(label_args));

	menu_button_args[0].value = (XtArgVal)order_menu[0].Label;
	menu_button_args[1].value = (XtArgVal)resources.button_font;
	menu_button_args[2].value = (XtArgVal)orderlabel;
	orderbutton = XtCreateManagedWidget("orderButton", menuButtonWidgetClass,
				compwindow, menu_button_args, XtNumber(menu_button_args));

	createMenuPopup(orderbutton, resources.menu_font, order_menu,
			XtNumber(order_menu), OrderCb);

	label_args[0].value = (XtArgVal)quitbutton;
	label_args[1].value = (XtArgVal)NULL;
	label_args[2].value = (XtArgVal)"       Id Date/Time From     To         Size Title";
	label_args[3].value = (XtArgVal)500;
	label_args[4].value = (XtArgVal)resources.list_font;
	listlabel = XtCreateManagedWidget("listLabel", labelWidgetClass,
				compwindow, label_args, XtNumber(label_args));

	viewport_args[0].value = (XtArgVal)listlabel;
	viewport = XtCreateManagedWidget("downloadedViewport", viewportWidgetClass,
				compwindow, viewport_args, XtNumber(viewport_args));
		
	callback[0].callback = SelectCb;
	callback[0].closure  = toplevel;
	list_args[0].value = (XtArgVal)callback;
	list_args[1].value = (XtArgVal)resources.list_font;
	downloadlist = XtCreateManagedWidget("downloadedList", listWidgetClass,
				viewport, list_args, XtNumber(list_args));

	createMessagePopup(resources.bold_font, resources.button_font);

	view = 0;
	Update();

	XtRealizeWidget(toplevel);

	XtAppMainLoop(app_context);

	return(0);
}
