/* Bluefish HTML Editor
 * bftextview2_tagedit.c
 *
 * Copyright (C) 2026 Olivier Sessink
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>
#include "bluefish.h"
#include "undo_redo.h"
#include "document.h"			/* doc_unblock_undo_reg() */
#include "bftextview2_private.h"
#include "bftextview2_tagedit.h"

/*#undef DBG_MSG
#define DBG_MSG g_print
*/
typedef struct {
	BluefishTextView *btv;
	GtkWidget *win;
	GtkWidget *entry;
	gchar newtag[128];
	gchar *oldstart;
	gchar *oldend;
	Tfoundblock *fblock;
	GtkTextIter it1, it2, it3, it4;
	guint32 start1_o, end1_o, start2_o, end2_o;
	gint w;
	gint h;
} Ttewin;

#define TEWIN(p) ((Ttewin *)(p))

static void
tewin_cleanup(BluefishTextView * btv)
{
	if (btv->tagedit) {
		gtk_widget_destroy(TEWIN(btv->tagedit)->win);
		g_free(TEWIN(btv->tagedit)->oldstart);
		g_free(TEWIN(btv->tagedit)->oldend);
		g_slice_free(Ttewin, btv->tagedit);
		btv->tagedit = NULL;
	}
}

static void
tagedit_replace_tag(Ttewin *tewin) {
	BluefishTextView *master = BLUEFISH_TEXT_VIEW(tewin->btv)->master;
	
	doc_unre_new_group(master->doc);
	doc_block_undo_reg(master->doc);

	gchar *newstart = g_strconcat("<", tewin->newtag, NULL);
	glong newstartlen = g_utf8_strlen(newstart,-1);
	gchar *newend = g_strconcat("</", tewin->newtag, ">", NULL);
	glong newendlen = g_utf8_strlen(newend,-1);
	
	DBG_MSG("tagedit_replace_tag() about to replace %s with %s\n",tewin->oldstart, newstart);
	DBG_MSG("tagedit_replace_tag() delete in buffer from %ld:%ld\n",(glong)tewin->start1_o, (glong)tewin->end1_o);
	gtk_text_buffer_delete(master->buffer,&tewin->it1,&tewin->it2);
	DBG_MSG("tagedit_replace_tag() register UndoDelete with text %s from %ld:%ld\n",tewin->oldstart, (glong)tewin->start1_o, (glong)tewin->end1_o);
	doc_unre_add(master->doc, tewin->oldstart, tewin->start1_o, tewin->end1_o, UndoDelete);
	DBG_MSG("tagedit_replace_tag() register UndoDelete with text %s from %ld:%ld\n",tewin->oldstart, (glong)tewin->start1_o, (glong)tewin->end1_o);
	gtk_text_buffer_insert(master->buffer,&tewin->it1, newstart, newstartlen);
	DBG_MSG("tagedit_replace_tag() register UndoInsert with text %s from %ld:%ld\n",newstart, (glong)tewin->start1_o, (glong)tewin->start1_o + newstartlen);
	doc_unre_add(master->doc, newstart, tewin->start1_o, tewin->start1_o + newstartlen, UndoInsert);
	
	/* re-calculate the positions of the end tag */
	gint lendiff = strlen(newstart) - (tewin->end1_o - tewin->start1_o);
	gtk_text_buffer_get_iter_at_offset(master->buffer,&tewin->it3, tewin->start2_o+lendiff);
	gtk_text_buffer_get_iter_at_offset(master->buffer,&tewin->it4, tewin->end2_o+lendiff);

	gtk_text_buffer_delete(master->buffer,&tewin->it3,&tewin->it4);
	DBG_MSG("tagedit_replace_tag() about to replace %s with %s\n",tewin->oldend, newend);
	doc_unre_add(master->doc, tewin->oldend, tewin->start2_o+lendiff, tewin->end2_o+lendiff, UndoDelete);
	gtk_text_buffer_insert(master->buffer,&tewin->it3, newend, newendlen);
	doc_unre_add(master->doc, newend, tewin->start2_o+lendiff, tewin->start2_o+lendiff + newendlen, UndoInsert);
	
	doc_unre_new_group(master->doc);
	doc_unblock_undo_reg(master->doc);
	doc_set_modified(master->doc, 1);
	g_free(newstart);
	g_free(newend);
}

static void addmax128chars(Ttewin *tewin, gchar c) {
	char *s=tewin->newtag;
	int i=0;
	while (*s++ && i<128) {
		i++;
	}
	if (i < 128) {
		*(s - 1) = c;
		*s = '\0';
	}
}

gboolean
tewin_check_keypress(BluefishTextView * btv, GdkEventKey * event)
{
	Ttewin *tewin = TEWIN(btv->tagedit);
	if ((event->state & GDK_CONTROL_MASK) || (event->state & GDK_MOD1_MASK)) {
		return FALSE;
	}
	if (event->keyval == GDK_Return) {
		/* activate the current contents */
		tagedit_replace_tag(tewin);
		tewin_cleanup(btv);
		return TRUE;
	} else if (event->keyval == GDK_Escape) {
		tewin_cleanup(btv);
		return TRUE;
	} else if (
		(event->keyval >= 48 && event->keyval <= 59) /* 0-9 */
		|| (event->keyval >= 65 && event->keyval <= 90) /* A-Z */
		|| (event->keyval >= 97 && event->keyval <= 122) /* a-z */
		|| (event->keyval == 95 ) /* _ */
		) {
			addmax128chars(tewin,event->keyval);
			gtk_entry_set_text(GTK_ENTRY(tewin->entry), tewin->newtag);
			return TRUE;
		}
	tewin_cleanup(btv);
	return FALSE;
}

static Ttewin *
tewin_create(BluefishTextView * btv)
{
	Ttewin *tew = g_slice_new0(Ttewin);
	tew->btv = btv;
	tew->win = gtk_window_new(GTK_WINDOW_POPUP);

	gtk_window_set_resizable(GTK_WINDOW(tew->win), FALSE);
	gtk_container_set_border_width(GTK_CONTAINER(tew->win), 1);
	gtk_window_set_decorated(GTK_WINDOW(tew->win), FALSE);

	/* setting the type hint makes wayland *not* create a subsurface (I learned that on IRC), and we need a 
	subsurface, otherwise the popup can't be moved. we also need to make the window transient for the parent
	for the wayland backend to create a subsurface */
	/*gtk_window_set_type_hint(GTK_WINDOW(tew->win), GDK_WINDOW_TYPE_HINT_POPUP_MENU);*/
	gtk_window_set_transient_for(GTK_WINDOW(tew->win), GTK_WINDOW(BFWIN(DOCUMENT(BLUEFISH_TEXT_VIEW(tew->btv->master)->doc)->bfwin)->main_window));
	tew->entry = gtk_entry_new();
	/*g_signal_connect(G_OBJECT(tew->entry), "changed", G_CALLBACK(tew_selection_changed_lcb), tew);*/
	gtk_container_add(GTK_CONTAINER(tew->win), tew->entry);
	gtk_widget_show(tew->entry);
	return tew;
}

/* returns TRUE if window is popped-up lower than the cursor,
returns FALSE if window is popped-up higher than the cursor (because cursor is low in the screen) */
static gboolean
tewin_position_at_cursor(BluefishTextView * btv)
{
	GtkTextIter it;
	GdkRectangle rect;
	GdkScreen *screen;
	gint x, y, sh;
	GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(btv));
	screen = gtk_widget_get_screen(GTK_WIDGET(btv));

	gtk_text_buffer_get_iter_at_mark(buffer, &it, gtk_text_buffer_get_insert(buffer));
	gtk_text_view_get_iter_location(GTK_TEXT_VIEW(btv), &it, &rect);
	gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT, rect.x, rect.y, &rect.x,
										  &rect.y);
	gdk_window_get_origin(gtk_text_view_get_window(GTK_TEXT_VIEW(btv), GTK_TEXT_WINDOW_TEXT), &x, &y);

	sh = gdk_screen_get_height(screen);
	if (rect.y + y + TEWIN(btv->tagedit)->h > sh) {
		DBG_MSG("tewin_position_at_cursor, popup above cursuor, rect.y+y=%d + rect.height(%d)-tew->h(%d)=%d, tew->h=%d, sh=%d\n"
				, rect.y + y,rect.height,TEWIN(btv->tagedit)->h
				, rect.y + y + rect.height - TEWIN(btv->tagedit)->h
				, TEWIN(btv->tagedit)->h, sh);
		gtk_window_move(GTK_WINDOW(TEWIN(btv->tagedit)->win), rect.x + x,
						rect.y + y + rect.height - TEWIN(btv->tagedit)->h);
		return FALSE;
	} else {
		DBG_MSG("tewin_position_at_cursor, popup below cursuor, rect.y+y=%d, tew->h=%d, sh=%d\n", rect.y + y, TEWIN(btv->tagedit)->h, sh);
		gtk_window_move(GTK_WINDOW(TEWIN(btv->tagedit)->win), rect.x + x, rect.y + y);
		return TRUE;
	}
}
 
void
tagedit_stop(BluefishTextView * btv)
{
	tewin_cleanup(btv);
}

static gchar * shortestnotnull(gchar *first, gchar *second) {
	g_print("shortestnotnull() first=%s, second=%s\n",first,second);
	if (first==NULL || (second!=NULL && first > second))
		return second;
	return first;
}

void
tagedit_run(BluefishTextView * btv)
{
	GtkTextIter cursorpos;
	BluefishTextView *master = BLUEFISH_TEXT_VIEW(btv->master);
	Tfoundblock *fblock = NULL;

	if (G_UNLIKELY(!master->bflang || !master->bflang->st))
		return;

	gtk_text_buffer_get_iter_at_mark(btv->buffer, &cursorpos, gtk_text_buffer_get_insert(btv->buffer));
	
	guint offset = gtk_text_iter_get_offset(&cursorpos);
	GtkTextIter it1, it2, it3, it4;
	fblock = bftextview2_get_in_active_block_at_offset(btv, offset, &it1,&it2,&it3,&it4);
	DBG_MSG("tagedit_run(), got fblock=%p at offset %d\n",fblock,offset);
	if (!fblock)
		return;
	DBG_MSG("tagedit_run(), have block from %d:%d and with end %d:%d\n",gtk_text_iter_get_offset(&it1),gtk_text_iter_get_offset(&it2)
						,gtk_text_iter_get_offset(&it3),gtk_text_iter_get_offset(&it4));

	/* only run tagedit if you are within the start and end position of the starttag of the block and if you are in a xml 
	or html tag, and we have the correponding end tag*/
	if (!gtk_text_iter_in_range(&cursorpos,&it1,&it2) && !gtk_text_iter_equal(&cursorpos, &it2)) {
		DBG_MSG("tagedit_run(), cursor not in start tag boundary, return\n");
		return;
	}
	gchar *starttag, *endtag;
	starttag = gtk_text_buffer_get_text(master->buffer, &it1, &it2, TRUE);
	endtag = gtk_text_buffer_get_text(master->buffer, &it3, &it4, TRUE);
	if (!starttag || !endtag) {
		g_warning("tagedit_run() error: got NULL from gtk_text_buffer_get_text()\n");
		return;
	}
	if (!starttag || !endtag || strlen(starttag)<2 || starttag[0]!='<' || strlen(endtag)<=3 || endtag[0]!='<' || endtag[1]!='/') {
		DBG_MSG("tagedit_run(), %s and %s doesn't look like a html or xml tag, return\n",starttag,endtag);
		g_free(starttag);
		g_free(endtag);
		return;
	} 
	
	/* now see what the starttag looks like. it might be <h1 or <food calories="300"> or <food> and we need only 
	the tag name. We need to adjust the offsets and iterators accordingly */
	gint lendiff=0;
	gchar *shortest = shortestnotnull(strchr(starttag, ' '), strchr(starttag, '>'));
	if (shortest) {
		lendiff = strlen(shortest);
		*shortest='\0';
		g_print("lendiff=%d, new starttag=%s\n",lendiff,starttag);
		gtk_text_iter_backward_chars(&it2,lendiff);
	}
	
	
	DBG_MSG("tagedit_run(), starttag=%s, endtag=%s\n",starttag, endtag);
	if (!btv->tagedit) {
		btv->tagedit = tewin_create(btv);
	}
	TEWIN(btv->tagedit)->it1 = it1;
	TEWIN(btv->tagedit)->it2 = it2;
	TEWIN(btv->tagedit)->it3 = it3;
	TEWIN(btv->tagedit)->it4 = it4;
	TEWIN(btv->tagedit)->newtag[0]='\0';
	TEWIN(btv->tagedit)->start1_o = fblock->start1_o;
	TEWIN(btv->tagedit)->end1_o = fblock->end1_o - lendiff;
	TEWIN(btv->tagedit)->start2_o = fblock->start2_o;
	TEWIN(btv->tagedit)->end2_o = fblock->end2_o;
	TEWIN(btv->tagedit)->oldstart = starttag;
	TEWIN(btv->tagedit)->oldend = endtag;
	gtk_entry_set_text(GTK_ENTRY(TEWIN(btv->tagedit)->entry), starttag+1);
	tewin_position_at_cursor(btv);
	gtk_widget_show(TEWIN(btv->tagedit)->win);
}
