#include "button.h"

#include <string.h>
#include <stdio.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <math.h>
#include <pango/pangocairo.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>

#include "window.h"
#include "server.h"
#include "panel.h"
#include "timer.h"
#include "common.h"

char *button_get_tooltip(void *obj);
void button_init_fonts();
int button_compute_desired_size(void *obj);
void button_dump_geometry(void *obj, int indent);

void default_button()
{
}

Button *create_button()
{
	Button *button = calloc(1, sizeof(Button));
	button->backend = calloc(1, sizeof(ButtonBackend));
	button->backend->centered = TRUE;
	button->backend->font_color.alpha = 0.5;
	return button;
}

gpointer create_button_frontend(gconstpointer arg, gpointer data)
{
	Button *button_backend = (Button *)arg;

	Button *button_frontend = calloc(1, sizeof(Button));
	button_frontend->backend = button_backend->backend;
	button_backend->backend->instances = g_list_append(button_backend->backend->instances, button_frontend);
	button_frontend->frontend = calloc(1, sizeof(ButtonFrontend));
	return button_frontend;
}

void destroy_button(void *obj)
{
	Button *button = (Button *)obj;
	if (button->frontend) {
		// This is a frontend element
		if (button->frontend->icon) {
			imlib_context_set_image(button->frontend->icon);
			imlib_free_image();
			button->frontend->icon = NULL;
		}
		button->backend->instances = g_list_remove_all(button->backend->instances, button);
		free_and_null(button->frontend);
		remove_area(&button->area);
		free_area(&button->area);
		free_and_null(button);
	} else {
		// This is a backend element
		free_and_null(button->backend->text);
		free_and_null(button->backend->icon_name);
		free_and_null(button->backend->tooltip);

		button->backend->bg = NULL;
		pango_font_description_free(button->backend->font_desc);
		button->backend->font_desc = NULL;
		free_and_null(button->backend->lclick_command);
		free_and_null(button->backend->mclick_command);
		free_and_null(button->backend->rclick_command);
		free_and_null(button->backend->dwheel_command);
		free_and_null(button->backend->uwheel_command);

		if (button->backend->instances) {
			fprintf(stderr, "Error: Attempt to destroy backend while there are still frontend instances!\n");
			exit(-1);
		}
		free(button->backend);
		free(button);
	}
}

void init_button()
{
	GList *to_remove = panel_config.button_list;
	for (int k = 0; k < strlen(panel_items_order) && to_remove; k++) {
		if (panel_items_order[k] == 'P') {
			to_remove = to_remove->next;
		}
	}

	if (to_remove) {
		if (to_remove == panel_config.button_list) {
			g_list_free_full(to_remove, destroy_button);
			panel_config.button_list = NULL;
		} else {
			// Cut panel_config.button_list
			if (to_remove->prev)
				to_remove->prev->next = NULL;
			to_remove->prev = NULL;
			// Remove all elements of to_remove and to_remove itself
			g_list_free_full(to_remove, destroy_button);
		}
	}

	button_init_fonts();
	for (GList *l = panel_config.button_list; l; l = l->next) {
		Button *button = l->data;

		// Set missing config options
		if (!button->backend->bg)
			button->backend->bg = &g_array_index(backgrounds, Background, 0);
	}
}

void init_button_panel(void *p)
{
	Panel *panel = (Panel *)p;

	// Make sure this is only done once if there are multiple items
	if (panel->button_list && ((Button *)panel->button_list->data)->frontend)
		return;

	// panel->button_list is now a copy of the pointer panel_config.button_list
	// We make it a deep copy
	panel->button_list = g_list_copy_deep(panel_config.button_list, create_button_frontend, NULL);

	load_icon_themes();

	for (GList *l = panel->button_list; l; l = l->next) {
		Button *button = l->data;
		button->area.bg = button->backend->bg;
		button->area.paddingx = button->backend->paddingx;
		button->area.paddingy = button->backend->paddingy;
		button->area.paddingxlr = button->backend->paddingxlr;
		button->area.parent = panel;
		button->area.panel = panel;
		button->area._dump_geometry = button_dump_geometry;
		button->area._compute_desired_size = button_compute_desired_size;
		snprintf(button->area.name, sizeof(button->area.name), "Button");
		button->area._draw_foreground = draw_button;
		button->area.size_mode = LAYOUT_FIXED;
		button->area._resize = resize_button;
		button->area._get_tooltip_text = button_get_tooltip;
		button->area._is_under_mouse = full_width_area_is_under_mouse;
		button->area.has_mouse_press_effect =
		    panel_config.mouse_effects &&
		    (button->area.has_mouse_over_effect = button->backend->lclick_command || button->backend->mclick_command ||
		                                          button->backend->rclick_command || button->backend->uwheel_command ||
		                                          button->backend->dwheel_command);

		button->area.resize_needed = TRUE;
		button->area.on_screen = TRUE;
		instantiate_area_gradients(&button->area);

		button_reload_icon(button);
	}
}

void button_init_fonts()
{
	for (GList *l = panel_config.button_list; l; l = l->next) {
		Button *button = l->data;
		if (!button->backend->font_desc)
			button->backend->font_desc = pango_font_description_from_string(get_default_font());
	}
}

void button_default_font_changed()
{
	gboolean needs_update = FALSE;
	for (GList *l = panel_config.button_list; l; l = l->next) {
		Button *button = l->data;

		if (!button->backend->has_font) {
			pango_font_description_free(button->backend->font_desc);
			button->backend->font_desc = NULL;
			needs_update = TRUE;
		}
	}
	if (!needs_update)
		return;

	button_init_fonts();
	for (int i = 0; i < num_panels; i++) {
		for (GList *l = panels[i].button_list; l; l = l->next) {
			Button *button = l->data;

			if (!button->backend->has_font) {
				button->area.resize_needed = TRUE;
				schedule_redraw(&button->area);
			}
		}
	}
	schedule_panel_redraw();
}

void button_reload_icon(Button *button)
{
	free_icon(button->frontend->icon);
	free_icon(button->frontend->icon_hover);
	free_icon(button->frontend->icon_pressed);
	button->frontend->icon = NULL;
	button->frontend->icon_hover = NULL;
	button->frontend->icon_pressed = NULL;

	button->frontend->icon_load_size = button->frontend->iconw;

	if (!button->backend->icon_name)
		return;

	char *new_icon_path = get_icon_path(icon_theme_wrapper, button->backend->icon_name, button->frontend->iconw, TRUE);
	if (new_icon_path)
		button->frontend->icon = load_image(new_icon_path, TRUE);
	free(new_icon_path);
	// On loading error, fallback to default
	if (!button->frontend->icon) {
		new_icon_path = get_icon_path(icon_theme_wrapper, DEFAULT_ICON, button->frontend->iconw, TRUE);
		if (new_icon_path)
			button->frontend->icon = load_image(new_icon_path, TRUE);
		free(new_icon_path);
	}
	Imlib_Image original = button->frontend->icon;
	button->frontend->icon = scale_icon(button->frontend->icon, button->frontend->iconw);
	free_icon(original);

	if (panel_config.mouse_effects) {
		button->frontend->icon_hover = adjust_icon(button->frontend->icon,
		                                           panel_config.mouse_over_alpha,
		                                           panel_config.mouse_over_saturation,
		                                           panel_config.mouse_over_brightness);
		button->frontend->icon_pressed = adjust_icon(button->frontend->icon,
		                                             panel_config.mouse_pressed_alpha,
		                                             panel_config.mouse_pressed_saturation,
		                                             panel_config.mouse_pressed_brightness);
	}
	schedule_redraw(&button->area);
}

void button_default_icon_theme_changed()
{
	for (int i = 0; i < num_panels; i++) {
		for (GList *l = panels[i].button_list; l; l = l->next) {
			Button *button = l->data;
			button_reload_icon(button);
		}
	}
	schedule_panel_redraw();
}

void cleanup_button()
{
	// Cleanup frontends
	for (int i = 0; i < num_panels; i++) {
		g_list_free_full(panels[i].button_list, destroy_button);
		panels[i].button_list = NULL;
	}

	// Cleanup backends
	g_list_free_full(panel_config.button_list, destroy_button);
	panel_config.button_list = NULL;
}

int button_compute_desired_size(void *obj)
{
	Button *button = (Button *)obj;
	Panel *panel = (Panel *)button->area.panel;
	int horiz_padding = (panel_horizontal ? button->area.paddingxlr : button->area.paddingy);
	int vert_padding = (panel_horizontal ? button->area.paddingy : button->area.paddingxlr);
	int interior_padding = button->area.paddingx;

	int icon_w, icon_h;
	if (button->backend->icon_name) {
		if (panel_horizontal)
			icon_h = icon_w = button->area.height - top_bottom_border_width(&button->area) - 2 * vert_padding;
		else
			icon_h = icon_w = button->area.width - left_right_border_width(&button->area) - 2 * horiz_padding;
		if (button->backend->max_icon_size) {
			icon_w = MIN(icon_w, button->backend->max_icon_size);
			icon_h = MIN(icon_h, button->backend->max_icon_size);
		}
	} else {
		icon_h = icon_w = 0;
	}

	int txt_height_ink, txt_height, txt_width;
	if (button->backend->text) {
		if (panel_horizontal) {
			get_text_size2(button->backend->font_desc,
						   &txt_height_ink,
						   &txt_height,
						   &txt_width,
						   panel->area.height,
						   panel->area.width,
						   button->backend->text,
						   strlen(button->backend->text),
						   PANGO_WRAP_WORD_CHAR,
						   PANGO_ELLIPSIZE_NONE,
						   FALSE);
		} else {
			get_text_size2(button->backend->font_desc,
						   &txt_height_ink,
						   &txt_height,
						   &txt_width,
						   panel->area.height,
						   button->area.width - icon_w - (icon_w ? interior_padding : 0) -
							   2 * horiz_padding - left_right_border_width(&button->area),
						   button->backend->text,
						   strlen(button->backend->text),
						   PANGO_WRAP_WORD_CHAR,
						   PANGO_ELLIPSIZE_NONE,
						   FALSE);
		}
	} else {
		txt_height_ink = txt_height = txt_width = 0;
	}

	if (panel_horizontal) {
		int new_size = txt_width + icon_w + (txt_width && icon_w ? interior_padding : 0);
		new_size += 2 * horiz_padding + left_right_border_width(&button->area);
		return new_size;
	} else {
		int new_size;
		new_size = txt_height + 2 * vert_padding + top_bottom_border_width(&button->area);
		new_size = MAX(new_size, icon_h + 2 * vert_padding + top_bottom_border_width(&button->area));
		return new_size;
	}
}

gboolean resize_button(void *obj)
{
	Button *button = (Button *)obj;
	Panel *panel = (Panel *)button->area.panel;
	int horiz_padding = (panel_horizontal ? button->area.paddingxlr : button->area.paddingy);
	int vert_padding = (panel_horizontal ? button->area.paddingy : button->area.paddingxlr);
	int interior_padding = button->area.paddingx;

	int icon_w, icon_h;
	if (button->backend->icon_name) {
		if (panel_horizontal)
			icon_h = icon_w = button->area.height - top_bottom_border_width(&button->area) - 2 * vert_padding;
		else
			icon_h = icon_w = button->area.width - left_right_border_width(&button->area) - 2 * horiz_padding;
		if (button->backend->max_icon_size) {
			icon_w = MIN(icon_w, button->backend->max_icon_size);
			icon_h = MIN(icon_h, button->backend->max_icon_size);
		}
	} else {
		icon_h = icon_w = 0;
	}

	button->frontend->iconw = icon_w;
	button->frontend->iconh = icon_h;
	if (button->frontend->icon_load_size != button->frontend->iconw)
		button_reload_icon(button);

	int txt_height_ink, txt_height, txt_width;
	if (button->backend->text) {
		if (panel_horizontal) {
			get_text_size2(button->backend->font_desc,
						   &txt_height_ink,
						   &txt_height,
						   &txt_width,
						   panel->area.height,
						   panel->area.width,
						   button->backend->text,
						   strlen(button->backend->text),
						   PANGO_WRAP_WORD_CHAR,
						   PANGO_ELLIPSIZE_NONE,
						   FALSE);
		} else {
			get_text_size2(button->backend->font_desc,
						   &txt_height_ink,
						   &txt_height,
						   &txt_width,
						   panel->area.height,
						   button->area.width - icon_w - (icon_w ? interior_padding : 0) -
							   2 * horiz_padding - left_right_border_width(&button->area),
						   button->backend->text,
						   strlen(button->backend->text),
						   PANGO_WRAP_WORD_CHAR,
						   PANGO_ELLIPSIZE_NONE,
						   FALSE);
		}
	} else {
		txt_height_ink = txt_height = txt_width = 0;
	}

	gboolean result = FALSE;
	if (panel_horizontal) {
		int new_size = txt_width + icon_w + (txt_width && icon_w ? interior_padding : 0);
		new_size += 2 * horiz_padding + left_right_border_width(&button->area);
		if (new_size != button->area.width) {
			button->area.width = new_size;
			result = TRUE;
		}
	} else {
		int new_size;
		new_size = txt_height + 2 * vert_padding + top_bottom_border_width(&button->area);
		new_size = MAX(new_size, icon_h + 2 * vert_padding + top_bottom_border_width(&button->area));
		if (new_size != button->area.height) {
			button->area.height = new_size;
			result = TRUE;
		}
	}
	button->frontend->textw = txt_width;
	button->frontend->texth = txt_height;
	if (button->backend->centered) {
		if (icon_w) {
			button->frontend->icony = (button->area.height - icon_h) / 2;
			button->frontend->iconx = (button->area.width - txt_width - (txt_width ? interior_padding : 0) - icon_w) / 2;
			button->frontend->texty = (button->area.height - txt_height) / 2;
			button->frontend->textx = button->frontend->iconx + icon_w + interior_padding;
		} else {
			button->frontend->texty = (button->area.height - txt_height) / 2;
			button->frontend->textx = (button->area.width - txt_width) / 2;
		}
	} else {
		if (icon_w) {
			button->frontend->icony = (button->area.height - icon_h) / 2;
			button->frontend->iconx = left_border_width(&button->area) + horiz_padding;
			button->frontend->texty = (button->area.height - txt_height) / 2;
			button->frontend->textx = button->frontend->iconx + icon_w + interior_padding;
		} else {
			button->frontend->texty = (button->area.height - txt_height) / 2;
			button->frontend->textx = left_border_width(&button->area) + horiz_padding;
		}
	}

	schedule_redraw(&button->area);

	return result;
}

void draw_button(void *obj, cairo_t *c)
{
	Button *button = obj;

	if (button->frontend->icon) {
		// Render icon
		Imlib_Image image;
		if (panel_config.mouse_effects) {
			if (button->area.mouse_state == MOUSE_OVER)
				image = button->frontend->icon_hover ? button->frontend->icon_hover : button->frontend->icon;
			else if (button->area.mouse_state == MOUSE_DOWN)
				image = button->frontend->icon_pressed ? button->frontend->icon_pressed : button->frontend->icon;
			else
				image = button->frontend->icon;
		} else {
			image = button->frontend->icon;
		}

		imlib_context_set_image(image);
		render_image(button->area.pix, button->frontend->iconx, button->frontend->icony);
	}

	// Render text
	if (button->backend->text) {
		PangoLayout *layout = pango_cairo_create_layout(c);

		pango_layout_set_font_description(layout, button->backend->font_desc);
		pango_layout_set_width(layout, button->frontend->textw * PANGO_SCALE);
		pango_layout_set_alignment(layout, button->backend->centered ? PANGO_ALIGN_CENTER : PANGO_ALIGN_LEFT);
		pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
		pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
		pango_layout_set_text(layout, button->backend->text, strlen(button->backend->text));

		pango_cairo_update_layout(c, layout);
		draw_text(layout,
				  c,
				  button->frontend->textx,
				  button->frontend->texty,
				  &button->backend->font_color,
				  panel_config.font_shadow);

		g_object_unref(layout);
	}
}

void button_dump_geometry(void *obj, int indent)
{
	Button *button = obj;

	if (button->frontend->icon) {
		Imlib_Image tmp = imlib_context_get_image();
		imlib_context_set_image(button->frontend->icon);
		fprintf(stderr,
		        "%*sIcon: x = %d, y = %d, w = %d, h = %d\n",
		        indent,
		        "",
		        button->frontend->iconx,
		        button->frontend->icony,
		        imlib_image_get_width(),
		        imlib_image_get_height());
		if (tmp)
			imlib_context_set_image(tmp);
	}
	fprintf(stderr,
	        "%*sText: x = %d, y = %d, w = %d, align = %s, text = %s\n",
	        indent,
	        "",
	        button->frontend->textx,
	        button->frontend->texty,
	        button->frontend->textw,
	        button->backend->centered ? "center" : "left",
	        button->backend->text);
}

void button_action(void *obj, int mouse_button, int x, int y)
{
	Button *button = obj;
	char *command = NULL;
	switch (mouse_button) {
	case 1:
		command = button->backend->lclick_command;
		break;
	case 2:
		command = button->backend->mclick_command;
		break;
	case 3:
		command = button->backend->rclick_command;
		break;
	case 4:
		command = button->backend->uwheel_command;
		break;
	case 5:
		command = button->backend->dwheel_command;
		break;
	}
	if (command) {
		char *full_cmd = g_strdup_printf("export BUTTON_X=%d;"
		                                 "export BUTTON_Y=%d;"
		                                 "export BUTTON_W=%d;"
		                                 "export BUTTON_H=%d; %s",
		                                 x,
		                                 y,
		                                 button->area.width,
		                                 button->area.height,
		                                 command);
		pid_t pid = fork();
		if (pid < 0) {
			fprintf(stderr, "Could not fork\n");
		} else if (pid == 0) {
			// Child process
			// Allow children to exist after parent destruction
			setsid();
			// Run the command
			execl("/bin/sh", "/bin/sh", "-c", full_cmd, NULL);
			fprintf(stderr, "Failed to execlp %s\n", full_cmd);
			exit(1);
		}
	}
}

char *button_get_tooltip(void *obj)
{
	Button *button = obj;

	if (button->backend->tooltip && strlen(button->backend->tooltip) > 0)
		return strdup(button->backend->tooltip);
	return NULL;
}
