#include "global.hh"

#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdarg.h>

#include "main.hh"
#include "boxes.hh"
#include "misc.hh"
#include "events.hh"
#include "log.hh"

Window boxw;

#ifdef DEBUG
int depth_box=0;
char *indents[10]={(char*)" ", (char*)"  ", (char*)"   ", (char*) "    ", (char*) "     ",
(char*) "      ", (char*) "       ", (char*) "        ", (char *) "         ", (char*)"          "};
#endif
// the type of groupbox used determines the (type)setting

// ia - objects express their desire concerning size/pos - from down
// to up in hierarchy

// fa - up to down - parents sets up their childrens

//********************************************************************

Rect::Rect ()
{
	cl ();
}

Rect::Rect (Rect *r)
{
	set (r);
}

int
Rect::set (Rect *r)
{
	x = r->x;
	y = r->y;
	wd = r->wd;
	ht = r->ht;
}

int
Rect::set (Rect &r)
{
	x = r.x;
	y = r.y;
	wd = r.wd;
	ht = r.ht;
}

int
Rect::cl()
{
	x = y = wd = ht = 0;
}

int
Rect::operator== (Rect &o)
{
	if (x == o.x && y == o.y && wd == o.wd && ht == o.ht)
		return 1;
	else
		return 0;
}

int
Rect::operator!= (Rect &o)
{
	if (x == o.x && y == o.y && wd == o.wd && ht == o.ht)
		return 0;
	else
		return 1;
}

int
Rect::set (int nx, int ny, int nwd, int nht)
{
	x = nx;
	y = ny;
	wd = nwd;
	ht = nht;
}

int
Rect::overlap (Rect &r)
{
	if (x+wd-1 < r.x || r.x+r.wd-1 < x || y+ht-1 < r.y || r.y+r.ht-1 < y)
		return 0;
	else
		return 1;
}

int
Rect::overlap (int rx, int ry, int rwd, int rht)
{
	if (x+wd-1 < rx || rx+rwd-1 < x || y+ht-1 < ry || ry+rht-1 < y)
		return 0;
	else
		return 1;
}

int
Rect::contains (int xx, int yy)
{
	if (xx >= x && xx < x+wd && yy >= y && yy < y+ht)
		return 1;
	else
		return 0;
}

int
Rect::dsq (int xx, int yy) // distance to rectangle squared
{
	int dx, dy;
	if (xx<x) dx=xx-x; else if (xx>x+wd) dx=xx-(x+wd); else dx=0;
	if (yy<y) dy=yy-y; else if (yy>y+ht) dy=yy-(y+ht); else dy=0;
	return (dx*dx + dy*dy);
}

#ifdef DEBUG
int
Rect::pr ()
{
	printf ("%d\t%d\t%d\t%d\n", x, y, wd, ht);
}
#endif

//********************************************************************

int
Box::ia ()
{
	wd = ht = 0;
	//  printf ("level %d\n", ddf);
}

int
Box::align ()
{
	ia ();
	fa ();
}

int
Box::cl ()
{
	XClearArea (dpy, boxw, x, y, wd, ht, TRUE);
}

Box::Box ()
{
	next = NULL;
	xs = ys = 0;
	x = y = wd = ht = 0;
}

Box::Box (int ixs, int iys)
{
	next = NULL;
	xs = ixs;
	ys = iys;
	x = y = wd = ht = 0;
}

Box::Box (Box *t)
{
	Box ();
	x = t->x;
	y = t->y;
	wd = t->wd;
	ht = t->ht;
}

/*
Box *
Box::list_next ()
{
	if (next)
		return next;
	else if (parent)
	{
		return parent; // will list GroupBoxes also
		//    return parent->list_next (); // will list only ordinary boxes
	}
	else
	{
		// we are at the end
		return NULL;
	};
};
*/

// i need the parent member only 
// have special class - EndBox - to close the list (with the "GroupBox
// *parent" member, which I don't need for any other Box in the list)

// i need to have only one pointer for loop (otherwise i need
// pop-stack of them (to save parents))

// if i have only one pointer for loop, i don't save parents, so
// parent pointer is needed (to setup the way on which the program
// runs)

//********************************************************************

Win::Win ()
{
	w = None;
	hbox = NULL;
	//  printf ("Win::Win\n");
	//  Window parentw;
	//  XCreateWindow (dpy, w, )
	// etc.
}

Win::~Win ()
{
	if (w) XDestroyWindow (dpy, w);
	if (hbox) delete hbox;
	XSync (dpy, 0);
}

int
Win::align ()
{
	ia (); fa ();
}

int
Win::ia ()
{
	hbox->ia ();
}

int
Win::fa ()
{
	hbox->fa ();
	wr.wd = hbox->wd; wr.ht = hbox->ht;
	
	// ??? XMoveResizeWindow (dpy, w, x, y, wd, ht);

	// ??? something like "base_class::fa ()" (the syntax would be
	// e.g. "::fa()") would be more apropriate; it would mean "call the
	// base class version of this function (the base class version can
	// be inherited from another more fundamental class) - if this is
	// already the most fundamental version (the first defined along the
	// class-tree), the compiler would generate an error) - which
	// function is to be called can be determined at the compile-time -
	// advantage: e.g. in our case "base_class" is "HBox"; but if I
	// changed base class in boxes.hh e.g. to "VBox", I have to manually
	// replace all occurences of "HBox::fn_name" calls with
	// "VBox::fn_name" calls; with "::fn_name ()", i wouldn't need to do
	// that
}

int
Win::handle_event ()
{
	if (event->x.xany.window == w) hbox->handle_event ();
}

int
Win::update ()
{
	XMoveResizeWindow (dpy, w, wr.x, wr.y, wr.wd, wr.ht);
}

//Win::map (
//{
//  XMapWindow (dpy, w);
//};

int
Win::draw ()
{
	Window bboxw = boxw;
	boxw = w;
	hbox->draw ();
	boxw = bboxw;
}

//********************************************************************
/*
int
HRule::HRule (char *color, int iht)
{
	c = color;
	c.init ();
	ht = iht;
};

int
HRule::ia ()
{
	wd = 0;
}; 

int
HRule::draw ()
{
	gcv.line_width = ht;
	gcv.foreground = c;
	XChangeGC (dpy, gc, c);
	XDrawLine (dpy, boxw, gc, x, y, x+wd, y);
};
*/
//********************************************************************

HSkip::HSkip (int iwd)
{
	wd = iwd;
	ht = 0;
	xs = ys = 0;
}

int
HSkip::ia ()
{
	ht = 0;
	//  printf ("level %d hskip\n", ddf);
}

VSkip::VSkip (int iht)
{
	wd = 0;
	ht = iht;
	xs = ys = 0;
}

int
VSkip::ia ()
{
	wd = 0;
	//  printf ("level %d vskip %d\n", ddf, ht);
}

//********************************************************************

int
GroupBox::ia ()
{
	for (Box *i=first; i; i=i->next) i->ia ();
}

int
GroupBox::fa ()
{
	for (Box *i=first; i; i=i->next) i->fa ();
}

GroupBox::GroupBox ()
{
	first = last = NULL;
}

GroupBox::GroupBox (Box *t, ...)
{
	first = last = NULL;
	va_list ap;
	va_start (ap, t);
	do aadd (t); while (t = va_arg (ap, Box *));
	va_end (ap);
}

int
GroupBox::rm_all ()
{
	//  printf ("~GroupBox\n");
	Box *i;
	while (first) {
		i = first;
		first = first->next;
		delete i;
	};

	first = last = NULL;
}

GroupBox::~GroupBox ()
{
	rm_all ();
}

/*
Box *i;
int incorrect;
for (i = dwin; i; i->list_next ())
	if (i->wd < 0 || i->ht < 0)
	{
		incorrect = 1;
		break;
	}; 

Box *
int
GroupBox::list_next ()
{
	// clist variable of structure GroupBox, set (in ::add()) to contain
	// the pointer to first item in list
	if (listed || !first)
	{
		listed = 0;
		return next;
	}
	else
	{
		listed = 1;
		return first;
	};
};
*/ 

int
GroupBox::aadd (Box *t)
{ // add one Box to end of group
	if (!last)
		first = last = t;
	else {
		last->next = t;
		last = t;
	}
}

int
GroupBox::preaadd (Box *t)
{ // add one Box to the beginning of group
	if (!first)
		first = last = t;
	else {
		t->next = first;
		last = t;
	}
}

int
GroupBox::add (Box *t, ...)
{ // add NULL terminated sequence of boxes to a group
	va_list ap;
	va_start (ap, t);
	do aadd (t); while (t = va_arg (ap, Box *));
	va_end (ap);
}

int
GroupBox::draw ()
{
	Box *i;
	for (i=first; i; i = i->next) i->draw ();
}

int
GroupBox::handle_event ()
{
	Box *i;

	switch (event->x.type)
	{
	case Expose:
		if (overlap (event->x.xexpose.x, event->x.xexpose.y, event->x.xexpose.width, event->x.xexpose.height))
			for (i=first; i; i=i->next) i->handle_event ();
		break;
	case ButtonPress:
	case ButtonRelease:
		if (contains (event->x.xbutton.x, event->x.xbutton.y))
			for (i=first; i; i=i->next) i->handle_event ();
		break;
	default:
		for (i=first; i && !event->handled; i=i->next)
			i->handle_event ();
		break;
	}
}

//********************************************************************
//
// The (only) difference between HBox and VBox:
//
// VBox -- stacks owned boxes below each other, grow them vertically,
// if they want (have nonzero ys)
//
// HBox -- stacks owned boxes next to each other, grow horizontally
//
//********************************************************************

int
VBox::add_border (int bw, GC gc)
{ // "surround itself with border"
	VBox *vbox = new VBox;
	*vbox = *this; // memcpy (???)

	HBox *hbox = new HBox;
	hbox->ys = 10; hbox->add (new VRule (bw, gc), vbox, new VRule (bw, gc), NULL);
	
	first = last = NULL; // "this = new VBox;"
	this->add (new HRule (bw, gc), hbox, new HRule (bw, gc), NULL);
}

int
VBox::add_padding (int pad_wd)
{ // "surround itself with padding"
	VBox *vbox = new VBox;
	*vbox = *this; // memcpy (???)

	HBox *hbox = new HBox;
	hbox->ys = 10; hbox->add (new HSkip (pad_wd), vbox, new HSkip (pad_wd), NULL);
	
	first = last = NULL; // "this = new VBox;"
	this->xs = 10; this->add (new VSkip (pad_wd), hbox, new VSkip (pad_wd), NULL);
}

int
VBox::ia ()
{
	GroupBox::ia (); // == ::ia all owned boxes

	//xs = 0;
	ys = 0;
	wd = ht = 0;
	Box *i;
	for (i = first; i; i = i->next) {
		ht += i->ht;
		if (i->wd > wd) wd = i->wd;
		ys += i->ys;
		//    if (i->xs > xs) xs = i->xs;
	}
	baseht = ht;
}

int
VBox::fa ()
{
	int yy = y;
	int ht_pool = ht - baseht; // 'distributed' height
	
#ifdef DEBUG	
	log_window.pr("%svbox.fa %dx%d p=%d xs=%d ys=%d", indents[depth_box], wd, ht, ht_pool, xs, ys);
	depth_box++;
#endif

	// inner_ys (?)

	Box *i;
	for (i = first; i; i = i->next) {
		i->x = this->x;
		i->y = yy;
		if (i->ys) {
			int ht_add = (int) ((((double) i->ys)/((double) ys))*ht_pool);
			i->ht += ht_add;
			ht_pool -= ht_add;
			ys -= i->ys;
		};
		yy += i->ht;
		i->wd = wd;
	};
	
	GroupBox::fa ();
#ifdef DEBUG
	depth_box--;
#endif
}

int
HBox::add_border (int bw, GC gc)
{ // "surround itself with border" 
	HBox *hbox = new HBox;
	*hbox = *this; // memcpy

	VBox *vbox = new VBox;
	vbox->xs = 10; vbox->add (new HRule (bw, gc), hbox, new HRule (bw, gc), NULL);

	first = last = NULL; // "this = new HBox;"
	this->add (new VRule (bw, gc), vbox, new VRule (bw, gc), NULL);
}

int
HBox::add_padding (int pad_wd)
{ // "surround itself with padding"
	HBox *hbox = new HBox;
	*hbox = *this; // memcpy
	
	VBox *vbox = new VBox;
	vbox->xs = 10; vbox->add (new VSkip (pad_wd), hbox, new VSkip (pad_wd), NULL);
	
	first = last = NULL; // "this = new HBox;"
	this->ys = 10; this->add (new HSkip (pad_wd), vbox, new HSkip (pad_wd), NULL);
}

// ia (): sets wd and basewd to the sum of its member's wd, sets ht to
// the highest member's ht

int
HBox::ia ()
{
	GroupBox::ia (); // == ::ia for all owned boxes
	
	// now i can operate with correctly initialized values of owned boxes
	xs = 0;
	//ys = 0;
	wd = ht = 0;
	for (Box *i = first; i; i = i->next) {
		wd += i->wd;
		if (i->ht > ht) ht = i->ht;
		xs += i->xs;
	};
	basewd = wd;
}

int
HBox::fa ()
{
	// members inherit height, gets (eventually) stretched horizontally
	int xx = x;
	int wd_pool = wd - basewd; // width to distribute

#ifdef DEBUG
	log_window.pr("%shbox.fa %dx%d p=%d xs=%d ys=%d", indents[depth_box], wd, ht, wd_pool, xs, ys);
	depth_box++;
#endif

	for (Box *i = first; i; i = i->next) {
		i->x = xx;
		i->y = this->y;
		if (i->xs) {
			int wd_add = (int) ((((double) i->xs)/((double) xs))*wd_pool);
			i->wd += wd_add;
			xs -= i->xs;
			wd_pool -= wd_add;
		};
		xx += i->wd;
		i->ht = ht;
	};

	GroupBox::fa ();
#ifdef DEBUG
	depth_box--;
#endif
}

// would be the output ok if I placed several hboxes (each using
// stretching/shrinking) in one hbox?

//********************************************************************

// hcenter = hbox which vertically center each of its boxes retaining
// its height

int
HCenter::fa ()
{
	int xx = x;
	int wd_pool = wd - basewd;

	Box *i;
	for (i = first; i; i = i->next) {
		int wd_add; // added width
		i->x = xx;
		i->y = y + (ht - i->ht)/2;
		if (xs) {
			wd_add = (int) ((((double) i->xs)/((double) xs))*wd_pool);
			i->wd += wd_add;
			xs -= i->xs;
			wd_pool -= wd_add;
		};
		xx += i->wd;
	};

	GroupBox::fa ();
}

//********************************************************************

HBoxTo::HBoxTo (int ibwd)
{
	basewd = ibwd;
}

int
HBoxTo::ia ()
{
	GroupBox::ia ();
}

//********************************************************************

int
FillBox::draw ()
{
	XFillRectangle (dpy, boxw, gc, x, y, wd, ht);
}

int
FillBox::handle_event ()
{
	switch (event->x.type)
	{
	case Expose:
		if (overlap (event->x.xexpose.x, event->x.xexpose.y, event->x.xexpose.width, event->x.xexpose.height))
			draw ();
		break;
	};
}

VRule::VRule (int iwd, GC igc)
{
	wd = iwd;
	gc = igc;
//	ys = 10;
}

int
VRule::ia ()
{
	ht = 0;
}

HRule::HRule (int iht, GC igc)
{
	ht = iht;
	gc = igc;
//	xs = 10;
}

int
HRule::ia ()
{
	wd = 0;
}

//********************************************************************

Button::Button ()
{
	state = B_FREE;
	pm_free = NULL;
	pm_hover = NULL;
	pm_down = NULL;
}

int
Button::handle_event ()
{
	switch (event->x.type)
	{
	case Expose:
		if (overlap (event->x.xexpose.x, event->x.xexpose.y, event->x.xexpose.width, event->x.xexpose.height))
			draw ();
		break;
	case LeaveNotify:
		state = B_FREE;
		draw ();
		break;
	case EnterNotify:
	case MotionNotify:
		if (contains (event->x.xbutton.x, event->x.xbutton.y)) {
			if (state == B_FREE) {
				state = B_HOVER;
				draw ();
			}
		}
		else {
			if (state != B_FREE) {
				state = B_FREE;
				draw ();
			}
		}
		break;
	case ButtonPress:
		if (contains (event->x.xbutton.x, event->x.xbutton.y)) {
			state = B_DOWN;
			draw ();
			grab_setup (event->x.xany.window);

			Event *event_orig = event;
			event = new Event;

			while (grab_active) {
				event->mask (ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask);
				// the same mask as in misc.cc
				handle_grab ();
			};

			delete event;
			event = event_orig;
			event->handled = 1;
		};
		break;
	default:
		break;
	}
}

int
Button::handle_grab ()
{
	switch (event->x.type)
	{
	case EnterNotify:
	case MotionNotify:
		if (event->x.xany.window == grab_win) {
			if (contains (event->x.xbutton.x, event->x.xbutton.y)) {
				if (state != B_DOWN) {
					state = B_DOWN;
					draw ();
				}}
			else {
				if (state != B_FREE) {
					state = B_FREE;
					draw ();
				}}}
		break;
	case ButtonRelease:
		if (grab_active) grab_cancel ();
		if (contains (event->x.xbutton.x, event->x.xbutton.y))
			if (state == B_DOWN) {
				state = B_HOVER;
				draw ();
				exec ();
				event->handled = 1;
			}
		break;
	default:
		break;
	}
}

int
Button::ia ()
{
	wd = pm_free->wd;
	ht = pm_free->ht;
}

int
Button::draw ()
{
	if (!pm_free || !pm_hover || !pm_down) return 0;

	switch (state) {
	case B_FREE:	pm_free->gx_copy_mask (boxw, x, y); break;
	case B_HOVER:	pm_hover->gx_copy_mask (boxw, x, y); break;
	case B_DOWN:	pm_down->gx_copy_mask (boxw, x, y); break;
	}
}

//********************************************************************

HText::HText (char *&itext)
{
	//  printf ("htext\n");
	text = &itext; // strcpy?
	xs = 10;
	ys = 0;
}

HText::~HText ()
{
	//  delete text;//?
}

int
HText::ia ()
{
	#ifdef FONTS_XFT
	XGlyphInfo extents;
	XftTextExtentsUtf8 (dpy, swm_font.font, (XftChar8*) text, strlen(*text), &extents);
	wd = extents.xOff; 
	#elif defined FONTS_XCORE
	wd = XTextWidth (swm_font.font, *text, strlen(*text));
	#endif
	ht = swm_font.ht;
}

int
HText::handle_event ()
{
	if (event->x.type == Expose)
		// *** FIXME *** ? catch only exposes which overlaps the real
		// width of the drawn text, not the width of the box
		if (overlap (event->x.xexpose.x, event->x.xexpose.y, event->x.xexpose.width, event->x.xexpose.height))
			draw ();
}

int
HText::draw ()
{
	XClearArea (dpy, boxw, x, y, wd, ht, FALSE);

	int nc = strlen (*text);
	
#ifdef FONTS_XFT
	// NEEDS_FIX: on two (and more) byte chars nc should be decreased by more than one
	// this should work as it is but it's not neat
	while (nc && XftTextWidthUtf8 (swm_font.font, *text, nc) > wd) nc--;
	
	XftDrawChange (xft_draw, boxw);
	XftDrawStringUtf8 (xft_draw, &dwin_style.title.xftcolor, swm_font.font,
		x, y + swm_font.y + (ht - swm_font.ht)/2, (XftChar8*) *text, strlen(*text));
#elif defined FONTS_XCORE
	while (nc && XftTextWidth (swm_font.font, *text, nc) > wd) nc--;
	XDrawString (dpy, boxw, dwin_style.titleGC, x, y + swm_font.y + (ht - swm_font.ht)/2, *text, nc);
#endif
}

