/*
 * Copyright (c) 1999  Albert Dorofeev <Albert@mail.dma.be>
 * For the updates see http://bewoner.dma.be/Albert/
 *
 * This software is distributed under GPL. For details see LICENSE file.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>

#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/Xatom.h>

#include "x_color.h"
#include "read_mem.h"
#include "state.h"
#include "pixmaps.h"

struct salmon_state state;

/* nice idea from ascd */
typedef struct _XpmIcon {
	Pixmap pixmap;
	Pixmap mask;
	XpmAttributes attributes;
} XpmIcon;

XpmIcon backgroundXpm;
XpmIcon foregroundXpm;

/* X windows related global variables */
Display *mainDisplay = 0;	/* The display we are working on */
Window drawWindow;		/* A hidden window for drawing */
Window mainWindow;		/* Application window */
Window iconWindow;		/* Icon window */
GC mainGC;			/* Graphics context */
Atom wm_delete_window;
Atom wm_protocols;

/* last time we updated */
time_t last_time = 0;

/* requests for update */
int update_request = 1;

void push_char_buf(Window win, char char_buf[20], int line) {

	int i, j, k[15], l, m, n, o;
	char buf[15];

	/* write character buffer to window */
	/* first count the number of blank areas and columns to be filled */
	l = m = n = o = 0;
	for (i=0; i<20; ++i) {
		j = char_buf[i];
		if (!j)
			break;
		if (j==PIPE) {
			++l;
		} else if (j==SPACE) {
			continue;
		} else {
			m += CHAR_SPACE - (j>43 && j<60);
		}
	}
	m = 60 - m;
	/* l=number of blank areas, m=number of columns not filled */
	/* now calc the x offset (k[]) for each character */
	for (i=0; i<20; ++i) {
		j = char_buf[i];
		if (!j)
			break;
		if (!i) {
			k[0] = 2 - (j>43 && j<60);
			m += (j>43 && j<60) || j==SPACE;
			if (j==SPACE)
				continue;
			if (j==PIPE) {
				n = m / l;		/* n=1/lth of unfilled columns */
				m = m - n;		/* m=remaining unfilled columns */
				k[1] = k[0] + n;
				--l;
				buf[0] = SPACE;
			} else {
				k[1] = k[0] + CHAR_SPACE;
				buf[0] = j;
			}
		} else if (j==PIPE) {
			if (!char_buf[i + 1])
				break;
			n = m / l;
			m = m - n;
			k[o + 1] = k[o] + n;
			--l;
			buf[o] = SPACE;
		} else if (j==SPACE) {
			continue;
		} else {
			if (!l) {
				k[o] += (j>43 && j<60);
				l = 1;
			}
			k[o] = k[o] - (j>43 && j<60);
			k[o + 1] = k[o] + CHAR_SPACE;
			buf[o] = j;
		}
		++o;
	}
	buf[o] = 0;
	/* fill in the line */
	for (i=0; i<15; ++i) {
		if (!buf[i])
			break;
		XCopyArea(
			mainDisplay,
			foregroundXpm.pixmap,
			win,
			mainGC,
			CHAR_WIDTH * (buf[i] % 20),
			CHAR_HEIGHT * (buf[i] / 20),
			CHAR_WIDTH,
			CHAR_HEIGHT,
			k[i],
			state.lines[line - 1]
			);
	}
}

void push_mem_buf(Window win, double free_mem, double total, char char_buf[20], int line) {

	long available;
	int i, j;

	/* format a memory line then fall through to push_char_buf() */
	if (state.percent && (line==state.opt.mem || line==state.opt.swap)) {
		/*
		 * Two decimal places of accuracy may be asking
		 * a lot, but it looks better in the display.
		 */
		available = (free_mem / total) * 10000;
		i = PCNT;
	} else if ((free_mem > 999999999999LL) || state.show_gig) {
		available = free_mem / 1024 / 1024 / 1024;
		i = GIGS;
	} else if ((free_mem > 999999999L) || state.show_meg) {
		available = free_mem / 1024 / 1024;
		i = MEGS;
	} else if ((free_mem > 999999L) || state.show_key) {
		available = free_mem / 1024;
		i = KEYS;
	} else {
		available = free_mem;
		i = 0;
	}
	j = 11;
	if (i) {
		char_buf[11] = i;
		j = 10;
	}
	for (i=j; i>4; --i) {
		char_buf[i] = (available % 10) + 48;
		available /= 10;
		if (!available) {
			if (i>=9 && state.percent && (line==state.opt.mem || line==state.opt.swap))
				char_buf[8] = ZERO;
			break;
		}
	}
	if (state.percent && (line==state.opt.mem || line==state.opt.swap)) {
		for (i=5; i<9; ++i)
			char_buf[i] = char_buf[i + 1];
		char_buf[8] = PERIOD;
	}
	push_char_buf(win, char_buf, line);
}

void draw_window(Window win) {

	int i, j, k, line;
	struct tm *tm_ptr;
	time_t current_time;
	float load_avg;
	double free_mem;
	long available;
	char char_buf[20];
	char *loads[3], *states[4];
	loads[0] = "Load ";
	loads[1] = "L-5m ";
	loads[2] = "L-15m";
	states[0] = "User";
	states[1] = "Nice";
	states[2] = "Sys ";
	states[3] = "Idle";

	current_time = time(NULL);
	/* Draw the background */
	XCopyArea(
		mainDisplay,
		backgroundXpm.pixmap,
		win,
		mainGC,
		0,
		0,
		backgroundXpm.attributes.width,
		backgroundXpm.attributes.height,
		0,
		0
		);
	for (j=0; j<3; ++j) {
		if (line = state.opt.load[j]) {
			/* get the one, five or fifteen minute load average */
			(void)sprintf(char_buf, "%5s%6s", loads[j], "|   .  ");
			load_avg = state.fresh.loadavg[j];
			k = load_avg * 100;
			char_buf[11] = (k % 10) + 48;			/* hundredths */
			k /= 10;
			char_buf[10] = (k % 10) + 48;			/* tenths */
			k /= 10;
			char_buf[8] = (k % 10) + 48;			/* ones */
			k /= 10;
			char_buf[7] = k ? (k % 10) + 48 : SPACE;	/* tens */
			k /= 10;
			char_buf[6] = k ? (k % 10) + 48 : SPACE;	/* tens */
			push_char_buf(win, char_buf, line);
		}
	}
	for (j=0; j<4; ++j) {
		if (line = state.opt.states[j]) {
			(void)sprintf(char_buf, "%4s%7s", states[j], "|   . %");
			load_avg = state.fresh.times[j];
			k = load_avg * 1000;
			char_buf[9] = (k % 10) + 48;				/* hundredths */
			k /= 10;
			char_buf[7] = (k % 10) + 48;				/* tenths */
			k /= 10;
			char_buf[6] = k ? (k % 10) + 48 : SPACE;		/* if we see 100% */
			k /= 10;
			char_buf[5] = k ? k + 48 : SPACE;
			push_char_buf(win, char_buf, line);
		}
	}
	if (line = state.opt.mem) {
		(void)sprintf(char_buf, "%12s", "Mem|        ");
		/* get the memory information */
		free_mem = state.show_used ? state.fresh.used : state.fresh.free;
		/* pass state.fresh.total because we may want percent */
		push_mem_buf(win, free_mem, state.fresh.total, char_buf, line);
	}
	if (line = state.opt.swap) {
		(void)sprintf(char_buf, "%12s", "Swap|       ");
		free_mem = state.show_used ? state.fresh.swap_used : state.fresh.swap_free;
		push_mem_buf(win, free_mem, state.fresh.swap_total, char_buf, line);
	}
	if (line = state.opt.cache) {
		(void)sprintf(char_buf, "%12s", "Cach|       ");
		push_mem_buf(win, state.fresh.cached, 1, char_buf, line);
	}
	if (line = state.opt.buffer) {
		(void)sprintf(char_buf, "%12s", "Buff|       ");
		push_mem_buf(win, state.fresh.buffers, 1, char_buf, line);
	}
	if (line = state.opt.share) {
		(void)sprintf(char_buf, "%12s", "Shar|       ");
		push_mem_buf(win, state.fresh.shared, 1, char_buf, line);
	}
	if (line = state.opt.proc) {
		(void)sprintf(char_buf, "%13s", "Proc|  ,|    ");
		available = state.fresh.procs[0];
		for (i=12; i>8; --i) {
			char_buf[i] = (available % 10) + 48;
			available /= 10;
			if (!available)
				break;				/* only show up to 9999 active procs */
		}
		i = state.fresh.procs[1];
		char_buf[6] = (i % 10) + 48;
		i /= 10;
		char_buf[5] = i ? (i % 10) + 48 : SPACE;	/* only show up to 99 running procs */
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.upt) {
		(void)sprintf(char_buf, "%14s", "Up|    d|  :  ");
		/* get the uptime */
		k = state.fresh.uptime;			/* minutes */
		j = k / 1440;				/* days */
		k = k % 1440;				/* minutes - days */
		for (i=6; i>3; --i) {			/* I'm chicken, I'll just wrap every 1000 days. */
			char_buf[i] = (j % 10) + 48;
			j /= 10;
			if (!j)
				break;
		}
		j = k / 60;				/* hours */
		k = k % 60;				/* minutes */
		char_buf[13] = (k % 10) + 48;		/* minutes */
		char_buf[12] = (k / 10) + 48;		/* tens minutes */
		char_buf[10] = (j % 10) + 48;		/* hours */
		j /= 10;
		char_buf[9] = j ? j + 48 : SPACE;	/* tens hours */
		/* write in the uptime */
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.date) {
		/* date based on local time */
		tm_ptr = localtime(&current_time);
		(void)strftime(char_buf, 14, (state.year ? "%b|%e,|%Y" : "%a,|%b|%e"), tm_ptr);
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.udate) {
		/* date based on universal time */
		tm_ptr = gmtime(&current_time);
		(void)strftime(char_buf, 14, (state.uyear ? "%b|%e,|%Y" : "%a,|%b|%e"), tm_ptr);
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.lct) {
		/* get the local time */
		tm_ptr = localtime(&current_time);
		(void)strftime(char_buf, 13, (state.twelve ?
					/* show seconds only if the refresh rate is 1 second */
					((state.refresh==1) ? "%l:%M:%S|%Z" : "%l:%M|%p|%Z")
							:
					((state.refresh==1) ? "%H:%M:%S|%Z" : "|%H:%M|%Z|")),
				tm_ptr);
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.gmt) {
		/* get the universal time */
		tm_ptr = gmtime(&current_time);
		(void)strftime(char_buf, 13, (state.utwelve ?
					/* show seconds only if the refresh rate is 1 second */
					((state.refresh==1) ? "%l:%M:%S|UTC" : "%l:%M|%p|UTC")
							:
					((state.refresh==1) ? "%H:%M:%S|UTC" : "|%H:%M|UTC|")),
				tm_ptr);
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.ostype) {
		(void)sprintf(char_buf, "%s", state.osname);
		j = strlen(char_buf);
		char_buf[j + 2] = 0;
		char_buf[j + 1] = PIPE;
		for (i=j; i>0; --i)
			char_buf[i] = char_buf[i - 1];
		char_buf[0] = PIPE;
		/* write in the os name */
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.version) {
		(void)sprintf(char_buf, "%s", state.osvers);
		j = strlen(char_buf);
#if defined(__FreeBSD__)
		k = 0;
		for (i=0; i<j; ++i)
			if (char_buf[i]==DASH) {
				char_buf[i] = PIPE;
			} else if (k) {
				char_buf[i] = tolower(char_buf[i]);
			} else if (char_buf[i]!=tolower(char_buf[i])) {
				k = 1;
			}
#else	/* Linux */
		char_buf[j + 2] = 0;
		char_buf[j + 1] = PIPE;
		for (i=j; i>0; --i)
			char_buf[i] = char_buf[i - 1];
		char_buf[0] = PIPE;
#endif	/* if defined(__FreeBSD__) */
		/* write in the os version */
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.host) {
		i = gethostname(char_buf, sizeof(char_buf));
		/* more than sizeof(char_buf) or too slow but it's coming */
		if (i && !(errno==ENOMEM || errno==EINPROGRESS))
			(void)sprintf(char_buf, "%6s", "Host?.");
		j = 0;
		for (i=0; i<10; ++i) {
			/* look for a zero byte or a period */
			if (!char_buf[i] || char_buf[i] == 46)
				break;
			if (!i)
				char_buf[i] = toupper(char_buf[i]);
			++j;
		}
		char_buf[j + 2] = 0;
		char_buf[j + 1] = PIPE;
		for (i=j; i>0; --i)
			char_buf[i] = char_buf[i - 1];
		char_buf[0] = PIPE;
		/* write in the local host name */
		push_char_buf(win, char_buf, line);
	}
	if (line = state.opt.luna) {
		/* I'm a card carryin' luna tick. */
		(void)sprintf(char_buf, "%11s", "Moon|  . % ");
		get_phase(current_time, char_buf);
		push_char_buf(win, char_buf, line);
	}
}

/*
 * This function clears up all X related
 * stuff and exits. It is called in case
 * of emergencies .
 *
 * This has become the only exit routine
 * other than during parse_args().  So we
 * always use exit(0).
 */
void salmon_cleanup() {

	if (mainDisplay)
		XCloseDisplay(mainDisplay);
	close_meminfo();
	exit(0);
}

/* 
 * This checks for X11 events. We distinguish the following:
 * - request to repaint the window
 * - request to quit (Close button)
 */
void CheckX11Events() {

	XEvent Event;

	while (XPending(mainDisplay)) { 
		XNextEvent(mainDisplay, &Event);
		switch(Event.type) {
			case Expose:
				if (Event.xexpose.count == 0) {
					++update_request;
				}
				break;
			case ClientMessage:
				if ((Event.xclient.message_type == wm_protocols)
						&& (Event.xclient.data.l[0] == wm_delete_window)) {
					salmon_cleanup();
				}
				break;
		}
	}
}

void salmon_redraw() {

	XCopyArea(
		mainDisplay,
		drawWindow,
		mainWindow,
		mainGC,
		0,
		0,
		backgroundXpm.attributes.width,
		backgroundXpm.attributes.height,
		0,
		0
		);
	XCopyArea(
		mainDisplay,
		drawWindow,
		iconWindow,
		mainGC,
		0,
		0,
		backgroundXpm.attributes.width,
		backgroundXpm.attributes.height,
		0,
		0
		);

	update_request = 0;
}

void salmon_update() {

	time_t current_time;

	current_time = time(NULL);
	if (abs(current_time - last_time) >= state.refresh) {
		/* round this out so the clocks tick on time */
		last_time = current_time - (current_time % state.refresh);
		read_meminfo();
		if (memcmp(&state.last, &state.fresh, sizeof(struct meminfo))) {
			memcpy(&state.last, &state.fresh, sizeof(struct meminfo));
			update_request = 1;
		}
	}
	CheckX11Events();
	if (update_request) {
		draw_window(drawWindow);
		salmon_redraw();
	}
}

void salmon_initialize( int argc, char **argv,
			char *display_name,
			char *mainGeometry,
			int withdrawn,
			int iconic,
			int pushed_in) {

	Window Root;			/* The root window of X11 */
	XGCValues mainGCV;		/* graphics context values */
	XWindowAttributes winAttr;
	XSizeHints SizeHints;
	XTextProperty title;
	XClassHint classHint;
	XWMHints WmHints;
	XEvent Event;
	Pixel back_pix, fore_pix;
	int screen, gravity, color_depth;
	int tmp, result;
	int x_negative = 0;
	int y_negative = 0;
	char background_color[2][50];
	char foreground_color[3][50];

	mainDisplay = XOpenDisplay(display_name);
	while (!mainDisplay) {
		(void)sleep(60);
		mainDisplay = XOpenDisplay(display_name);
	}
	screen = DefaultScreen(mainDisplay);
	Root = RootWindow(mainDisplay, screen);
	back_pix = GetColor(state.bgcolor, mainDisplay, Root);
	fore_pix = GetColor(state.fgcolor, mainDisplay, Root);
	color_depth = DefaultDepth(mainDisplay, screen);
	/* adjust the background pixmap */
	sprintf(background_color[0], ". c %s", state.bgcolor);
	sprintf(background_color[1], "c c %s", DarkenCharColor(state.fgcolor, 1.6, mainDisplay, Root));
	background_xpm[1] = background_color[0];
	background_xpm[2] = background_color[1];
	backgroundXpm.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	if (XpmCreatePixmapFromData(
			mainDisplay,		/* display */
			Root,			/* window */
			background_xpm,		/* xpm */ 
			&backgroundXpm.pixmap,	/* resulting pixmap */
			&backgroundXpm.mask,
			&backgroundXpm.attributes
			)!=XpmSuccess)
		salmon_cleanup();
	sprintf(foreground_color[0], ". c %s", state.bgcolor);
	sprintf(foreground_color[1], "# c %s", state.fgcolor);
	sprintf(foreground_color[2], "c c %s", DarkenCharColor(state.fgcolor, 1.6, mainDisplay, Root));
	for (tmp=0; tmp<3; ++tmp)
		foreground_xpm[tmp+1] = foreground_color[tmp];
	foregroundXpm.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
	if (XpmCreatePixmapFromData(
			mainDisplay,
			Root,
			foreground_xpm,
			&foregroundXpm.pixmap,	/* resulting pixmap */
			&foregroundXpm.mask,
			&foregroundXpm.attributes
			)!=XpmSuccess)
		salmon_cleanup();
	SizeHints.flags = USSize | USPosition;
	SizeHints.x = 0; 
	SizeHints.y = 0; 
	XWMGeometry(
			mainDisplay,
			screen,
			mainGeometry,
			NULL,
			1,
			& SizeHints,
			&SizeHints.x,
			&SizeHints.y,
			&SizeHints.width,
			&SizeHints.height,
			&gravity
			);
	SizeHints.min_width = SizeHints.max_width = SizeHints.width = backgroundXpm.attributes.width;
	SizeHints.min_height = SizeHints.max_height = SizeHints.height= backgroundXpm.attributes.height;
	SizeHints.flags |= PMinSize | PMaxSize;

	/* Correct the offsets if the X/Y are negative */
	SizeHints.win_gravity = NorthWestGravity;
	if (x_negative) {
		SizeHints.x -= SizeHints.width;
		SizeHints.win_gravity = NorthEastGravity;
	}
	if (y_negative) {
		SizeHints.y -= SizeHints.height;
		if (x_negative)
			SizeHints.win_gravity = SouthEastGravity;
		else
			SizeHints.win_gravity = SouthWestGravity;
	}
	SizeHints.flags |= PWinGravity;

	drawWindow = XCreatePixmap(
			mainDisplay,
			Root,
			SizeHints.width,
			SizeHints.height,
			color_depth
			);

	mainWindow = XCreateSimpleWindow(
			mainDisplay,		/* display */
			Root,			/* parent */
			SizeHints.x,		/* x */
			SizeHints.y,		/* y */
			SizeHints.width,	/* width */
			SizeHints.height,	/* height */
			0,			/* border_width */
			fore_pix,		/* border */
			back_pix		/* background */
			);

	iconWindow = XCreateSimpleWindow(
			mainDisplay,		/* display */
			Root,			/* parent */
			SizeHints.x,		/* x */
			SizeHints.y,		/* y */
			SizeHints.width,	/* width */
			SizeHints.height,	/* height */
			0,			/* border_width */
			fore_pix,		/* border */
			back_pix		/* background */
			);

	XSetWMNormalHints(mainDisplay, mainWindow, &SizeHints);
	(void)XClearWindow(mainDisplay, mainWindow);

	(void)XGetWindowAttributes(
			mainDisplay,		/* display */
			mainWindow,		/* window */
			& winAttr		/* window_attributes_return */
			);
	(void)XSetWindowBackgroundPixmap(
			mainDisplay,		/* display */
			mainWindow,		/* window */
			backgroundXpm.pixmap	/* background_pixmap */
			);
	(void)XSetWindowBackgroundPixmap(
			mainDisplay,		/* display */
			iconWindow,		/* window */
			backgroundXpm.pixmap	/* background_pixmap */
			);

	(void)XStringListToTextProperty(&state.app_name, 1, &title);
	XSetWMName(mainDisplay, mainWindow, &title);
	XSetWMName(mainDisplay, iconWindow, &title);

	classHint.res_name = state.app_name;
	classHint.res_class = state.app_name;
	XSetClassHint(mainDisplay, mainWindow, &classHint);
	XStoreName(mainDisplay, mainWindow, state.app_name);
	XSetIconName(mainDisplay, mainWindow, state.app_name);

	(void)XSelectInput(
			mainDisplay,		/* display */
			mainWindow,		/* window */
			ExposureMask		/* event_mask */
			);
	(void)XSelectInput(
			mainDisplay,		/* display */
			iconWindow,		/* window */
			ExposureMask		/* event_mask */
			);

	/* Creating Graphics context */
	mainGCV.foreground = fore_pix;
	mainGCV.background = back_pix;
	mainGCV.graphics_exposures = False;
	mainGCV.line_style = LineSolid;
	mainGCV.fill_style = FillSolid;
	mainGCV.line_width = 1;
	mainGC = XCreateGC(
			mainDisplay,
			mainWindow, 
			GCForeground|GCBackground|GCLineWidth|
			GCLineStyle|GCFillStyle,
			&mainGCV
			);

	(void)XSetCommand(mainDisplay, mainWindow, argv, argc);

	/* Set up the event for quitting the window */
	wm_delete_window = XInternAtom(
			mainDisplay, 
			"WM_DELETE_WINDOW",	/* atom_name */
			False			/* only_if_exists */
			);
	wm_protocols = XInternAtom(
			mainDisplay,
			"WM_PROTOCOLS",		/* atom_name */
			False			/* only_if_exists */
			);
	(void)XSetWMProtocols(
			mainDisplay, 
			mainWindow,
			&wm_delete_window,
			1
			);
	(void)XSetWMProtocols(
			mainDisplay, 
			iconWindow,
			&wm_delete_window,
			1
			);

	WmHints.flags = StateHint | IconWindowHint;
	WmHints.initial_state = 
			withdrawn ? WithdrawnState :
			iconic ? IconicState : NormalState;
	WmHints.icon_window = iconWindow;
	if (withdrawn) {
		WmHints.window_group = mainWindow;
		WmHints.flags |= WindowGroupHint;
	}
	if (iconic || withdrawn) {
		WmHints.icon_x = SizeHints.x;
		WmHints.icon_y = SizeHints.y;
		WmHints.flags |= IconPositionHint;
	}   
	XSetWMHints(mainDisplay, mainWindow, &WmHints);

	/* Finally show the window */
	(void)XMapWindow(mainDisplay, mainWindow);
	/* wait for the Expose event now */
	XNextEvent(mainDisplay, &Event); 
	/* We have Expose -> draw the parts of the window. */
	salmon_redraw();
	XFlush(mainDisplay);
	salmon_update();
}

