/*
 * Copyright 2008,2010 Vincent Sanders <vince@simtec.co.uk>
 *
 * Framebuffer windowing toolkit core.
 *
 * This file is part of NetSurf, http://www.netsurf-browser.org/
 *
 * NetSurf 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; version 2 of the License.
 *
 * NetSurf 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 <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>

#include <libnsfb.h>
#include <libnsfb_plot.h>
#include <libnsfb_plot_util.h>
#include <libnsfb_event.h>
#include <libnsfb_cursor.h>

#include "utils/utils.h"
#include "utils/log.h"
#include "netsurf/browser_window.h"
#include "netsurf/plotters.h"

#include "framebuffer/gui.h"
#include "framebuffer/fbtk.h"
#include "framebuffer/image_data.h"

#include "widget.h"

#ifdef FBTK_LOGGING

/* tree dump debug, also example of depth first tree walk */
static void
dump_tk_tree(fbtk_widget_t *widget)
{
	widget = fbtk_get_root_widget(widget);
	int indent = 0;

	while (widget != NULL) {
		NSLOG(fbtk, DEBUG, "%*s%p", indent, "", widget);
		if (widget->first_child != NULL) {
			widget = widget->first_child;
			indent += 6;
		} else if (widget->next != NULL) {
			widget = widget->next;
		} else {
			while ((widget->parent != NULL) &&
			       (widget->parent->next == NULL)) {
				widget = widget->parent;
				indent -= 6;
			}
			if (widget->parent != NULL) {
				indent -= 6;
				widget = widget->parent->next;
			} else {
				widget = NULL;
			}
		}
	}
}

#endif 

/* exported function documented in fbtk.h */
void
fbtk_request_redraw(fbtk_widget_t *widget)
{
	fbtk_widget_t *cwidget;
	fbtk_widget_t *pwidget;

	assert(widget != NULL);

	/* if widget not mapped do not try to redraw it */
	pwidget = widget;
	while (pwidget != NULL) {
		if (pwidget->mapped == false)
			return;
		pwidget = pwidget->parent;
	}

	widget->redraw.needed = true;
	widget->redraw.x = 0;
	widget->redraw.y = 0;
	widget->redraw.width = widget->width;
	widget->redraw.height = widget->height;

	NSLOG(fbtk, DEBUG,
	      "redrawing %p %d,%d %d,%d",
	      widget,
	      widget->redraw.x,
	      widget->redraw.y,
	      widget->redraw.width,
	      widget->redraw.height);

	cwidget = widget->last_child;
	while (cwidget != NULL) {
		fbtk_request_redraw(cwidget);
		cwidget = cwidget->prev;
	}

	while (widget->parent != NULL) {
		widget = widget->parent;
		widget->redraw.child = true;
	}
}



/* exported function documented in fbtk.h */
int
fbtk_set_mapping(fbtk_widget_t *widget, bool map)
{
	NSLOG(netsurf, INFO, "setting mapping on %p to %d", widget, map);
	widget->mapped = map;
	if (map) {
		fbtk_request_redraw(widget);
	} else {
		fbtk_request_redraw(widget->parent);
	}
	return 0;
}


/**
 * Swap a sibling widget with the next deepest in the hierachy
 *
 * \param lw The widget to swap
 */
static void
swap_siblings(fbtk_widget_t *lw)
{
	fbtk_widget_t *rw = lw->next; /* the widget to swap lw with */
	fbtk_widget_t *before;
	fbtk_widget_t *after;

	assert(rw != NULL);

	NSLOG(netsurf, INFO, "Swapping %p with %p", lw, rw);
	before = lw->prev;
	after = rw->next;

	if (before == NULL) {
		/* left widget is currently the first child */
		lw->parent->first_child = rw;
	} else {
		before->next = rw;
	}
	rw->prev = before;
	rw->next = lw;

	if (after == NULL) {
		/* right widget is currently the last child */
		rw->parent->last_child = lw;
	} else {
		after->prev = lw;
	}
	lw->next = after;
	lw->prev = rw;
}



/* exported function documented in fbtk.h */
int
fbtk_set_zorder(fbtk_widget_t *widget, int z)
{
	while (z != 0) {
		if (z < 0) {
			if (widget->prev == NULL)
				break; /* cannot go any shallower */

			/* swap with previous entry */
			swap_siblings(widget->prev);

			z++;
		} else {
			if (widget->next == NULL)
				break; /* cannot go any deeper */

			/* swap with subsequent entry */
			swap_siblings(widget);

			z--;
		}
	}

	return z;
}


/* exported function documented in fbtk.h */
bool
fbtk_set_pos_and_size(fbtk_widget_t *widget,
		      int x, int y,
		      int width, int height)
{
	if (widget->parent != NULL) {
		fbtk_widget_t *parent = widget->parent;

		/* make new window fit inside parent */
		if (width == 0) {
			width = parent->width - x;
		} else if (width < 0) {
			width = parent->width + width - x;
		}
		if ((width + x) > parent->width) {
			width = parent->width - x;
		}

		if (height == 0) {
			height = parent->height - y;
		} else if (height < 0) {
			height = parent->height + height - y;
		}
		if ((height + y) > parent->height) {
			height = parent->height - y;
		}
	}

	if ((widget->x != x) ||
	    (widget->y != y) ||
	    (widget->width != width) ||
	    (widget->height != height)) {
		widget->x = x;
		widget->y = y;
		widget->width = width;
		widget->height = height;
		return true;
	}
	return false;
}


/* exported function docuemnted in fbtk.h */
void
fbtk_set_caret(fbtk_widget_t *widget, bool set,
		int x, int y, int height,
		void (*remove_caret)(fbtk_widget_t *widget))
{
	fbtk_widget_t *root;

	assert(widget != NULL);
	root = fbtk_get_root_widget(widget);

	if (root->u.root.caret.owner != NULL &&
			root->u.root.caret.remove_cb != NULL)
		root->u.root.caret.remove_cb(widget);

	if (set) {
		assert(remove_caret != NULL);

		root->u.root.caret.owner = widget;
		root->u.root.caret.x = x;
		root->u.root.caret.y = y;
		root->u.root.caret.height = height;
		root->u.root.caret.remove_cb = remove_caret;

	} else {
		root->u.root.caret.owner = NULL;
		root->u.root.caret.remove_cb = NULL;
	}
}

/* exported function documented in fbtk.h */
int
fbtk_destroy_widget(fbtk_widget_t *widget)
{
	fbtk_widget_t *parent;
	int ret = 0;

	ret = fbtk_post_callback(widget, FBTK_CBT_DESTROY);

	while (widget->first_child != NULL) {
		fbtk_destroy_widget(widget->first_child);
	}

	parent = widget->parent;
	if (parent != NULL) {

		/* unlink from siblings */
		if (widget->prev != NULL) {
			widget->prev->next = widget->next;
		} else {
			/* must be the first widget, unlink from parent */
			parent->first_child = widget->next;
		}
		if (widget->next != NULL) {
			widget->next->prev = widget->prev;
		} else {
			/* must be the last widget, unlink from parent */
			parent->last_child = widget->prev;
		}

		free(widget);
	}

	return ret;
}

/* region coverage flags. */
enum {
	POINT_LEFTOF_REGION = 1,
	POINT_RIGHTOF_REGION = 2,
	POINT_ABOVE_REGION = 4,
	POINT_BELOW_REGION = 8,
};

/* Computes where a point lies in respect to an area. */
#define REGION(x,y,cx1,cx2,cy1,cy2)			\
	(( (y) > (cy2) ? POINT_BELOW_REGION : 0) |	\
	 ( (y) < (cy1) ? POINT_ABOVE_REGION : 0) |	\
	 ( (x) > (cx2) ? POINT_RIGHTOF_REGION : 0) |	\
	 ( (x) < (cx1) ? POINT_LEFTOF_REGION : 0) )

/* swap two integers */
#define SWAP(a, b) do { int t; t=(a); (a)=(b); (b)=t;  } while(0)

/* exported function documented in fbtk.h */
bool
fbtk_clip_rect(const bbox_t * restrict clip, bbox_t * restrict box)
{
	uint8_t region1;
	uint8_t region2;

	/* ensure co-ordinates are in ascending order */
	if (box->x1 < box->x0) 
		SWAP(box->x0, box->x1);
	if (box->y1 < box->y0) 
		SWAP(box->y0, box->y1);

	region1 = REGION(box->x0, box->y0, clip->x0, clip->x1 - 1, clip->y0, clip->y1 - 1);
	region2 = REGION(box->x1, box->y1, clip->x0, clip->x1 - 1, clip->y0, clip->y1 - 1);

	/* area lies entirely outside the clipping rectangle */
	if ((region1 | region2) && (region1 & region2))
		return false;

	if (box->x0 < clip->x0)
		box->x0 = clip->x0;
	if (box->x0 > clip->x1)
		box->x0 = clip->x1;

	if (box->x1 < clip->x0)
		box->x1 = clip->x0;
	if (box->x1 > clip->x1)
		box->x1 = clip->x1;

	if (box->y0 < clip->y0)
		box->y0 = clip->y0;
	if (box->y0 > clip->y1)
		box->y0 = clip->y1;

	if (box->y1 < clip->y0)
		box->y1 = clip->y0;
	if (box->y1 > clip->y1)
		box->y1 = clip->y1;

	return true;
}

/* exported function documented in fbtk.h */
bool
fbtk_clip_to_widget(fbtk_widget_t *widget, bbox_t * restrict box)
{
	bbox_t wbox;
	wbox.x0 = 0;
	wbox.y0 = 0;
	wbox.x1 = widget->width;
	wbox.y1 = widget->height;
	return fbtk_clip_rect(&wbox, box);
}



/* internally exported function documented in widget.h */
int
fbtk_set_ptr(fbtk_widget_t *widget, fbtk_callback_info *cbi)
{
	fbtk_widget_t *root = fbtk_get_root_widget(widget);
	struct fbtk_bitmap *bm = cbi->context;

	nsfb_cursor_set(root->u.root.fb,
			(nsfb_colour_t *)bm->pixdata,
			bm->width,
			bm->height,
			bm->width,
			bm->hot_x,
			bm->hot_y);

	return 0;
}


/* internally exported function documented in widget.h */
fbtk_widget_t *
fbtk_get_root_widget(fbtk_widget_t *widget)
{
	while (widget->parent != NULL)
		widget = widget->parent;

	/* check root widget was found */
	if (widget->type != FB_WIDGET_TYPE_ROOT) {
		NSLOG(netsurf, INFO,
		      "Widget with null parent that is not the root widget!");
		return NULL;
	}

	return widget;
}


/* exported function documented in fbtk.h */
int
fbtk_get_absx(fbtk_widget_t *widget)
{
	int x = widget->x;

	while (widget->parent != NULL) {
		widget = widget->parent;
		x += widget->x;
	}

	return x;
}


/* exported function documented in fbtk.h */
int
fbtk_get_absy(fbtk_widget_t *widget)
{
	int y = widget->y;

	while (widget->parent != NULL) {
		widget = widget->parent;
		y += widget->y;
	}

	return y;
}


/* exported function documented in fbtk.h */
int
fbtk_get_height(fbtk_widget_t *widget)
{
	return widget->height;
}

/* exported function documented in fbtk.h */
int
fbtk_get_width(fbtk_widget_t *widget)
{
	return widget->width;
}

/* exported function documented in fbtk.h */
bool
fbtk_get_bbox(fbtk_widget_t *widget, nsfb_bbox_t *bbox)
{
	bbox->x0 = widget->x;
	bbox->y0 = widget->y;
	bbox->x1 = widget->x + widget->width;
	bbox->y1 = widget->y + widget->height;

	widget = widget->parent;
	while (widget != NULL) {
		bbox->x0 += widget->x;
		bbox->y0 += widget->y;
		bbox->x1 += widget->x;
		bbox->y1 += widget->y;
		widget = widget->parent;
	}

	return true;
}

bool
fbtk_get_caret(fbtk_widget_t *widget, int *x, int *y, int *height)
{
	fbtk_widget_t *root = fbtk_get_root_widget(widget);

	if (root->u.root.caret.owner == widget) {
		*x = root->u.root.caret.x;
		*y = root->u.root.caret.y;
		*height = root->u.root.caret.height;

		return true;

	} else {
		*x = 0;
		*y = 0;
		*height = 0;

		return false;
	}
}

/* exported function documented in fbtk.h */
fbtk_widget_t *
fbtk_get_widget_at(fbtk_widget_t *nwid, int x, int y)
{
	fbtk_widget_t *widget = NULL; /* found widget */

	/* require the root widget to start */
	nwid = fbtk_get_root_widget(nwid);

	while (nwid != NULL) {
		if ((nwid->mapped) &&
		    (x >= nwid->x) &&
		    (y >= nwid->y) &&
		    (x < (nwid->x + nwid->width)) &&
		    (y < (nwid->y + nwid->height))) {
			widget = nwid;
			x -= nwid->x;
			y -= nwid->y;
			nwid = nwid->first_child;
		} else {
			nwid = nwid->next;
		}
	}

	return widget;
}




/* internally exported function documented in widget.h */
fbtk_widget_t *
fbtk_widget_new(fbtk_widget_t *parent,
		enum fbtk_widgettype_e type,
		int x,
		int y,
		int width,
		int height)
{
	fbtk_widget_t *neww; /* new widget */

	if (parent == NULL)
		return NULL;

	neww = calloc(1, sizeof(fbtk_widget_t));
	if (neww == NULL)
		return NULL;

	NSLOG(fbtk, DEBUG, "creating %p %d,%d %d,%d", neww, x, y, width, height);

	/* make new window fit inside parent */
	if (width == 0) {
		width = parent->width - x;
	} else if (width < 0) {
		width = parent->width + width - x;
	}
	if ((width + x) > parent->width) {
		width = parent->width - x;
	}

	if (height == 0) {
		height = parent->height - y;
	} else if (height < 0) {
		height = parent->height + height - y;
	}
	if ((height + y) > parent->height) {
		height = parent->height - y;
	}

	NSLOG(fbtk, DEBUG, "using %p %d,%d %d,%d", neww, x, y, width, height);

	/* set values */
	neww->type = type;
	neww->x = x;
	neww->y = y;
	neww->width = width;
	neww->height = height;

	/* insert into widget heiarchy */

	neww->parent = parent;

	if (parent->first_child == NULL) {
		/* no child widgets yet */
		parent->last_child = neww;
	} else {
		/* add new widget to front of sibling chain */
		neww->next = parent->first_child;
		neww->next->prev = neww;
	}
	parent->first_child = neww;

	return neww;
}

/* exported function documented in fbtk.h */
bool
fbtk_get_redraw_pending(fbtk_widget_t *widget)
{
	fbtk_widget_t *root;

	/* ensure we have the root widget */
	root = fbtk_get_root_widget(widget);

	return root->redraw.needed | root->redraw.child;
}

/** Perform a depth-first tree-walk, calling the redraw callback of the widgets in turn.
 *
 * This function makes no decisions of its own and simply walks the
 * widget tree depth first calling widgets redraw callbacks if flagged
 * to do so.
 * The tree search is optimised with a flag to indicate wether the
 * children of a node should be considered.
 */
static int
do_redraw(nsfb_t *nsfb, fbtk_widget_t *widget)
{
	nsfb_bbox_t plot_ctx;
	fbtk_widget_t *cwidget; /* child widget */

	/* check if the widget requires redrawing */
	if (widget->redraw.needed == true) {
		plot_ctx.x0 = fbtk_get_absx(widget) + widget->redraw.x;
		plot_ctx.y0 = fbtk_get_absy(widget) + widget->redraw.y;
		plot_ctx.x1 = plot_ctx.x0 + widget->redraw.width;
		plot_ctx.y1 = plot_ctx.y0 + widget->redraw.height;

		NSLOG(fbtk, DEBUG,
		      "clipping %p %d,%d %d,%d",
		      widget,
		      plot_ctx.x0, plot_ctx.y0,
		      plot_ctx.x1, plot_ctx.y1);

		if (nsfb_plot_set_clip(nsfb, &plot_ctx) == true) {
			fbtk_post_callback(widget, FBTK_CBT_REDRAW);
		}
		widget->redraw.needed = false;
	}

	/* walk the widgets children if child flag is set */
	if (widget->redraw.child) {
		cwidget = widget->last_child;
		while (cwidget != NULL) {
			do_redraw(nsfb, cwidget);
			cwidget = cwidget->prev;
		}
		widget->redraw.child = false;
	}

	return 1;
}

/* exported function documented in fbtk.h */
int
fbtk_redraw(fbtk_widget_t *widget)
{
	fbtk_widget_t *root;

	/* ensure we have the root widget */
	root = fbtk_get_root_widget(widget);

	return do_redraw(root->u.root.fb, root);
}

/* exported function documented in fbtk.h */
fbtk_callback
fbtk_get_handler(fbtk_widget_t *widget, fbtk_callback_type cbt)
{
	if ((cbt <= FBTK_CBT_START) || (cbt >= FBTK_CBT_END)) {
		/* type out of range, no way to report error so return NULL */
		return NULL;
	}

	return widget->callback[cbt];
}

/* exported function documented in fbtk.h */
fbtk_callback
fbtk_set_handler(fbtk_widget_t *widget,
		 fbtk_callback_type cbt,
		 fbtk_callback cb,
		 void *context)
{
	fbtk_callback prevcb;

	if ((cbt <= FBTK_CBT_START) || (cbt >= FBTK_CBT_END)) {
		/* type out of range, no way to report error so return NULL */
		return NULL;
	}

	prevcb = widget->callback[cbt];

	widget->callback[cbt] = cb;
	widget->callback_context[cbt] = context;

	return prevcb;
}

/* exported function docuemnted in fbtk.h */
int
fbtk_post_callback(fbtk_widget_t *widget, fbtk_callback_type cbt, ...)
{
	fbtk_callback_info cbi;
	int ret = 0;
	va_list ap;

	if (widget == NULL)
		return -1;
	/* if the widget is not mapped do not attempt to post any
	 * events to it
	 */
	if (widget->mapped == false)
		return ret;

	if (widget->callback[cbt] != NULL) {
		cbi.type = cbt;
		cbi.context = widget->callback_context[cbt];

		va_start(ap, cbt);

		switch (cbt) {
		case FBTK_CBT_SCROLLX:
			cbi.x = va_arg(ap,int);
			break;

		case FBTK_CBT_SCROLLY:
			cbi.y = va_arg(ap,int);
			break;

		case FBTK_CBT_CLICK:
			cbi.event = va_arg(ap, void *);
			cbi.x = va_arg(ap, int);
			cbi.y = va_arg(ap, int);
			break;

		case FBTK_CBT_INPUT:
			cbi.event = va_arg(ap, void *);
			break;

		case FBTK_CBT_POINTERMOVE:
			cbi.x = va_arg(ap, int);
			cbi.y = va_arg(ap, int);
			break;

		case FBTK_CBT_REDRAW:
			break;

		case FBTK_CBT_USER:
			break;

		case FBTK_CBT_STRIP_FOCUS:
			break;

		default:
			break;
		}
		va_end(ap);

		ret = (widget->callback[cbt])(widget, &cbi);
	}

	return ret;
}

/* exported function docuemnted in fbtk.h */
void
fbtk_set_focus(fbtk_widget_t *widget)
{
	fbtk_widget_t *root;

	/* ensure we have the root widget */
	root = fbtk_get_root_widget(widget);

	if (root->u.root.input != NULL &&
			root->u.root.input != widget) {
		/* inform previous holder of focus that it's being stripped
		 * of focus */
		fbtk_post_callback(root->u.root.input, FBTK_CBT_STRIP_FOCUS);
	}

	root->u.root.input = widget;
}



/* exported function docuemnted in fbtk.h */
nsfb_t *
fbtk_get_nsfb(fbtk_widget_t *widget)
{
	fbtk_widget_t *root;

	/* ensure we have the root widget */
	root = fbtk_get_root_widget(widget);

	return root->u.root.fb;
}

/* exported function docuemnted in fbtk.h */
fbtk_widget_t *
fbtk_init(nsfb_t *fb)
{
	fbtk_widget_t *root;

	/* create and configure root widget */
	root = calloc(1, sizeof(fbtk_widget_t));
	if (root == NULL)
		return NULL;

	root->type = FB_WIDGET_TYPE_ROOT;
	root->u.root.fb = fb;
	root->u.root.caret.owner = NULL;

	nsfb_get_geometry(fb, &root->width, &root->height, NULL);

	root->mapped = true;

	return root;
}

/*
 * Local Variables:
 * c-basic-offset:8
 * End:
 */
