// bbweather.cpp for bbweather
//
//  Copyright (c) 2001,2003,2004 by Jan Schaumann, jschauma@netmeister.org
//
//  based on bbdate by by John Kennis, j.m.b.m.kennis@ele.tue.nl
//
//  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.
//
// (See the included file COPYING / GPL-2.0)
//

#include "bbweather.h"
#include "version.h"

#include <sys/wait.h>
#ifdef _GNU_SOURCE
#include <getopt.h>
#endif
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

ToolWindow::ToolWindow(struct OPTIONS *opts):
  Basewindow(opts)
{
	metric = opts->optmetric;
	cycle = opts->optcycle;
	extra = opts->optextra;
	shape = opts->optshape;
	withdrawn = opts->optwithdrawn;

	if (opts->optupdt)
		updt = opts->optupdt;

	if (opts->opttime)
		ticks = opts->opttime;

	if (opts->geom) {
		position = (char *)malloc((strlen(opts->geom)+1)*sizeof(char));
		if (position) {
			memset(position, '\0', strlen(opts->geom)+1);
			strncpy(position, opts->geom, strlen(opts->geom));
		} else {
			fprintf(stderr, "Could not allocate memory for geometry.\n");
			fprintf(stderr, "Using defaults from config-file...\n");
		}
	}

	station = (char *)malloc((strlen(opts->optstation)+1)*sizeof(char));
	if (station) {
		memset(station, '\0', strlen(opts->optstation)+1);
		strncpy(station, opts->optstation, strlen(opts->optstation));
	} else {
		fprintf(stderr, "Could not allocate memory for StationID, bailing out!\n");
		exit(1);
	}

	weathdir = (char *)malloc((strlen(opts->wthrdir)+1)*sizeof(char));
	if (weathdir) {
		memset(weathdir, '\0', strlen(opts->wthrdir)+1);
		strncpy(weathdir, opts->wthrdir, strlen(opts->wthrdir));
	} else {
		fprintf(stderr, "Could not allocate memory for Weather Directory, bailing out!\n");
		exit(1);
	}

	memset(wdir, '\0', 8);
	memset(conditions, '\0', 64);
	memset(location, '\0', sizeof(location));
	memset(updated, '\0', sizeof(updated));
	tzone = opts->zone;

	celsius = fahrenheit = visibility = press = pressm = dp = dpm = 0.0;
	humidity = wspeed = wspeedm = 0;

	XrmInitialize();

	resource = new Resource(this);

	CheckWeather(True);
	MakeWindow(False);
}

ToolWindow::~ToolWindow()
{
	XUnmapWindow(dpy,framewin);

	/* destroy pixmaps */
	if (pixmap.frame) image_control->removeImage(pixmap.frame);
	if (pixmap.label) image_control->removeImage(pixmap.label);

	/* destroy windows */
	XDestroyWindow(dpy,labelwin);
	XDestroyWindow(dpy,framewin);
}

void
ToolWindow::Reconfigure(void)
{
	struct stat file_status;

	/* destroy pixmaps */
	image_control->removeImage(pixmap.frame);
	image_control->removeImage(pixmap.label);

	if (stat(config_filename,&file_status)!=0) {
		fprintf(stderr,"Could not open config file %s\n",
				resource->style.conf_filename);
	} else if (file_status.st_mtime != resource->style.mtime) {
		resource->style.mtime=file_status.st_mtime;
	}

	resource->Reload();

	MakeWindow(True);

	XClearWindow(dpy, labelwin);
	XClearWindow(dpy, framewin);
	Redraw(0);
}


void
ToolWindow::MakeWindow(bool reconfigure)
{
	XSetWindowAttributes attrib;
	XWMHints wmhints;

	unsigned long create_mask = CWBackPixmap|
		CWOverrideRedirect |CWCursor|CWEventMask; 

	frame.width=XTextWidth(resource->label.font,
			"[location]  T: xxx.x  (x) H: xx%",
			strlen("[location]  T: xxx.x  (x) H: xx%")) +
		resource->frame.bevelWidth*4;
	label.width=frame.width-2*resource->frame.bevelWidth;
	frame.height=resource->label.font->ascent+
		resource->label.font->descent+4*resource->frame.bevelWidth;
	label.height=frame.height-2*resource->frame.bevelWidth;

	if (resource->position.mask & XNegative) 
		resource->position.x =
			display_width + resource->position.x - frame.width;
	if (resource->position.mask & YNegative)
		resource->position.y =
			display_height + resource->position.y - frame.height;

	if (withdrawn) {
		attrib.override_redirect = False;
		wmhints.initial_state = WithdrawnState;
	} else {
		attrib.override_redirect = True;
		wmhints.initial_state = NormalState;
	}

	attrib.background_pixmap = ParentRelative;

	pixmap.frame = image_control->renderImage(frame.width, frame.height,
			&resource->frame.texture);

	pixmap.label =	image_control->renderImage(label.width, label.height,
			&resource->label.texture);

	attrib.cursor = cursor;
	attrib.event_mask = ButtonPressMask | ButtonReleaseMask | ExposureMask |
		FocusChangeMask | KeyPressMask | StructureNotifyMask;

	if (!reconfigure) {
		framewin = XCreateWindow(dpy, root, resource->position.x, 
				resource->position.y, frame.width,
				frame.height, 0, depth, InputOutput,
				v, create_mask, &attrib);
	} else if (!withdrawn) {
		XMoveResizeWindow(dpy, framewin, resource->position.x, 
				resource->position.y, frame.width,
				frame.height);

	}
	else
		XResizeWindow(dpy,framewin,frame.width,frame.height);

	wmhints.flags = IconWindowHint | StateHint;
	wmhints.icon_window = framewin;
	XSetWMHints(dpy, framewin, &wmhints);

	if (!shape)
		XSetWindowBackgroundPixmap(dpy, framewin, pixmap.frame);

	if (!reconfigure) {
		labelwin = XCreateWindow(dpy, framewin,
				resource->frame.bevelWidth, 
				resource->frame.bevelWidth, 
				label.width, label.height, 0,
				depth, InputOutput, v, create_mask, &attrib);
	} else {
		XMoveResizeWindow(dpy, labelwin, resource->frame.bevelWidth, 
				resource->frame.bevelWidth,
				label.width, label.height);
	}

	if (!resource->label.transparent)
		XSetWindowBackgroundPixmap(dpy, labelwin, pixmap.label);

	if (!reconfigure) {
		gcv.font = resource->label.font->fid;
		gcv.foreground = resource->label.textColor.pixel;
		frameGC = XCreateGC(dpy, framewin,GCFont|GCForeground, &gcv);
	} else {
		gcv.font = resource->label.font->fid;
		gcv.foreground = resource->label.textColor.pixel;
		XChangeGC(dpy, frameGC,GCFont|GCForeground, &gcv);
	}

	if (!reconfigure) {
		XClearWindow(dpy, framewin);
		XMapWindow(dpy, framewin);  
		XMapSubwindows(dpy, framewin);
	}
}

void
ToolWindow::CheckWeather(bool redraw)
{

	if (weathdir) {
		char *fn;
		int	size = strlen(weathdir) + 1 +
			strlen(station) + 4 + 1;

		fn = (char *)malloc(size * sizeof(char));
		if (fn) {
			FILE *read;
			memset(fn, '\0', size);
			snprintf(fn, size, "%s/%s.TXT", 
					weathdir, station);

			if ((read = fopen(fn, "r"))!=NULL) {
				char *line = (char *)malloc(64*sizeof(char));
				if (!line) {
					fprintf(stderr,
						"Could not allocate memory "
						"for \"line\", bailing out!\n");
					exit(1);
				}
				memset(line, '\0', 64);

				while (fgets(line, 64, read)) {
					if (!strlen(location))
						sscanf(line, "%[A-Za-z ,/] (%*s) %*d-",
								location);

					sscanf(line, "ob: %*s %s", updated);

					sscanf(line,
							"Wind: from the %s (%*d degrees) at %d MPH (%*d KT) %*s",
							wdir, &wspeed);

					sscanf(line,
							"Visibility: %lf mile(s): %*s",
							&visibility);

					sscanf(line,
							"Sky conditions: %[a-zA-Z ]",
							conditions);

					sscanf(line,
							"Temperature: %lf F (%lf C)", 
							&fahrenheit, &celsius);

					sscanf(line,
							"Dew Point: %lf F (%lf C)", 
							&dp, &dpm);

					sscanf(line,
							"Relative Humidity: %d%%",
							&humidity);
					sscanf(line,
							"Pressure (altimeter): %lf in. Hg (%lf hPa)",
							&press, &pressm);

				}
				free(line);
				fclose(read);
			} else {
				fprintf(stderr,"Error: can't read from %s\n", fn);
				return;
			}

			free(fn);
		}
	}
}

void
ToolWindow::Redraw(int index)
{
	char data[64], outline[80];
	char       *cp;
	const char *u = metric ? "KMH" : "MPH";
	const char *dist = metric ? "km" : "Miles(s)";
	const char *p = metric ? "hPa" : "in. Hg";
	char unit = metric ? 'C' : 'F';
	/* speed is given in MPH, convert to KMPH if metric */
	int s = metric ? wspeed * 8/5 : wspeed;
	double pressure = metric ? pressm : press;
	double dew = metric ? dpm : dp;
	double temperature = metric ? celsius : fahrenheit; 
	double visi = metric ? visibility * 1.60934 : visibility;
	memset(data,'\0', 64);

	switch(index) {
	case 0:
		sprintf(data, " T: %.1f (%c) H: %d%%", 
				temperature, unit, humidity);
		break;
	case 1:
		sprintf(data, " W: %d %s from %s", s, u, wdir);
		break;
	case 2:
		sprintf(data, " V: %.1f %s", visi, dist);
		break;
	case 3:
		sprintf(data, "  %s  ", conditions);
		break;
	case 4:
		sprintf(data, " P: %.2f %s", pressure, p);
		break;
	case 5:
		sprintf(data, "DP: %.1f (%c)", dew, unit);
		break;
	case 6:
		{
			int  twrk, hwrk;
			char swrk[32], zstr[4];

			strcpy( swrk, updated );
			swrk[strlen(swrk)-1] = '\0';
			twrk = atoi( &(swrk[2]) );
			if ( tzone == 0 ) {
				hwrk = twrk / 100;
				twrk -= (hwrk * 100);
				strcpy( zstr, "UTC" );
			} else {
				if ( ( ( tzone * 100 ) + twrk ) < 0 )
					twrk += 2400;
				twrk += (tzone * 100);
				hwrk = twrk / 100;
				twrk -= (hwrk * 100);
				strcpy( zstr, "LCL" );
			}
			sprintf(data, "As of: %d:%02d %s", hwrk, twrk, zstr);
			break;
		}
	case 7:
		data[0] = '\0';
		break;
	}

	/* prefix display line with location */
	memset(outline, '\0', 80);
	strncat(outline, "[", 1);
	strncat(outline, location, 8);
	outline[9] = '\0';
	cp = &(outline[8]);
	while (( cp != outline ) && ( !isalpha( *cp ) ) ) {
		*cp = '\0';
		cp--;
	}

	strcat( outline, "] " );

	if ( index != 7 ) {
		strcat( outline, data );
		prepDraw(outline, index);
	}
}

void
ToolWindow::prepDraw(char *data, int index)
{
	int maxstrlen = 32;
	int len = strlen(data);

	if (len > maxstrlen) {
		char tmp;
		int i,j,cnt;
		time_t lastTime;

		cnt = 1;
		lastTime = time(NULL);
		while (cnt) {
			/* "In: " */
			if (data[2] == ':') {
				tmp = data[3];
				j = 3;
			} else {
				tmp = data[0];
				j = 0;
			}
			for (i=j; i<len-1; i++)
				data[i] = data[i+1];

			data[len-1] = tmp;
			data[len] = '\0';
			usleep(250000);
			doDraw(data);

			if (cycle) {
				time_t now = time(NULL);
				if ((now - lastTime)==ticks)
					cnt=0;
			}
		}
	} else
		doDraw(data);
}

void
ToolWindow::doDraw(char *data)
{
	XClearWindow(dpy, labelwin);
	XDrawString(dpy, labelwin, frameGC, resource->frame.bevelWidth,
			(label.height+resource->label.font->ascent-
			 resource->label.font->descent) / 2,
			data, strlen(data));
	XFlush(dpy);
}

void ToolWindow::EventLoop(void) 
{
	time_t lastTime = time(NULL);
	time_t chkf = time(NULL);
	bool shutdown=False;

	int i = 0;

	while (!shutdown)  {
		time_t now;

		now = time(NULL);
		usleep(50);

		if (XPending(dpy)) {
			XEvent Event;
			XNextEvent(dpy, &Event);

			/* process events */
			switch (Event.type) {
			case Expose:
				Redraw(i);
				break;
			case ButtonPress:
				if (Event.xbutton.button == LEFT_BUTTON) {
					char *command = (char *)malloc(128*sizeof(char));
					if (command) {
						memset(command, '\0', 128);
						sprintf(command, 
								"xmessage -center -file %s/%s.TXT &", 
								weathdir, 
								station);

						system(command);
						free(command);
					} else {
						fprintf(stderr, 
								"Could not allocate memory for \"xmessage\", sorry!\n");
					}
					if (!(raised)) {
						XRaiseWindow(dpy,framewin);
						raised=True;
						Redraw(i);
					}
				} else if (Event.xbutton.button ==
						MIDDLE_BUTTON) {
					cycle = cycle ? False : True;
					lastTime = now;
				} else if (Event.xbutton.button == RIGHT_BUTTON) {
					Reconfigure();
					i = (i>6) ? 0 : i+1;
					/*
					 * 'conditions' aren't always given,
					 * skip if blank (three blanks)
					 */
					if ((i == 3) && (strlen(conditions)==0))
						i++;
					Redraw(i);
				}
				break;
			case ConfigureNotify:
				if (Event.xconfigure.window==framewin &&
					Event.xconfigure.send_event) {
					Reconfigure();
					int parent_x,parent_y;
					Window parent_root;
					unsigned int parent_width;
					unsigned int parent_height;
					unsigned int parent_border_width;
					unsigned int parent_depth;
					XGetGeometry(dpy,Event.xconfigure.above,
						&parent_root,
						&parent_x,&parent_y,
						&parent_width,&parent_height,
						&parent_border_width,
						&parent_depth);
					frame.x=Event.xconfigure.x+parent_x;
					frame.y=Event.xconfigure.y+parent_y;
				}
			}
		} else {
			if (now - chkf > (updt*60)) {
				CheckWeather(True);
				Redraw(i);
				chkf = now;
			}
		}

		if (cycle) {
			if ((now - lastTime)==ticks) {
				int j = (extra) ? 6 : 2;
				i = (i>j) ? 0 : i+1;
				/*
				 * 'conditions' line isnt always in the
				 * weather file. Skip it if it's blank.
				 */
				if ((i == 3) && (strlen(conditions) == 0))
					i = (extra) ? 4 : 0;
				Redraw(i);
				lastTime = now;
			}
		}

	}
}

void 
usage()
{
	fprintf(stdout,"%s version %s \n",BBTOOL,BBTOOL_VERSION);
	fprintf(stdout,"Usage: %s [options] <station>\n",BBTOOL);
	fprintf(stdout,"Options:\n");
#ifndef _GNU_SOURCE
	fprintf(stdout, "-c      Cycle through conditions\n");
	fprintf(stdout, "-d dir  Weather Home Directory\n");
	fprintf(stdout, "-f file bbweather configuration file\n");
	fprintf(stdout, "-g      Specify Geometry\n");
	fprintf(stdout, "-h      Display this help\n");
	fprintf(stdout, "-m      Use metric system for units\n");
	fprintf(stdout, "-s      Don't show frame\n");
	fprintf(stdout, "-t sec  Cycle in sec seconds interval\n");
	fprintf(stdout, "-u min  Check for new weather data ever min Minutes\n");
	fprintf(stdout, "-w      Display bbweather in the Slit\n");
	fprintf(stdout, "-v      Display version number\n");
	fprintf(stdout, "-x      When cycling, display extra information\n");
	fprintf(stdout, "-z      Time Zone offset from UTC of location\n");
#else
	fprintf(stdout, "-c, --cycle           Cycle through conditions\n");
	fprintf(stdout, "-d, --weathdir dir    Weather Home Directory\n");
	fprintf(stdout, "-f, --file file       bbweather configuration file\n");
	fprintf(stdout, "-g, --geometry +x+y   Specify Geometry\n");
	fprintf(stdout, "-h, --help            Display this help\n");
	fprintf(stdout, "-m, --metric          Use metric system for units\n");
	fprintf(stdout, "-s, --shape           Don't show frame\n");
	fprintf(stdout, "-t, --ticks sec       Cycle in sec seconds interval\n");
	fprintf(stdout, "-u, --update min      Check for new weather data ever min Minutes\n");
	fprintf(stdout, "-w, --withdrawn       Display bbweather in the Slit\n");
	fprintf(stdout, "-v, --version         Display version number\n");
	fprintf(stdout, "-x, --extra           When cycling, display extra information\n");
	fprintf(stdout, "-z, --zone            Time Zone offset from UTC of location\n");
#endif
	fprintf(stdout, "\n<station> must be the four-letter code for the weather-station\n");
	fprintf(stdout, "from which you retrieved data using \"GrabWeather\".\n");
}

int 
main(int argc,char **argv)
{
	int c;
	char *home = getenv("HOME");
	char *subdir = ".wmWeatherReports";
	struct OPTIONS opts;

	opts.optwithdrawn = false;
	opts.optmetric = false;
	opts.optcycle = false;
	opts.optextra = false;
	opts.optshape = false;
	opts.opttime = 0;
	opts.optstation = NULL;
	opts.optupdt = 10;
	opts.wthrdir = NULL;
	opts.cnfg = DEFAULT_CONF;
	opts.geom = NULL;
	opts.zone = 0;

	if (home) {
		int size = strlen(home) + 1 +
			strlen(subdir) + 1;
		opts.wthrdir = (char *)malloc(size * sizeof(char));
		memset(opts.wthrdir, '\0', size);
		snprintf(opts.wthrdir, size, "%s/%s", home, subdir);
	} else {
		fprintf(stderr, "Hmmm... can't get $HOME.  You better supply ");
		fprintf(stderr, "a directory where I can find the data using ");
		fprintf(stderr, "the \"-d\" command-line option.\n");
	}

	while (1) {
#ifdef _GNU_SOURCE
		int option_index = 0;
		static struct option long_options[] =
		{
			{"cycle", 0, 0, 'c'},
			{"extra", 0, 0, 'x'},
			{"file", 1, 0, 'f'},
			{"geometry", 1, 0, 'g'},
			{"help", 0, 0, 'h'},
			{"metric", 0, 0, 'm'},
			{"shape", 0, 0, 's'},
			{"ticks", 1, 0, 't'},
			{"update", 1, 0, 't'},
			{"version", 0, 0, 'v'},
			{"weathdir", 1, 0, 'd'},
			{"withdrawn", 0, 0, 'w'},
			{"zone", 1, 0, 'z'}
		};

		c = getopt_long (argc, argv, "cd:f:g:hmst:u:vwxz:",
				long_options, &option_index);
#else
		c = getopt(argc, argv, "cd:f:g:hmst:u:vwxz:");
#endif
		if (c == -1)
			break;

		switch(c) {
		case '?':
		case 'h':
			usage();
			exit(0);
			break;
		case 'g':
			opts.geom = (char *)malloc((strlen(optarg)+1)*sizeof(char));
			if (opts.geom) {
				memset(opts.geom, '\0', strlen(optarg)+1);
				strncpy(opts.geom, optarg, strlen(optarg));
			} else {
				fprintf(stderr, "Error allocating memory for geometry.\n");
				fprintf(stderr, "Using defaults...\n");
			}
			break;
		case 'c':
			opts.optcycle = true;
			break;
		case 'd':
			opts.wthrdir = (char *)malloc((strlen(optarg)+1)*sizeof(char));
			if (opts.wthrdir != NULL) {
				memset(opts.wthrdir, '\0', strlen(optarg)+1);
				strncpy(opts.wthrdir, optarg, strlen(optarg));
			} else {
				fprintf(stderr, "Error allocating memory for weathdir.\n");
				fprintf(stderr, "Using defaults...\n");
			}
			break;
		case 'f':
			opts.cnfg = (char *)malloc((strlen(optarg)+1)*sizeof(char));
			if (opts.cnfg != NULL) {
				memset(opts.cnfg, '\0', strlen(optarg)+1);
				strncpy(opts.cnfg, optarg, strlen(optarg));
			} else {
				fprintf(stderr, "Error allocating memory for config file.\n");
				fprintf(stderr, "Using defaults...\n");
			}
			break;
		case 'm':
			opts.optmetric = true;
			break;
		case 's':
			opts.optshape = true;
			break;
		case 't':
			opts.opttime = atoi(optarg);
			if (opts.opttime < 1)
				opts.opttime = 60;
			break;
		case 'u':
			opts.optupdt = atoi(optarg);
			if (opts.optupdt < 1)
				opts.optupdt = 10;
			break;
		case 'v':
			fprintf(stdout,"%s version %s \n",
					BBTOOL,BBTOOL_VERSION);
			exit(0);
			/* NOTREACHED */
			break;
		case 'w':
			opts.optwithdrawn = true;
			break;
		case 'x':
			opts.optextra = true;
			break;
		case 'z':
			opts.zone =  atoi(optarg);
			if (( opts.zone > 12 ) || ( opts.zone < -12 ))
				opts.zone = 0;
			break;
		default:
			fprintf (stderr, 
				"?? getopt returned character code 0%o ??\n",
				c);
		}
	}

	if (!argv[optind]) {
		usage(); 
		exit(1);
	}

	opts.optstation = (char *)malloc((strlen(argv[optind])+1)*sizeof(char));
	if (opts.optstation) {
		memset(opts.optstation, '\0', strlen(argv[optind])+1);
		strncpy(opts.optstation, argv[optind], strlen(argv[optind]));
	} else {
		fprintf(stderr, "Error allocating memory for Station ID!\n");
		fprintf(stderr, "Bailing out, sorry.\n");
		exit(1);
	}

	ToolWindow *AppWindow = new ToolWindow (&opts);

	AppWindow->EventLoop();
	delete AppWindow;
	return 0;
}
