#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <semaphore.h>

#include "config.h"

#define EXIT_ACTION 0
#define EXIT_FAIL 1
#define EXIT_DISMISS 2

Display *display;
Window window;
int exit_code = EXIT_DISMISS;

static void die(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(EXIT_FAIL);
}

char *read_stdin_walloc(void)
{
	char *buff = NULL;
	size_t size = 0;
	FILE *memstream = open_memstream(&buff, &size);

	if (!memstream)
		die("open_memstream failed");

	char *temp = malloc(4096);
	if (!temp)
		die("malloc failed");

	size_t len;
	while ((len = fread(temp, 1, 4096, stdin)) > 0)
		fwrite(temp, 1, len, memstream);

	free(temp);
	fclose(memstream);

	return buff;
}

char *invert_color(const char *color)
{
	char c;
	static char inverted[8];

	strcpy(inverted, color);
	for (int i = 1; i < 7; i++)
	{
		c = color[i];
			inverted[i] = (c < 'a') ?
			"0123456789ABCDEF"[15 - (c - '0' - (c > '9' ? 7 : 0))] :
			"0123456789abcdef"[15 - (c - 'a')];
	}

	inverted[7] = '\0';
	return inverted;
}

int get_max_len(char *string, XftFont *font, int max_text_width)
{
	int eol = strlen(string);
	XGlyphInfo info;
	XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);

	if (info.width > max_text_width)
	{
		eol = max_text_width / font->max_advance_width;
		info.width = 0;

		while (info.width < max_text_width)
		{
			eol++;
			XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
		}

		eol--;
	}

	for (int i = 0; i < eol; i++)
		if (string[i] == '\n')
		{
			string[i] = ' ';
			return ++i;
		}

	if (info.width <= max_text_width)
		return eol;

	int temp = eol;

	while (string[eol] != ' ' && eol)
		--eol;

	if (eol == 0)
		return temp;
	else
		return ++eol;
}

void expire(int sig)
{
	XEvent event;
	event.type = ButtonPress;
	event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
	XSendEvent(display, window, 0, 0, &event);
	XFlush(display);
}

int main(int argc, char *argv[])
{
	int nargc = 0, rstatus;
	regex_t regex;
	regmatch_t match;
	char **nargv = NULL;
	char *body = NULL, *bptr;
	const char *pattern = "#[a-fA-F0-9]{6}";

	if (!isatty(STDIN_FILENO))
	{
		body = read_stdin_walloc();
		bptr = body;

		while (*bptr)
		{
			if (*bptr == '\n' && *(bptr + 1)) nargc++;
			bptr++;
		}

		nargc++;
		bptr = body;

		nargv = malloc((argc + nargc) * sizeof(char *));
		if (!nargv)
			die("malloc failed");

		memcpy(nargv, argv, argc * sizeof(char *));

		int c = argc;
		nargv[c] = body;
		c++;

		while (*bptr)
		{
			if (c == nargc + argc) break;
			if (*bptr == '\n' && *(bptr + 1))
			{
				nargv[c] = bptr+1;
				*bptr = '\0';
				c++;
			}
			bptr++;
		}

		argc += nargc;
		argv = nargv;
	}
	else if (argc == 1)
	{
		sem_unlink("/herbe");
		die("usage: %s [body]", argv[0]);
	}

	struct sigaction act_expire, act_ignore;

	if ((rstatus = regcomp(&regex, pattern, REG_EXTENDED)))
		die("%s: regex compilation failed", argv[0]);

	if (!(rstatus = regexec(&regex, argv[1], 1, &match, 0)))
	{
		background_color = argv[1];
		font_color = invert_color(argv[1]);
		border_color = font_color;
	}

	act_expire.sa_handler = expire;
	act_expire.sa_flags = SA_RESTART;
	sigemptyset(&act_expire.sa_mask);

	act_ignore.sa_handler = SIG_IGN;
	act_ignore.sa_flags = 0;
	sigemptyset(&act_ignore.sa_mask);

	sigaction(SIGALRM, &act_expire, 0);
	sigaction(SIGTERM, &act_expire, 0);
	sigaction(SIGINT, &act_expire, 0);

	sigaction(SIGUSR1, &act_ignore, 0);
	sigaction(SIGUSR2, &act_ignore, 0);

	if (!(display = XOpenDisplay(0)))
		die("Cannot open display");

	int screen = DefaultScreen(display);
	Visual *visual = DefaultVisual(display, screen);
	Colormap colormap = DefaultColormap(display, screen);

	int screen_width = DisplayWidth(display, screen);
	int screen_height = DisplayHeight(display, screen);

	XSetWindowAttributes attributes;
	attributes.override_redirect = True;
	XftColor color;
	XftColorAllocName(display, visual, colormap, background_color, &color);
	attributes.background_pixel = color.pixel;
	XftColorAllocName(display, visual, colormap, border_color, &color);
	attributes.border_pixel = color.pixel;

	int num_of_lines = 0;
	int max_text_width = width - 2 * padding;
	int lines_size = 5;
	char **lines = malloc(lines_size * sizeof(char *));
	if (!lines)
		die("malloc failed");

	XftFont *font = XftFontOpenName(display, screen, font_pattern);

	for (int i = 1; i < argc; i++)
	{
		for (unsigned int eol = get_max_len(argv[i], font, max_text_width); eol; argv[i] += eol, num_of_lines++, eol = get_max_len(argv[i], font, max_text_width))
		{
			if (lines_size <= num_of_lines)
			{
				lines = realloc(lines, (lines_size += 5) * sizeof(char *));
				if (!lines)
					die("realloc failed");
			}

			lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
			if (!lines[num_of_lines])
				die("malloc failed");

			strncpy(lines[num_of_lines], argv[i], eol);
			lines[num_of_lines][eol] = '\0';
		}
	}

	unsigned int x = pos_x;
	unsigned int y = pos_y;
	unsigned int text_height = font->ascent - font->descent;
	unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;

	if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
		x = screen_width - width - border_size * 2 - pos_x;

	if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
		y = screen_height - height - border_size * 2 - pos_y;

	window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
						   CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);

	XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
	XftColorAllocName(display, visual, colormap, font_color, &color);

	XSelectInput(display, window, ExposureMask | ButtonPress);
	XMapWindow(display, window);

	sem_t *mutex = sem_open("/herbe", O_CREAT, 0644, 1);
	sem_wait(mutex);

	sigaction(SIGUSR1, &act_expire, 0);
	sigaction(SIGUSR2, &act_expire, 0);

	if (duration != 0)
		alarm(duration);

	for (;;)
	{
		XEvent event;
		XNextEvent(display, &event);

		if (event.type == Expose)
		{
			XClearWindow(display, window);
			for (int i = 0; i < num_of_lines; i++)
				XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
								  (FcChar8 *)lines[i], strlen(lines[i]));
		}
		else if (event.type == ButtonPress)
		{
			if (event.xbutton.button == DISMISS_BUTTON)
				break;
			else if (event.xbutton.button == ACTION_BUTTON)
			{
				exit_code = EXIT_ACTION;
				break;
			}
		}
	}

	sem_post(mutex);
	sem_close(mutex);

	for (int i = 0; i < num_of_lines; i++)
		free(lines[i]);

	if (nargv)
		free(nargv);
	if (body)
		free(body);
	free(lines);
	regfree(&regex);
	XftDrawDestroy(draw);
	XftColorFree(display, visual, colormap, &color);
	XftFontClose(display, font);
	XCloseDisplay(display);

	return exit_code;
}
