/* ZoiteChat
 * Copyright (C) 1998-2010 Peter Zelezny.
 * Copyright (C) 2009-2013 Berke Viktor.
 * Copyright (C) 2026 deepend-tildeclub.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "../../fe-gtk.h"

#include <math.h>

#include "../theme-access.h"
#include "../theme-css.h"
#include "../theme-manager.h"
#include "../theme-runtime.h"
#include "../../xtext-color.h"
#include "../../../common/zoitechat.h"

struct session *current_sess;
struct session *current_tab;
struct session *lastact_sess;
struct zoitechatprefs prefs;

static gboolean stub_dark_active;
static gboolean stub_gtk3_active;
static ThemeSemanticToken stub_last_color_token;
static int stub_runtime_get_color_calls;
static int stub_runtime_widget_calls;
static int stub_runtime_xtext_calls;
static int stub_runtime_xtext_mapped_calls;
static size_t stub_runtime_xtext_last_len;
static ThemeGtkPaletteMap stub_last_gtk_map;
static gboolean stub_last_gtk_map_valid;
static gboolean gtk_available;

static GdkRGBA stub_light_colors[THEME_TOKEN_COUNT];
static GdkRGBA stub_dark_colors[THEME_TOKEN_COUNT];

void
setup_apply_real (const ThemeChangedEvent *event)
{
	(void) event;
}

void
gtkutil_append_font_css (GString *css, const PangoFontDescription *font_desc)
{
	(void) css;
	(void) font_desc;
}

gboolean
fe_dark_mode_is_enabled_for (unsigned int mode)
{
	return mode == ZOITECHAT_DARK_MODE_DARK;
}

gboolean
theme_runtime_apply_mode (unsigned int mode, gboolean *dark_active)
{
	if (mode == ZOITECHAT_DARK_MODE_DARK)
		stub_dark_active = TRUE;
	if (mode == ZOITECHAT_DARK_MODE_LIGHT)
		stub_dark_active = FALSE;
	if (dark_active)
		*dark_active = stub_dark_active;
	return TRUE;
}

void
theme_runtime_load (void)
{
}

gboolean
theme_runtime_save (void)
{
	return TRUE;
}

void
theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col)
{
	(void) token;
	(void) col;
}

void
theme_runtime_dark_set_color (ThemeSemanticToken token, const GdkRGBA *col)
{
	(void) token;
	(void) col;
}

gboolean
theme_runtime_mode_has_user_colors (gboolean dark_mode)
{
	(void) dark_mode;
	return FALSE;
}

gboolean
theme_runtime_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
{
	g_assert_nonnull (out_rgba);
	stub_runtime_get_color_calls++;
	stub_last_color_token = token;
	*out_rgba = stub_dark_active ? stub_dark_colors[token] : stub_light_colors[token];
	return TRUE;
}

void
theme_runtime_get_widget_style_values (ThemeWidgetStyleValues *out_values)
{
	stub_runtime_widget_calls++;
	gdk_rgba_parse (&out_values->background, "#010203");
	gdk_rgba_parse (&out_values->foreground, "#fdfcfa");
}

void
theme_runtime_get_xtext_colors (XTextColor *palette, size_t palette_len)
{
	size_t i;

	stub_runtime_xtext_calls++;
	stub_runtime_xtext_last_len = palette_len;
	for (i = 0; i < palette_len; i++)
	{
		palette[i].red = (unsigned short) (i + 1);
		palette[i].green = (unsigned short) (i + 2);
		palette[i].blue = (unsigned short) (i + 3);
	}
}

gboolean
theme_runtime_is_dark_active (void)
{
	return stub_dark_active;
}

void
theme_runtime_get_widget_style_values_mapped (const ThemeGtkPaletteMap *gtk_map, ThemeWidgetStyleValues *out_values)
{
	(void) gtk_map;
	theme_runtime_get_widget_style_values (out_values);
}

void
theme_runtime_get_xtext_colors_mapped (const ThemeGtkPaletteMap *gtk_map, XTextColor *palette, size_t palette_len)
{
	size_t i;

	stub_runtime_xtext_mapped_calls++;
	stub_last_gtk_map = *gtk_map;
	stub_last_gtk_map_valid = TRUE;
	stub_runtime_xtext_last_len = palette_len;
	for (i = 0; i < palette_len; i++)
	{
		palette[i].red = (unsigned short) (100 + i);
		palette[i].green = (unsigned short) (200 + i);
		palette[i].blue = (unsigned short) (300 + i);
	}
}

gboolean
theme_gtk3_is_active (void)
{
	return stub_gtk3_active;
}

gboolean
theme_gtk3_lookup_theme_color (const char *name, GdkRGBA *out_color)
{
	(void) name;
	(void) out_color;
	return FALSE;
}

static gboolean
rgba_equal (const GdkRGBA *a, const GdkRGBA *b)
{
	return a->red == b->red && a->green == b->green && a->blue == b->blue && a->alpha == b->alpha;
}

static void
reset_stubs (void)
{
	size_t i;
	char light[32];
	char dark[32];

	stub_dark_active = FALSE;
	stub_last_color_token = THEME_TOKEN_MIRC_0;
	stub_runtime_get_color_calls = 0;
	stub_runtime_widget_calls = 0;
	stub_runtime_xtext_calls = 0;
	stub_runtime_xtext_mapped_calls = 0;
	stub_runtime_xtext_last_len = 0;
	stub_last_gtk_map_valid = FALSE;
	stub_gtk3_active = FALSE;
	for (i = 0; i < THEME_TOKEN_COUNT; i++)
	{
		g_snprintf (light, sizeof (light), "#%02x%02x%02x", (unsigned int) (i + 1), 0x11, 0x22);
		g_snprintf (dark, sizeof (dark), "#%02x%02x%02x", (unsigned int) (i + 1), 0xaa, 0xbb);
		g_assert_true (gdk_rgba_parse (&stub_light_colors[i], light));
		g_assert_true (gdk_rgba_parse (&stub_dark_colors[i], dark));
	}
}

static gboolean
rgba_close (const GdkRGBA *a, const GdkRGBA *b)
{
	return fabs (a->red - b->red) < 0.0001 &&
		fabs (a->green - b->green) < 0.0001 &&
		fabs (a->blue - b->blue) < 0.0001 &&
		fabs (a->alpha - b->alpha) < 0.0001;
}

static void
test_access_semantic_token_routes_directly (void)
{
	ThemeSemanticToken token;
	GdkRGBA color;
	size_t i;

	reset_stubs ();
	for (i = 0; i < theme_palette_token_def_count (); i++)
	{
		const ThemePaletteTokenDef *def = theme_palette_token_def_at (i);

		g_assert_nonnull (def);
		token = def->token;
		g_assert_true (theme_get_color (token, &color));
		g_assert_cmpint (stub_last_color_token, ==, token);
		g_assert_true (rgba_equal (&color, &stub_light_colors[token]));
	}
}

static void
test_access_token_routes_without_legacy_accessor (void)
{
	ThemeSemanticToken token = THEME_TOKEN_MIRC_0;
	GdkRGBA color;
	size_t i;

	reset_stubs ();
	for (i = 0; i < theme_palette_token_def_count (); i++)
	{
		const ThemePaletteTokenDef *def = theme_palette_token_def_at (i);

		g_assert_nonnull (def);
		g_assert_true (theme_palette_legacy_index_to_token (def->legacy_index, &token));
		g_assert_true (theme_get_color (token, &color));
		g_assert_cmpint (stub_last_color_token, ==, token);
		g_assert_true (rgba_equal (&color, &stub_light_colors[token]));
	}
}

static void
test_access_xtext_palette_forwarding (void)
{
	XTextColor palette[4] = { 0 };

	reset_stubs ();
	theme_get_xtext_colors (palette, G_N_ELEMENTS (palette));
	g_assert_cmpint (stub_runtime_xtext_calls, ==, 1);
	g_assert_cmpuint (stub_runtime_xtext_last_len, ==, G_N_ELEMENTS (palette));
	g_assert_cmpuint (palette[0].red, ==, 1);
	g_assert_cmpuint (palette[1].green, ==, 3);
	g_assert_cmpuint (palette[3].blue, ==, 6);
}


static void
test_access_widget_style_forwarding (void)
{
	ThemeWidgetStyleValues values;

	reset_stubs ();
	theme_get_widget_style_values (&values);
	g_assert_cmpint (stub_runtime_widget_calls, ==, 1);
	g_assert_true (fabs (values.background.red - (0x01 / 255.0)) < 0.0001);
	g_assert_true (fabs (values.foreground.green - (0xfc / 255.0)) < 0.0001);
}

static void
test_access_xtext_palette_widget_mapping_when_gtk3_active (void)
{
	GtkWidget *window;
	GtkWidget *label;
	GtkStyleContext *context;
	GtkCssProvider *provider;
	XTextColor palette[2] = { 0 };
	GdkRGBA expected;

	if (!gtk_available)
	{
		g_test_message ("GTK display not available");
		return;
	}

	reset_stubs ();
	stub_gtk3_active = TRUE;
	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	label = gtk_label_new ("mapped");
	gtk_container_add (GTK_CONTAINER (window), label);
	provider = gtk_css_provider_new ();
	gtk_css_provider_load_from_data (provider,
		"label { color: #112233; background-color: #445566; }"
		"label:selected { color: #778899; background-color: #aabbcc; }"
		"label:link { color: #123456; }",
		-1,
		NULL);
	context = gtk_widget_get_style_context (label);
	gtk_style_context_add_provider (context,
		GTK_STYLE_PROVIDER (provider),
		GTK_STYLE_PROVIDER_PRIORITY_USER);
	gtk_widget_realize (window);

	theme_get_xtext_colors_for_widget (label, palette, G_N_ELEMENTS (palette));

	g_assert_cmpint (stub_runtime_xtext_mapped_calls, ==, 1);
	g_assert_cmpint (stub_runtime_xtext_calls, ==, 0);
	g_assert_true (stub_last_gtk_map_valid);
	g_assert_true (gdk_rgba_parse (&expected, "#112233"));
	g_assert_true (rgba_close (&stub_last_gtk_map.text_foreground, &expected));
	g_assert_true (gdk_rgba_parse (&expected, "#445566"));
	g_assert_true (rgba_close (&stub_last_gtk_map.text_background, &expected));
	g_assert_true (gdk_rgba_parse (&expected, "#778899"));
	g_assert_true (rgba_close (&stub_last_gtk_map.selection_foreground, &expected));
	g_assert_true (gdk_rgba_parse (&expected, "#aabbcc"));
	g_assert_true (rgba_close (&stub_last_gtk_map.selection_background, &expected));
	g_assert_true (gdk_rgba_parse (&expected, "#123456"));
	g_assert_true (rgba_close (&stub_last_gtk_map.accent, &expected));
	g_assert_cmpuint (palette[0].red, ==, 100);
	g_assert_cmpuint (palette[1].green, ==, 201);

	gtk_widget_destroy (window);
	g_object_unref (provider);
}

static void
test_access_palette_css_does_not_feed_back_into_sampling (void)
{
	GtkWidget *window;
	GtkWidget *label;
	GtkCssProvider *theme1;
	GtkCssProvider *theme2;
	GdkScreen *screen;
	XTextColor palette[2] = { 0 };
	GdkRGBA old_bg;
	GdkRGBA old_fg;
	GdkRGBA expected;

	if (!gtk_available)
	{
		g_test_message ("GTK display not available");
		return;
	}

	reset_stubs ();
	stub_gtk3_active = TRUE;
	screen = gdk_screen_get_default ();
	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	label = gtk_label_new ("mapped");
	gtk_container_add (GTK_CONTAINER (window), label);
	gtk_widget_realize (window);

	/* Old GTK3 theme active at screen level. */
	theme1 = gtk_css_provider_new ();
	gtk_css_provider_load_from_data (theme1,
		"label { color: #112233; background-color: #445566; }"
		"label:selected { color: #778899; background-color: #aabbcc; }",
		-1, NULL);
	gtk_style_context_add_provider_for_screen (screen,
		GTK_STYLE_PROVIDER (theme1), GTK_STYLE_PROVIDER_PRIORITY_USER);

	/* The app paints the sampled theme colors onto the widget, like the
	 * user list / channel tree / input box palette provider does. */
	g_assert_true (gdk_rgba_parse (&old_bg, "#445566"));
	g_assert_true (gdk_rgba_parse (&old_fg, "#112233"));
	theme_css_apply_palette_widget (label, &old_bg, &old_fg, NULL);

	/* The user switches to a new GTK3 theme. */
	gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme1));
	theme2 = gtk_css_provider_new ();
	gtk_css_provider_load_from_data (theme2,
		"label { color: #00ff00; background-color: #ff0000; }"
		"label:selected { color: #0000ff; background-color: #ffff00; }",
		-1, NULL);
	gtk_style_context_add_provider_for_screen (screen,
		GTK_STYLE_PROVIDER (theme2), GTK_STYLE_PROVIDER_PRIORITY_USER);
	gtk_style_context_reset_widgets (screen);

	/* Re-sampling must see the new theme, not the palette colors the
	 * app applied for the old theme. */
	theme_get_xtext_colors_for_widget (label, palette, G_N_ELEMENTS (palette));

	g_assert_true (stub_last_gtk_map_valid);
	g_assert_true (gdk_rgba_parse (&expected, "#00ff00"));
	g_assert_true (rgba_close (&stub_last_gtk_map.text_foreground, &expected));
	g_assert_true (gdk_rgba_parse (&expected, "#ff0000"));
	g_assert_true (rgba_close (&stub_last_gtk_map.text_background, &expected));

	/* Sampling must not permanently strip the palette class. */
	g_assert_true (gtk_style_context_has_class (gtk_widget_get_style_context (label),
		theme_css_palette_class_name ()));

	gtk_style_context_remove_provider_for_screen (screen, GTK_STYLE_PROVIDER (theme2));
	gtk_widget_destroy (window);
	g_object_unref (theme1);
	g_object_unref (theme2);
}

static void
test_access_dark_light_switch_affects_token_consumers (void)
{
	ThemeSemanticToken token;
	GdkRGBA light;
	GdkRGBA dark;
	reset_stubs ();
	token = THEME_TOKEN_TEXT_FOREGROUND;
	g_assert_true (theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL));
	g_assert_true (theme_get_color (token, &light));
	g_assert_true (rgba_equal (&light, &stub_light_colors[token]));

	g_assert_true (theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_DARK, NULL));
	g_assert_true (theme_get_color (token, &dark));
	g_assert_true (rgba_equal (&dark, &stub_dark_colors[token]));
	g_assert_false (rgba_equal (&light, &dark));
}

int
main (int argc, char **argv)
{
	g_test_init (&argc, &argv, NULL);
	g_test_add_func ("/theme/access/semantic_token_routes_directly", test_access_semantic_token_routes_directly);
	g_test_add_func ("/theme/access/token_routes_without_legacy_accessor", test_access_token_routes_without_legacy_accessor);
	g_test_add_func ("/theme/access/xtext_palette_forwarding", test_access_xtext_palette_forwarding);
	g_test_add_func ("/theme/access/xtext_palette_widget_mapping_when_gtk3_active",
			 test_access_xtext_palette_widget_mapping_when_gtk3_active);
	g_test_add_func ("/theme/access/palette_css_does_not_feed_back_into_sampling",
			 test_access_palette_css_does_not_feed_back_into_sampling);
	g_test_add_func ("/theme/access/widget_style_forwarding", test_access_widget_style_forwarding);
	g_test_add_func ("/theme/access/dark_light_switch_affects_token_consumers",
			 test_access_dark_light_switch_affects_token_consumers);
	gtk_available = gtk_init_check (&argc, &argv);
	return g_test_run ();
}
