/* 
 * Copyright 2004-2005 Timo Hirvonen
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <xmalloc.h>
#include <pager.h>
#include <opt.h>
#include <x.h>
#include <sconf.h>

#include <X11/Xlib.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

char *program_name = NULL;
char config_filename[256];

typedef struct {
	int border;
	int min;
	int max;
	char *cmd;
} command;

int auto_hide = 0;
static int running = 1;
static int n_cmd = 0;
static command *cmd = NULL;
static char *border_commands = NULL;

static struct pager *pager;

static void ignored(XEvent *event)
{
/* 	printf("ignoring event %d\n", event->type); */
}

static void check_borders(void)
{
	Window root, child;
	static int rx=1, ry=1, wx, wy;
	int n, mask;

	XQueryPointer(display, DefaultRootWindow(display),
		      &root, &child, &rx, &ry, &wx, &wy, &mask);
        wx = DisplayWidth(display, DefaultScreen(display)) - 1;
        wy = DisplayHeight(display, DefaultScreen(display)) - 1;
	mask = 0;

        for (n = 0; n < n_cmd; n++) {
		if (cmd[n].border == 0) {
		    if (rx == 0 &&
	                ry >= cmd[n].min && ry <= cmd[n].max) {
			    if(cmd[n].cmd) system(cmd[n].cmd); else mask = 1;
		    }
		} else
		if (cmd[n].border == 1) { 
		    if (rx == wx &&
	                ry >= cmd[n].min && ry <= cmd[n].max) {
			    if(cmd[n].cmd) system(cmd[n].cmd); else mask = 1;
		    }
		} else
		if (cmd[n].border == 2) {
		    if (ry == 0 &&
	                rx >= cmd[n].min && rx <= cmd[n].max) {
			    if(cmd[n].cmd) system(cmd[n].cmd); else mask = 1;
		    }
		} else
		if (cmd[n].border == 3) {
		    if (ry == wy &&
	                rx >= cmd[n].min && rx <= cmd[n].max) {
			    if(cmd[n].cmd) system(cmd[n].cmd); else mask = 1;
		    }
		}
	}
	if (mask)
		pager_raise(pager);
}

/*
 * SubstructureNotifyMask:
 *
 * CirculateNotify, ConfigureNotify, CreateNotify, DestroyNotify,
 * GravityNotify, MapNotify, ReparentNotify, UnmapNotify
 */
static void handle_event(XEvent *event)
{
/* 	printf("event %2d, window 0x%x\n", event->type, (int)event->xany.window); */
	switch (event->type) {
	case ButtonPress:
		pager_button_press(pager, event->xbutton.x, event->xbutton.y, event->xbutton.button);
		break;
	case ButtonRelease:
		pager_button_release(pager, event->xbutton.x, event->xbutton.y, event->xbutton.button);
		break;
	case MotionNotify:
		pager_motion(pager, event->xbutton.x, event->xbutton.y);
		break;
	case EnterNotify:
		pager_enter(pager, event->xcrossing.x, event->xcrossing.y);
		break;
	case LeaveNotify:
		pager_leave(pager);
		if (auto_hide) 
			pager_unmap(pager);
		break;
	case Expose:
		pager_expose_event(pager, event);
		break;
	case CreateNotify:
		ignored(event);
		break;
	case DestroyNotify:
		ignored(event);
		break;
	case UnmapNotify:
		ignored(event);
		break;
	case MapNotify:
		ignored(event);
		break;
	case ReparentNotify:
		ignored(event);
		break;
	case ConfigureNotify:
		pager_configure_notify(pager);
		break;
	case CirculateNotify:
		ignored(event);
		break;
	case PropertyNotify:
		pager_property_notify(pager);
		break;
	case ClientMessage:
		ignored(event);
		break;
	default:
	  	fprintf(stderr, "unexpected event %2d, window 0x%x\n", event->type, (int)event->xany.window);
	}
}

static Bool
evpred(Display * d, XEvent *e, XPointer a)
{
        return (True);
}

static void loop(void)
{
	XEvent e;

	while (running) {
		pager_handle_events(pager);
		check_borders();
                if (n_cmd) {
			while (XCheckIfEvent(display, &e, evpred, (XPointer)0)) {
				handle_event(&e);
			}
			usleep(50000);
		} else
		do {
			XNextEvent(display, &e);
			handle_event(&e);
		} while (XPending(display));
	}
}

int ignore_bad_window = 0;

static int xerror_handler(Display *d, XErrorEvent *e)
{
	char buffer[256];

	XGetErrorText(d, e->error_code, buffer, sizeof(buffer) - 1);
	if (e->error_code != BadWindow || !ignore_bad_window) {
		fprintf(stderr, "%s: error: %s:\n"
				" resource id:  0x%x\n"
				" request code: 0x%x\n"
				" minor code:   0x%x\n"
				" serial:       0x%lx\n",
				program_name,
				buffer,
				(unsigned int)e->resourceid,
				e->request_code,
				e->minor_code,
				(unsigned long)e->serial);
	}
	if (e->error_code != BadWindow)
		exit(1);
	return 0;
}

enum {
	OPT_DISPLAY,
	OPT_HELP,
	OPT_ZOOM,
	OPT_TRANSIENT,
	OPT_TRAY,
	OPT_VERSION,
	NUM_OPTIONS
};

static struct option options[NUM_OPTIONS + 1] = {
	{ "display",     1 },
	{ "help",        0 },
	{ "zoom",        0 },
	{ "transient",   0 },
	{ "tray",        0 },
	{ "version",     0 },
	{ NULL,          0 }
};

/* -- configuration -- */
static const char *display_name = NULL;
static char *window_font = NULL;
static char *popup_font = NULL;
static char *geometry = NULL;
static double opacity = 1.0;
static int show_popups = 1;
static int show_sticky = 1;
static int show_titles = 1;
static int allow_cover = 1;
static int show_icons = 1;
static enum pager_layer layer = LAYER_NORMAL;
int opt_zoom = 0;
int opt_tray = 0;
int opt_transient = 0;
int cols_init = -1;
int rows_init = -1;
int desk_cols_init = -1;
int desk_rows_init = -1;
int action_button[4] = { 0, 1, 2, 3};

static int option_handler(int opt, const char *arg)
{
	switch (opt) {
	case OPT_DISPLAY:
		display_name = arg;
		break;
	case OPT_ZOOM:
		opt_zoom = 1;
		break;
	case OPT_TRANSIENT:
		opt_transient = 1;
		break;
	case OPT_TRAY:
		opt_tray = 1;
		break;
	case OPT_HELP:
		printf(
"Usage: %s [OPTION]...\n"
"\n"
"  -display NAME      X server to connect to\n"
"  -help              display this help and exit\n"
"  -zoom              start with zoom activated\n"
"  -transient         set transient mode (when called from systray)\n"
"  -tray              embed pager in system tray\n"
"  -version           output version information and exit\n"
"\n"
"Fonts:\n"
"  Example: Verdana:size=7\n"
"  Run `fc-list' to see available fonts.\n"
"\n"
"Mouse buttons:\n"
"  1: change desk or page, activate window, move window to other desk\n"
"  2: activate/disactivate zoom\n"
"  3: move window around pages and desks\n"
"\n"
"Config file:    ~/.config/netwmpager/config\n"
"Example config: " DATADIR "/netwmpager/config-example\n"
"\n"
"Report bugs to <tihirvon@gmail.com>.\n" , program_name);
		exit(0);
	case OPT_VERSION:
		printf("netwmpager " VERSION "\nCopyright 2004-2005 Timo Hirvonen\n");
		exit(0);
	}
	return 0;
}

static void load_config(void)
{
	char *str;
	int n;
	double d;

	sconf_load();

	if (sconf_get_int_option("cols", &cols_init) && (cols_init < 1 && cols_init != -1)) {
		fprintf(stderr, "%s: cols must be positive integer or -1\n", program_name);
		cols_init = 1;
	}
	if (sconf_get_int_option("rows", &rows_init) && (rows_init < 1 && rows_init != -1)) {
		fprintf(stderr, "%s: rows must be positive integer or -1\n", program_name);
		rows_init = 1;
	}

	if (sconf_get_int_option("desk_cols", &desk_cols_init) && (desk_cols_init < 1 && desk_cols_init != -1)) {
		fprintf(stderr, "%s: desk_cols must be positive integer or -1\n", program_name);
		desk_cols_init = 1;
	}
	if (sconf_get_int_option("desk_rows", &desk_rows_init) && (desk_rows_init < 1 && desk_rows_init != -1)) {
		fprintf(stderr, "%s: desk_rows must be positive integer or -1\n", program_name);
		desk_rows_init = 1;
	}

	if (sconf_get_flt_option("opacity", &opacity) && (opacity < 0.0 || opacity > 1.0)) {
		fprintf(stderr, "%s: opacity must be between 0.0 and 1.0\n", program_name);
		opacity = 1.0;
	}
	if (opt_transient) {
		sconf_get_str_option("geometry_transient", &geometry);
		sconf_get_int_option("border_width_transient", &border_width);
	} else
	if (opt_tray) {
		sconf_get_str_option("geometry_tray", &geometry);
		sconf_get_int_option("border_width_tray", &border_width);
	} else {
		sconf_get_str_option("geometry", &geometry);
		sconf_get_int_option("border_width", &border_width);
	}

	sconf_get_str_option("popup_font", &popup_font);
	sconf_get_str_option("window_font", &window_font);
	sconf_get_bool_option("show_popups", &show_popups);
	sconf_get_bool_option("show_sticky", &show_sticky);
	sconf_get_bool_option("show_titles", &show_titles);
	sconf_get_bool_option("allow_cover", &allow_cover);
	sconf_get_bool_option("show_icons", &show_icons);
	sconf_get_bool_option("auto_hide", &auto_hide);
	sconf_get_str_option("border_commands", &border_commands);

	str = border_commands;
 iter_border:
	if (str) {
		cmd = (command *)realloc(cmd, (n_cmd+1)*sizeof(command));
		if (!strncmp(str, "left", 4)) n = 0;
			else
		if (!strncmp(str, "right", 5)) n = 1;
			else
		if (!strncmp(str, "top", 3)) n = 2;
			else
		if (!strncmp(str, "bottom", 6)) n = 3;
		cmd[n_cmd].border = n;
		str = strchr(str, ',');
		if (str) {
			++str;
			cmd[n_cmd].min = (int) (65536 * atof(str) + 0.5);
			str = strchr(str, ',');
		}
		if (str) {
			++str;
			cmd[n_cmd].max = (int) (65536 * atof(str) + 0.5);
			str = strchr(str, ',');
		}
		if (str) {
		  	++str;
			str = strdup(str);
		  	cmd[n_cmd].cmd = str;
			str = strchr(str, '|');
			if (str) *str = '\0';
		  	if (!strcmp(cmd[n_cmd].cmd, "&"))
		  		cmd[n_cmd].cmd = NULL;
			++n_cmd;
		}
	}
	if (str) {
		++str;
		goto iter_border;
	}

	if (sconf_get_str_option("layer", &str)) {
		if (strcmp(str, "below") == 0) {
			layer = LAYER_BELOW;
		} else if (strcmp(str, "normal") == 0) {
			layer = LAYER_NORMAL;
		} else if (strcmp(str, "commute") == 0) {
			layer = LAYER_COMMUTE;
		} else if (strcmp(str, "above") == 0) {
			layer = LAYER_ABOVE;
		} else {
			fprintf(stderr, "%s: layer must be \"below\", \"normal\" or \"above\"\n", program_name);
		}
		free(str);
	}

	if (sconf_get_flt_option("zoom", &d))
		zoom = d;
	if (sconf_get_flt_option("zoom_factor", &d))
		zoom_factor = d;

	if (sconf_get_int_option("action_buttons", &n)) {
		if (n != 123 && n != 132 && n != 213 && n != 231 &&
		    n != 312 && n != 321) {
			fprintf(stderr, "action_buttons %d incorrect : must be a permutation of 123\n", n);
		}
		action_button[1] = n / 100;
		action_button[2] = n / 10 - 10 * action_button[1];
		action_button[3] = n % 10;
	}

	sconf_get_int_option("win_shift_x", &win_shift_x);
	sconf_get_int_option("win_shift_y", &win_shift_y);
	sconf_get_int_option("switch_delay", &switch_delay);

	sconf_get_str_option("active_win_color", &active_win_color);
	sconf_get_str_option("inactive_win_color", &inactive_win_color);
	sconf_get_str_option("active_page_color", &active_page_color);
	sconf_get_str_option("inactive_page_color", &inactive_page_color);
	sconf_get_str_option("win_border_color", &win_border_color);
	sconf_get_str_option("pager_border_color", &pager_border_color);
	sconf_get_str_option("grid_color", &grid_color);
	sconf_get_str_option("desk_grid_color", &desk_grid_color);

	sconf_get_str_option("active_win_font_color", &active_win_font_color);
	sconf_get_str_option("inactive_win_font_color", &inactive_win_font_color);

	sconf_get_str_option("popup_color", &popup_color);
	sconf_get_str_option("popup_font_color", &popup_font_color);

	sconf_free();

	if (opt_tray)
		auto_hide = 0;
}

int main(int argc, char *argv[])
{
	char **args = argv + 1;
	int n, p, nr_args = argc - 1;

	program_name = argv[0];
	nr_args -= options_parse(&args, options, option_handler, NULL);

	load_config();
	if (*args) {
		fprintf(stderr, "%s: too many arguments\n", argv[0]);
		fprintf(stderr, "Try `%s -help' for more information.\n", argv[0]);
		return 1;
	}

	if (x_init(display_name)) {
		fprintf(stderr, "%s: unable to open display %s\n", argv[0],
				display_name ? display_name : "");
		return 1;
	}
	XSetErrorHandler(xerror_handler);

        /* Fix border command dimensions */
	for (n = 0; n < n_cmd; n++) {
		p = cmd[n].border;
		if (p >= 2)
	 		p = DisplayWidth(display, DefaultScreen(display));
		else
			p = DisplayHeight(display, DefaultScreen(display));
		cmd[n].min = (p * cmd[n].min)/65536;
		cmd[n].max = (p * cmd[n].max)/65536;
		printf("Registering command [%d] %d %d %d : %s\n", n, cmd[n].border, cmd[n].min, cmd[n].max, cmd[n].cmd?cmd[n].cmd:"&raise");

	}

	pager = pager_new(geometry, cols_init, rows_init, desk_cols_init, desk_rows_init);
	if (pager == NULL) {
		x_exit();
		return 1;
	}

	/* window title must be set before this! */
	XSynchronize(display, True);

	pager_set_layer(pager, layer);
	pager_set_show_sticky(pager, show_sticky);
	pager_set_show_window_titles(pager, show_titles);
	pager_set_show_window_icons(pager, show_icons);
	pager_set_show_popups(pager, show_popups);
	pager_set_allow_cover(pager, allow_cover);
	if (popup_font) {
		if (pager_set_popup_font(pager, popup_font))
			fprintf(stderr, "%s: could not set font '%s'\n",
					argv[0], popup_font);
	}
	if (window_font) {
		if (pager_set_window_font(pager, window_font))
			fprintf(stderr, "%s: could not set font '%s'\n",
					argv[0], window_font);
	}

	pager_show(pager);
	pager_set_opacity(pager, opacity);

	loop();

	pager_delete(pager);
	x_exit();
	return 0;
}
