/*
 * astime is an analogue clock utility for X Windows.
 *
 * Copyright (c) 1998-2000  Albert Dorofeev <albert@tigr.net>
 * Copyright (c) 1999 William Kostis <kostis@ee.cornell.edu>
 *
 * For the updates see http://www.tigr.net/afterstep/as-apps/
 *
 * This software is distributed under GPL. For details see LICENSE file.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "safecopy.h"

#include "astime_x.h"
#include "state.h"

extern struct astime_state state;

/*
 * default check and update intervals in microseconds
 *      x11 events - every 1/100 th of a second (in mks)
 *      update dial - every second (in sec)
 */

#define X11_INTERVAL    10000L
#define CHK_INTERVAL    1

/*
   #define DEBUG
   #define P_MAPPING
   #define P_CRON
 */

int withdrawn = 0;
int iconic = 0;
int pushed_in = 1;
char display_name[MAX_OPT_LEN];
char mainGeometry[MAX_OPT_LEN];
char window_name[MAX_OPT_LEN];

int thickspec = 0;
int fancyhands = 0;
int outlinedhands = 0;

FILE *rcf;
char rcfname[255];
char *hname;

void parsercfile(FILE * ifp);
void handprops();

void defaults()
{
	int i, j;

	state.update_interval = CHK_INTERVAL;
	state.draw_seconds = 1;
	state.tprop = 1;
	state.float_seconds = 0;
	state.thin_seconds = 0;
	state.draw_date = 1;
	state.draw_ampm = 1;
	state.shift = 0;
	withdrawn = 0;
	iconic = 0;
	pushed_in = 1;
	state.total_size.x = state.total_size.y = 54;
	safecopy(window_name, "astime", MAX_OPT_LEN);
	safecopy(display_name, "", MAX_OPT_LEN);
	safecopy(mainGeometry, "", MAX_OPT_LEN);
	safecopy(state.bgcolor, "#385971", MAX_OPT_LEN);
	safecopy(state.fgcolor, "#ffffff", MAX_OPT_LEN);

	safecopy(state.seccolor, "#efc669", MAX_OPT_LEN);
	safecopy(state.mincolor, "#ff3030", MAX_OPT_LEN);
	safecopy(state.hourcolor, "#ef3838", MAX_OPT_LEN);

	safecopy(state.secocolor, "<>", MAX_OPT_LEN);
	safecopy(state.minocolor, "<>", MAX_OPT_LEN);
	safecopy(state.hourocolor, "<>", MAX_OPT_LEN);

	safecopy(state.secfillcolor, "<>", MAX_OPT_LEN);
	safecopy(state.minfillcolor, "<>", MAX_OPT_LEN);
	safecopy(state.hourfillcolor, "<>", MAX_OPT_LEN);

	safecopy(state.daycolor, "#a0a0a0", MAX_OPT_LEN);
	safecopy(state.ampmcolor, "#a0a0a0", MAX_OPT_LEN);
	safecopy(state.datecolor, "#a0a0a0", MAX_OPT_LEN);

	/* label options */
	state.show_label = 0;
	state.label_top = 0;
	safecopy(state.label, "astime", 256);
	safecopy(state.font_name, 
			"-*-helvetica-medium-r-*-*-9-240-*-*-*-*-*-*", 256);
	safecopy(state.label_color, "#a0a0a0", MAX_OPT_LEN);

	/* clear hand rendering options */
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++)
			state.hprop[i][j] = 0;
	}
	state.hthick = 1;
	state.othick = 1;
	state.neon = 0;
	state.run_program = 0;
	state.cron_program = 0;
	safecopy(state.program_name, "echo astime: No program specified.", 255);

	state.nprogs = 0;
	/* state.PInf = (PInfo *) malloc(sizeof(*(state.PInf))); */
	state.PInf = (PInfo *) NULL;
}

/* print the usage for the tool */
void usage()
{
	printf("Usage : astime [options ...]\n\n");
	printf("-V               print version and exit\n");
	printf("-h -H -help      print this message\n");
	printf("-rc <filename>   resource file name\n");
	printf("-u <secs>        the update interval in seconds\n");
	printf("-exe <prog>      the program to start when clicked on\n");
	printf("-display <name>  the name of the display to use\n");
	printf("-geometry <xywh> position/size on the screen (X geometry)\n");
	printf("-withdrawn       start in withdrawn shape (for WindowMaker)\n");
	printf("-iconic          start iconized\n");
	printf("-standout        standing out rather than being pushed in\n");
	printf("-shift <hrs>     show the time with the given shift in hours\n");
	printf("-title <name>    set the window/icon title to this name\n");
	printf("-bg <color>      background color\n");
	printf("-fg <color>      dial foreground color\n");
	printf("-sec <color>     second pointer color\n");
	printf("-min <color>     minute pointer color\n");
	printf("-hour <color>    hour pointer color\n");
	printf("-day <color>     day of the week display color\n");
	printf("-ampm <color>    AM/PM display color\n");
	printf("-date <color>    date display color\n");
	printf("-ns              no second hand\n");
	printf("-nd              no date/day of the week\n");
	printf("-nap             no AM/PM indicator\n");
	printf("-nt              draw no ticks\n");
	printf("-at              draw all ticks\n");
	printf("-fs              use a floating second indicator\n");
	printf("-ts              use a thin second hand/indicator\n");
	printf("-fh              render fancy hands\n");
	printf("-nh              neon hands (nice with -fh)\n");
	printf("-ht <0-200>      hand thickness\n");
	printf("-ot <0-200>      hand outline thickness (with -fh)\n");
	printf("-nf              no foreground (-nd -nap -nt)\n");
	printf("-label <text>    show the label <text> on the clock\n");
	printf("-fn <name>       use the font name for the label\n");
	printf("-lcol <color>    color to use for the label\n");
	printf("-ltop            the label should go on top\n");
	printf("\n");
	exit(0);
}

/* print the version of the tool */
void version()
{
	printf("astime : AfterStep analog clock version 2.6\n");
}

/* parse the rc file */
void parsercfile(FILE * ifp)
{
	int i, j, ind;
	int tval;
	char *res;
	char line[200];
	char param[2][100];
	char *wptr;
	char *eptr;
	char sstr[8] = " \t\n";

	while ((res = fgets(line, 200, ifp)) != NULL) {

		if ((line[0] != '#') && (line[0] != '\n')) {
#ifdef DEBUG
			fprintf(stderr, "parsercfile: <%s>\n", line);
#endif
			wptr = strtok(line, sstr);

			for (i = 0; i < 2; i++) {
				if (wptr) {
					if (!strcmp(wptr, "\n"))
						fprintf(stderr, "Warning: newline\n");
#ifdef DEBUG
					fprintf(stderr, "param[%d] will get Token (%s)\n", i, wptr);
#endif
					strcpy(param[i], wptr);
				} else {
					fprintf(stderr, "parsercfile: missing param[%d]\n", i);
				}

				if ((i == 0) && !strcmp(param[0], "execProg"))
					wptr += 9;
				else if ((i == 0) && !strcmp(param[0], "at"))
					break;
				else
					wptr = strtok(NULL, sstr);
			}

#ifdef P_MAPPING
			if (strcmp(param[0], "at")) {
				if (strlen(param[0]) > 8)
					fprintf(stderr, "map (%s) \t<- (%s)\n", param[0], param[1]);
				else
					fprintf(stderr, "map (%s) \t\t<- (%s)\n", param[0], param[1]);
			}
#endif

			if (!strcmp(param[0], "geometry")) {
				safecopy(mainGeometry, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "background")) {
				safecopy(state.bgcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "foreground")) {
				safecopy(state.fgcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "secondColor")) {
				safecopy(state.seccolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "minuteColor")) {
				safecopy(state.mincolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "hourColor")) {
				safecopy(state.hourcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "secondOutColor")) {
				safecopy(state.secocolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "minuteOutColor")) {
				safecopy(state.minocolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "hourOutColor")) {
				safecopy(state.hourocolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "secondFillColor")) {
				safecopy(state.secfillcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "minuteFillColor")) {
				safecopy(state.minfillcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "hourFillColor")) {
				safecopy(state.hourfillcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "dayColor")) {
				safecopy(state.daycolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "AMPMColor")) {
				safecopy(state.ampmcolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "dateColor")) {
				safecopy(state.datecolor, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "Label")) {
				state.show_label = 1;
				safecopy(state.label, param[1], 256);
			} else if (!strcmp(param[0], "Font")) {
				safecopy(state.font_name, param[1], 256);
			} else if (!strcmp(param[0], "LabelColor")) {
				safecopy(state.label_color, param[1], MAX_OPT_LEN);
			} else if (!strcmp(param[0], "LabelTop")) {
				tval = atoi(param[1]);
				if (tval == 0)
					state.label_top = 0;
				else
					state.label_top = 1;
			} else if (!strcmp(param[0], "drawSecondHand")) {
				tval = atoi(param[1]);
				if (tval)
					state.draw_seconds = atoi(param[1]);
			} else if (!strcmp(param[0], "drawDayDate")) {
				tval = atoi(param[1]);
				if (tval == 0)
					state.draw_date = 0;
			} else if (!strcmp(param[0], "drawAMPM")) {
				tval = atoi(param[1]);
				if (tval == 0)
					state.draw_ampm = 0;
			} else if (!strcmp(param[0], "drawTicks")) {
				tval = atoi(param[1]);
				if (tval == 0)
					state.tprop = 0;
			} else if (!strcmp(param[0], "drawAllTicks")) {
				tval = atoi(param[1]);
				if ((state.tprop) && (tval == 1))
					state.tprop = 2;
			} else if (!strcmp(param[0], "drawForeground")) {
				tval = atoi(param[1]);
				if (tval == 0) {
					state.draw_date = 0;
					state.draw_ampm = 0;
					state.tprop = 0;
				}
			} else if (!strcmp(param[0], "floatSeconds")) {
				tval = atoi(param[1]);
				if (tval)
					state.float_seconds = atoi(param[1]);
			} else if (!strcmp(param[0], "thinSeconds")) {
				tval = atoi(param[1]);
				if (tval)
					state.thin_seconds = atoi(param[1]);
			} else if (!strcmp(param[0], "handThickness")) {
				state.hthick = atoi(param[1]);
				thickspec = 1;
			} else if (!strcmp(param[0], "outThickness")) {
				state.othick = atoi(param[1]);
			} else if (!strcmp(param[0], "fancyHands")) {
				tval = atoi(param[1]);
				if (tval) {
					fancyhands = 1;
					if (!thickspec)
						state.hthick = 5;
				}
			} else if (!strcmp(param[0], "outlinedHands")) {
				tval = atoi(param[1]);
				if (tval)
					outlinedhands = 1;
			} else if (!strcmp(param[0], "neonHands")) {
				tval = atoi(param[1]);
				if (tval)
					state.neon = 1;
			} else if (!strcmp(param[0], "execProg")) {
				eptr = strchr(param[1], '"');
				if (eptr != NULL) {
					eptr += 1;
					safecopy(state.program_name, eptr, strlen(eptr) - 1);
					state.run_program = 1;
#ifdef P_MAPPING
					fprintf(stderr, "exec : <%s>\n", state.program_name);
#endif
				} else
					fprintf(stderr, "astime: executables must be double-quoted in astimerc file, ignoring...\n");
			} else if (!strcmp(param[0], "at")) {
				ind = state.nprogs;
				state.nprogs += 1;
				state.PInf = (PInfo *) realloc(state.PInf, state.nprogs * sizeof(*(state.PInf)));
				if (state.PInf == NULL) {
					fprintf(stderr, "astime: memory reallocation error, exiting\n");
					exit(1);
				}
				state.PInf[ind] = (PInfo) malloc(sizeof(**(state.PInf)));
				if (state.PInf[ind] == NULL) {
					fprintf(stderr, "astime: memory reallocation error, exiting\n");
					exit(1);
				}
				for (j = 0; j < 3; j++) {
					wptr = strtok(NULL, " :");
					tval = atoi(wptr);
#ifdef DEBUG
					fprintf(stderr, "map (state.PInf[%d]->time[%d]) <- %d <%s>\n", ind, j, tval, wptr);
#endif
					state.PInf[ind]->time[j] = tval;
				}

				wptr += 3;
				eptr = strchr(wptr, '"');

				if (eptr != NULL) {
					eptr += 1;
					safecopy(state.PInf[ind]->name, eptr, strlen(eptr) - 1);
					state.PInf[ind]->done = 0;
					state.cron_program = 1;
#ifdef P_CRON
					fprintf(stderr, "cron[%d] : %02d:%02d:%02d : <%s>\n",
						ind, state.PInf[ind]->time[0], state.PInf[ind]->time[1],
						state.PInf[ind]->time[2], state.PInf[ind]->name);
#endif
				} else
					fprintf(stderr, "astime: executables must be double-quoted in astimerc file, ignoring...\n");
			} else if (!strcmp(param[0], "withdrawn")) {
				tval = atoi(param[1]);
				if (tval)
					withdrawn = 1;
			} else if (!strcmp(param[0], "iconic")) {
				tval = atoi(param[1]);
				if (tval)
					iconic = 1;
			} else if (!strcmp(param[0], "standout")) {
				tval = atoi(param[1]);
				if (tval)
					pushed_in = 0;
			} else if (!strcmp(param[0], "shift")) {
				state.shift = atoi(param[1]);
				if ((state.shift > 23) || (state.shift < -23))
					state.shift = 0;
			} else if (!strcmp(param[0], "title")) {
				safecopy(window_name, param[1], MAX_OPT_LEN);
			} else {
				fprintf(stderr, "astime: error in astimerc file, ignoring...\n");
			}
		}
	}
}

void parsecmdline(int argc, char *argv[])
{
	char *argument;
	int i;

	/* parse the command line */
	for (i = 1; i < argc; i++) {
		argument = argv[i];
		if (argument[0] == '-') {
			if (!strncmp(argument, "-rc", 3)) {
				// just skip it
				if (++i >= argc)
					usage();
			} else if (!strncmp(argument, "-withdrawn", 10)) {
				withdrawn = 1;
			} else if (!strncmp(argument, "-iconic", 7)) {
				iconic = 1;
			} else if (!strncmp(argument, "-standout", 9)) {
				pushed_in = 0;
			} else if (!strncmp(argument, "-u", 2)) {
				if (++i >= argc)
					usage();
				state.update_interval = atoi(argv[i]);
				if (state.update_interval < 1)
					state.update_interval = CHK_INTERVAL;
			} else if (!strncmp(argument, "-shift", 6)) {
				if (++i >= argc)
					usage();
				state.shift = atoi(argv[i]);
				if ((state.shift > 23) || (state.shift < -23))
					state.shift = 0;
			} else if (!strncmp(argument, "-ns", 3)) {
				state.draw_seconds = 0;
			} else if (!strncmp(argument, "-nd", 3)) {
				state.draw_date = 0;
			} else if (!strncmp(argument, "-nap", 4)) {
				state.draw_ampm = 0;
			} else if (!strncmp(argument, "-geometry", 9)) {
				if (++i >= argc)
					usage();
				safecopy(mainGeometry, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-title", 6)) {
				if (++i >= argc)
					usage();
				safecopy(window_name, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-display", 8)) {
				if (++i >= argc)
					usage();
				safecopy(display_name, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-bg", 3)) {
				if (++i >= argc)
					usage();
				safecopy(state.bgcolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-fg", 3)) {
				if (++i >= argc)
					usage();
				safecopy(state.fgcolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-label", 6)) {
				if (++i >= argc)
					usage();
				state.show_label = 1;
				safecopy(state.label, argv[i], 256);
			} else if (!strncmp(argument, "-fn", 3)) {
				if (++i >= argc)
					usage();
				safecopy(state.font_name, argv[i], 256);
			} else if (!strncmp(argument, "-lcolor", 3)) {
				if (++i >= argc)
					usage();
				safecopy(state.label_color, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-ltop", 4)) {
				state.label_top = 1;
			} else if (!strncmp(argument, "-sec", 4)) {
				if (++i >= argc)
					usage();
				safecopy(state.seccolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-min", 4)) {
				if (++i >= argc)
					usage();
				safecopy(state.mincolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-hour", 5)) {
				if (++i >= argc)
					usage();
				safecopy(state.hourcolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-day", 4)) {
				if (++i >= argc)
					usage();
				safecopy(state.daycolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-ampm", 5)) {
				if (++i >= argc)
					usage();
				safecopy(state.ampmcolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-date", 5)) {
				if (++i >= argc)
					usage();
				safecopy(state.datecolor, argv[i], MAX_OPT_LEN);
			} else if (!strncmp(argument, "-exe", 4)) {
				if (++i >= argc)
					usage();
				safecopy(state.program_name, argv[i], 255);
				state.run_program = 1;
			} else if (!strncmp(argument, "-at", 3)) {
				state.tprop = 2;
			} else if (!strncmp(argument, "-nt", 3)) {
				state.tprop = 0;
			} else if (!strncmp(argument, "-fs", 3)) {
				state.float_seconds = 1;
			} else if (!strncmp(argument, "-ts", 3)) {
				state.thin_seconds = 1;
			} else if (!strncmp(argument, "-ht", 3)) {
				if (++i >= argc)
					usage();
				state.hthick = atoi(argv[i]);
				thickspec = 1;
			} else if (!strncmp(argument, "-ot", 3)) {
				if (++i >= argc)
					usage();
				state.othick = atoi(argv[i]);
			} else if (!strncmp(argument, "-fh", 3)) {
				fancyhands = 1;
				if (!thickspec)
					state.hthick = 5;
			} else if (!strncmp(argument, "-oh", 3)) {
				outlinedhands = 1;
			} else if (!strncmp(argument, "-nh", 3)) {
				state.neon = 1;
			} else if (!strncmp(argument, "-nf", 3)) {
				state.draw_date = 0;
				state.draw_ampm = 0;
				state.tprop = 0;
			} else if (!strncmp(argument, "-V", 2)) {
				version();
				exit(0);
			} else if (!strncmp(argument, "-H", 2)) {
				version();
				usage();
			} else if (!strncmp(argument, "-h", 2)) {
				version();
				usage();
			} else {
				version();
				usage();
			}
		} else {
			version();
			usage();
		}
	}
}


void handprops()
{
	/* set hand rendering options */
	if (outlinedhands) {
		state.hprop[PSEC][HOLINE] = 1;
		state.hprop[PMIN][HOLINE] = 1;
		state.hprop[PHOUR][HOLINE] = 1;
	} else if (fancyhands) {
		state.hprop[PSEC][HOLINE] = 1;
		state.hprop[PSEC][HFILL] = 1;
		state.hprop[PMIN][HOLINE] = 1;
		state.hprop[PMIN][HFILL] = 1;
		state.hprop[PHOUR][HOLINE] = 1;
		state.hprop[PHOUR][HFILL] = 1;
	} else {
		state.hprop[PSEC][HCLINE] = 1;
		state.hprop[PMIN][HCLINE] = 1;
		state.hprop[PHOUR][HCLINE] = 1;
	}

	if (state.thin_seconds) {
		state.hprop[PSEC][HOLINE] = 0;
		state.hprop[PSEC][HFILL] = 0;
		state.hprop[PSEC][HCLINE] = 1;
	}
}


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

	defaults();

	/*
	 * Check if there is a resource file name
	 * specified on the command line. That file
	 * takes precedence over ~/.astimerc
	 */
	rcfound = 0;
	for (i=1; i<argc; i++) {
		if ( !strncmp(argv[i], "-rc", 3) ) {
			if (++i >= argc)
				usage();
			safecopy(rcfname, argv[i], 255);
			rcfound = 1;
		}
	}
	if ( ! rcfound ) {
		hname = getenv("HOME");
		strcpy(rcfname, hname);
		if (rcfname[strlen(rcfname) - 1] != '/')
			strcat(rcfname, "/");

		strcat(rcfname, ".astimerc");
#ifdef DEBUG
		fprintf(stderr, "hname is %s\n", hname);
		fprintf(stderr, "rcfname is %s\n", rcfname);
#endif
	}

	rcf = fopen(rcfname, "r");
	if (rcf != NULL) {
		parsercfile(rcf);
		fclose(rcf);
	} else {
		if ( rcfound ) 
			fprintf(stderr, "astime: could not open %s ...\n",
					rcfname);
	}

	parsecmdline(argc, argv);

	handprops();

	initialize(argc, argv,
		   window_name,
		   display_name,
		   mainGeometry,
		   withdrawn,
		   iconic,
		   pushed_in);
	while (1) {
		update();
		usleep(X11_INTERVAL);
	}
	return (0);
}
