#include <stdio.h>
#include <ntt.h>
#include <niterror.h>
#include <nwbindry.h>
#include <nwmisc.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "acct.h"

#define PROG_NAME		"LOGGRAPH"
#define DEFAULT_DIR		"\\SYSTEM"
#define DEFAULT_ACCT_FILE	"\\NET$ACCT.DAT"
#define	MAX_BINDERY_OBJ_LEN	48
#define HOUR			3600
#define QUARTER_HOUR		900
#define MINUTE			60

typedef struct StationRecord {
	LONG	clientID;
	LONG	net;
	LONG	nodeHigh;
	WORD	nodeLow;
	int	loggedIn;
	struct StationRecord	*next;
} STATION_RECORD;

void Usage(void);
void ProcessRecord(ACCT_RECORD *record);
time_t TimeStampToTimeT(BYTE *timeStamp);
void DisplayLine(void);
void LogInRecord(ACCT_RECORD *record);
void LogOutRecord(ACCT_RECORD *record);
STATION_RECORD *AddStation(ACCT_RECORD *record);
void DisplayStations(void);

STATION_RECORD	*stationList = NULL;
time_t		currentTime;
int		granularity = HOUR;

void main(int argc, char **argv)
{
	char	acctFilePath[MAX_DIR_ENTRY] = "";
	char	*path;
	FILE	*acctFile;
	int	itemsRead;
	int	firstRecord = TRUE;
	ACCT_RECORD	*record;

	if (argc > 3)
		/* too many args */
		Usage();
	else {
		/* 0-2 args */
		while (argc-- > 1) {
			if ((argv[argc][0] == '/') && (argv[argc][1] == 'a')) {
				/* found /a */
				path = &argv[argc][2];
				strcpy(acctFilePath, path);

		       } else if ((argv[argc][0] == '/') && (argv[argc][1] == 'h')) {
				/* found /h */
				granularity = HOUR;

		       } else if ((argv[argc][0] == '/') && (argv[argc][1] == 'q')) {
				/* found /q */
				granularity = QUARTER_HOUR;

		       } else if ((argv[argc][0] == '/') && (argv[argc][1] == 'm')) {
				/* found /m */
				granularity = MINUTE;

			} else {
				/* unrecognized arg */
				Usage();
				exit(1);
			}
		}

		if (strlen(acctFilePath) == 0)
			sprintf(acctFilePath, "%s%s", DEFAULT_DIR, DEFAULT_ACCT_FILE);

		acctFile = fopen(acctFilePath, "rb");
		if (acctFile == NULL) {
			printf("Unable to open %s\n", acctFilePath);
			exit(1);
		}

		do {
			itemsRead = fread(&(record->length), sizeof(WORD), 1, acctFile);
			if (itemsRead == 1) {
				itemsRead = fread(&(record->serverID), IntSwap(record->length), 1, acctFile);
				if (itemsRead != 1) {
					printf("Unexpected end of file %s\n", acctFilePath);
					break;
				}
				if (firstRecord) {
					currentTime = TimeStampToTimeT(record->timeStamp);

					/* round to nearest time unit */
					currentTime = currentTime - (currentTime % granularity);

					firstRecord = FALSE;
				}
				ProcessRecord(record);
			}
		} while(itemsRead);

		/* force display of one last line */
		ProcessRecord(NULL);
		DisplayStations();
	}
}

void Usage(void)
{
	printf("%s:  Display simple graph of user logins\n", PROG_NAME);
	printf("Usage:\n\n");
	printf("\t%s [/aAcctFileName]\n\n", PROG_NAME);
	printf("\t/a     Specify accounting data file\n");
	printf("\t       Default data file is %s%s\n", DEFAULT_DIR, DEFAULT_ACCT_FILE);
	printf("\t/h     Display hourly login report (default)\n");
	printf("\t/q     Display quarter-hourly login report\n");
	printf("\t/m     Display minute-by-minute login report\n");
}

void ProcessRecord(ACCT_RECORD *record)
{
	time_t	recordTime;
	char	timeStr[18];

	if (record == NULL) {
		/* force display of one last line */
		DisplayLine();
		return;
	}

	recordTime = TimeStampToTimeT(record->timeStamp);

	while (recordTime > currentTime) {
		DisplayLine();
		currentTime = currentTime + granularity;
	}

	if (record->recordType == NOTE_RECORD) {
		if (IntSwap(record->chargeOrNote.note.noteType) == LOGIN_NOTE)
			LogInRecord(record);

		else if (IntSwap(record->chargeOrNote.note.noteType) == LOGOUT_NOTE)
			LogOutRecord(record);

		else if (IntSwap(record->chargeOrNote.note.noteType) == SERVER_TIME_MODIFIED_NOTE) {
			strftime(timeStr, 18, "%m/%d/%y %H:%M:%S", localtime(&recordTime));
			printf("********** Server time changed to %s **********\n", timeStr);

			/* set new current time */
			currentTime = TimeStampToTimeT(record->timeStamp);

			/* round to nearest time unit */
			currentTime = currentTime - (currentTime % granularity);
		}
	}
}

void DisplayLine(void)
{
	char		timeStr[15];
	STATION_RECORD	*currRec;

	strftime(timeStr, 15, "%m/%d/%y %H:%M", localtime(&currentTime));
	printf("%s  ", timeStr);

	for(currRec=stationList; currRec; currRec=currRec->next)
		if (currRec->loggedIn)
			printf(" * ");
		else
			printf("   ");

	printf("\n");
}

/* Given time stamp from accounting record, convert to seconds since 1/1/70 */
time_t TimeStampToTimeT(BYTE *timeStamp)
{
	struct tm	timeStruct;

	timeStruct.tm_year = timeStamp[0];
	timeStruct.tm_mon = timeStamp[1] - 1;	/* tm structures are 0-based */
	timeStruct.tm_mday = timeStamp[2];
	timeStruct.tm_hour = timeStamp[3];
	timeStruct.tm_min = timeStamp[4];
	timeStruct.tm_sec = timeStamp[5];

	return(mktime(&timeStruct));
}

void LogInRecord(ACCT_RECORD *record)
{
	STATION_RECORD	*currRec,
			*lastRec;

	if (!stationList)
		stationList = AddStation(record);

	else {
		currRec = stationList;
		while (currRec) {
			if ((currRec->clientID == record->clientID) &&
			    (currRec->net == record->chargeOrNote.note.noteComment.loginNote.net) &&
			    (currRec->nodeHigh == record->chargeOrNote.note.noteComment.loginNote.nodeHigh) &&
			    (currRec->nodeLow == record->chargeOrNote.note.noteComment.loginNote.nodeLow)) {
				currRec->loggedIn = TRUE;
				break;

			} else {
				lastRec = currRec;
				currRec = currRec->next;
			}
		}
		if (!currRec)
			lastRec->next = AddStation(record);
	}
}

STATION_RECORD *AddStation(ACCT_RECORD *record)
{
	STATION_RECORD	*newRec;

	newRec = (STATION_RECORD *)malloc(sizeof(STATION_RECORD));
	if (!newRec) {
		printf("Out of memory\n");
		exit(1);
	}

	newRec->clientID = record->clientID;
	newRec->net = record->chargeOrNote.note.noteComment.loginNote.net;
	newRec->nodeHigh = record->chargeOrNote.note.noteComment.loginNote.nodeHigh;
	newRec->nodeLow = record->chargeOrNote.note.noteComment.loginNote.nodeLow;
	newRec->loggedIn = TRUE;
	newRec->next = NULL;

	return(newRec);
}

void LogOutRecord(ACCT_RECORD *record)
{
	STATION_RECORD	*currRec;

	currRec = stationList;
	while(currRec) {
		if ((currRec->clientID == record->clientID) &&
		    (currRec->net == record->chargeOrNote.note.noteComment.loginNote.net) &&
		    (currRec->nodeHigh == record->chargeOrNote.note.noteComment.loginNote.nodeHigh) &&
		    (currRec->nodeLow == record->chargeOrNote.note.noteComment.loginNote.nodeLow)) {
			currRec->loggedIn = FALSE;
			break;

		} else
			currRec = currRec->next;
	}
}

void DisplayStations(void)
{
	int	ccode;
	char	userName[MAX_BINDERY_OBJ_LEN];
	STATION_RECORD	*currRec;
	int	stationNum;

	/* display station numbers */
	printf("\nStation Number:");
	for (currRec = stationList, stationNum = 1;
	     currRec;
	     currRec = currRec->next, stationNum++)
		printf("%3d", stationNum);

	printf("\n\n");

	/* display user name, net, and node numbers for each station */
	for (currRec = stationList, stationNum = 1;
	     currRec;
	     currRec = currRec->next, stationNum++) {

		printf("Station %d:\n", stationNum);
		ccode = GetBinderyObjectName(LongSwap(currRec->clientID), userName, (BYTE *)NULL);
		if (ccode)
			strcpy(userName, "(unknown)");

		printf("   %s\n", userName);
		printf("   %lX\n", LongSwap(currRec->net));
		printf("   %lX%X\n\n", LongSwap(currRec->nodeHigh), IntSwap(currRec->nodeLow));
	}
}
