/* Functions common to more than one platform. */

#include <stdlib.h>

#include "x3.h"
#include "x3common.h"

int n_x3needshow;
int n_x3needshow_max;
x3widget **x3needshow;


#if defined(X3_CARBON) || defined(X3_WIN32)

int n_x3needsizereqs;
int n_x3needsizereqs_max;
x3widget **x3needsizereqs;

int n_x3needsizeallocs;
int n_x3needsizeallocs_max;
x3widget **x3needsizeallocs;

void x3qsizereq(x3widget *w)
{
    if (w && !(w->flags & x3flag_needsizereq)) {
	x3qsizereq(w->parent);
	if (n_x3needsizereqs == n_x3needsizereqs_max)
	    x3needsizereqs = (x3widget **)realloc(x3needsizereqs,
						  sizeof(x3widget *) *
						  (n_x3needsizereqs_max <<= 1));
	x3needsizereqs[n_x3needsizereqs++] = w;
	w->flags |= x3flag_needsizereq;
    }
}

void x3add_default(x3widget *parent, x3widget *child)
{
    const int n_children_init = 4;

    child->parent = parent;
    if (parent->n_children == 0)
	parent->children = (x3widget **)malloc(sizeof(x3widget *) *
					       n_children_init);
    else if (parent->n_children >= n_children_init &&
	     !(parent->n_children & (parent->n_children - 1)))
	parent->children = (x3widget **)realloc(parent->children,
						sizeof(x3widget *) *
						(parent->n_children << 1));
    parent->children[parent->n_children++] = child;
}

void x3add(x3widget *parent, x3widget *child)
{
    parent->type->add(parent, child);
}

/* Widgets common to carbon and win32 platforms */

typedef struct {
    x3widget base;
    int homogeneous;
    int spacing;
    int cur_child_prop;
    int *child_props; /* bit 0=expand, bit 1=fill, bits 2:31=padding */
} x3widget_box;

void x3vbox_sizereq(x3widget *w)
{
    x3widget_box *wb = (x3widget_box *)w;
    int i;
    int spacing = wb->spacing;

    w->sizerequest.x0 = 0;
    w->sizerequest.y0 = 0;
    w->sizerequest.x1 = 0;
    w->sizerequest.y1 = 0;
    for (i = 0; i < w->n_children; i++) {
	x3widget *child = w->children[i];
	int childw = child->sizerequest.x1 - child->sizerequest.x0;
	int childh = child->sizerequest.y1 - child->sizerequest.y0;
	int padding = wb->child_props[i] >> 2;

	if (i < w->n_children - 1)
	    childh += spacing;
	w->sizerequest.y1 += childh + 2 * padding;
	if (childw > w->sizerequest.x1)
	    w->sizerequest.x1 = childw;
    }
}

void x3vbox_sizealloc(x3widget *w, x3rect *r)
{
    x3widget_box *wb = (x3widget_box *)w;
    int i;
    x3rect child_r = *r;
    int spacing = wb->spacing;
    int n_extend = 0;
    int n_stretch, i_stretch = 0;
    int extra;

    /* todo: impl padding & homog, factor hbox/vbox common */
    printf("vbox sizealloc = (%g, %g) - (%g, %g), req was %g x %g\n",
	   r->x0, r->y0, r->x1, r->y1,
	   w->sizerequest.x1, w->sizerequest.y1);
    extra = r->y1 - r->y0 - w->sizerequest.y1;
    for (i = 0; i < w->n_children; i++)
	if (wb->child_props[i] & 1)
	    n_extend++;
    n_stretch = n_extend ? n_extend : w->n_children;
    printf("extra = %d, n_stretch = %d\n", extra, n_stretch);
    for (i = 0; i < w->n_children; i++) {
	x3widget *child = w->children[i];
	int childh = child->sizerequest.y1 - child->sizerequest.y0;
	int my_extra;
	int next_top;

	if (n_extend == 0 || (wb->child_props[i] & 1)) {
	    my_extra = (extra * (i_stretch + 1)) / n_stretch -
		(extra * i_stretch) / n_stretch;
	    i_stretch++;
	} else
	    my_extra = 0;
	next_top = child_r.y0 + childh + spacing + my_extra;

	if (wb->child_props[i] & 2) {
	    childh += my_extra;
	} else {
	    child_r.y0 += my_extra >> 1;
	}
	child_r.y1 = child_r.y0 + childh;

	child->type->sizealloc(child, &child_r);
	child->flags &= ~x3flag_needsizealloc;

	child_r.y0 = next_top;
    }
}

void x3vbox_add(x3widget *w, x3widget *child)
{
    x3widget_box *wb = (x3widget_box *)w;
    const int n_children_init = 4;

    if (w->n_children == 0)
	wb->child_props = (int *)malloc(sizeof(int) * n_children_init);
    else if (w->n_children >= n_children_init &&
	     !(w->n_children & (w->n_children - 1)))
	wb->child_props = (int *)realloc(wb->child_props,
					 sizeof(int) * (w->n_children << 1));
    wb->child_props[w->n_children] = wb->cur_child_prop;
    x3add_default(w, child);
}

x3type x3vboxtype = { x3vbox_sizereq,
		      x3vbox_sizealloc,
		      x3vbox_add };

x3widget *x3vbox(x3widget *parent, int homogeneous, int spacing)
{
    x3widget_box *result = (x3widget_box *)malloc(sizeof(x3widget_box));
    x3widget_init(&result->base, &x3vboxtype);
    x3add(parent, &result->base);
    result->homogeneous = homogeneous;
    result->spacing = spacing;
    result->cur_child_prop = 3;
    x3qsizereq(&result->base);
    return &result->base;
}

void x3setpacking(x3widget *w, int fill, int expand, int padding)
{
    if (w->type == &x3vboxtype) {
	x3widget_box *wb = (x3widget_box *)w;
	int child_props = padding << 2;

	if (fill) child_props |= 1;
	if (expand) child_props |= 2;
	wb->cur_child_prop = child_props;
    }
}

typedef struct {
    x3widget base;
    x3alignment alignment;
} x3widget_align;

void x3align_sizereq(x3widget *w)
{
    w->sizerequest.x0 = 0;
    w->sizerequest.y0 = 0;
    w->sizerequest.x1 = 0;
    w->sizerequest.y1 = 0;
    if (w->n_children) {
	x3widget *child = w->children[0];
	int childw = child->sizerequest.x1 - child->sizerequest.x0;
	int childh = child->sizerequest.y1 - child->sizerequest.y0;
	w->sizerequest.x1 = childw;
	w->sizerequest.y1 = childh;
    }
}

void x3align_sizealloc(x3widget *w, x3rect *r)
{
    x3widget_align *z = (x3widget_align *)w;
    x3alignment a = z->alignment;
    int xa = a & 3;
    int ya = (a >> 2) & 3;
    x3rect child_r = *r;

    printf("align sizealloc = (%g, %g) - (%g, %g)\n",
	   r->x0, r->y0, r->x1, r->y1);
    if (w->n_children) {
	x3widget *child = w->children[0];
	if (xa < 3) {
	    int childw = child->sizerequest.x1 - child->sizerequest.x0;
	    int pad = r->x1 - r->x0 - childw;
	    child_r.x0 += (pad * (1 + (xa >> 1) - (xa & 1)) + 1) >> 1;
	    child_r.x1 = child_r.x0 + childw;
	}
	if (ya < 3) {
	    int childh = child->sizerequest.y1 - child->sizerequest.y0;
	    int pad = r->y1 - r->y0 - childh;
	    child_r.y0 += (pad * (1 + (ya >> 1) - (ya & 1)) + 1) >> 1;
	    child_r.y1 = child_r.y0 + childh;
	}

	child->type->sizealloc(child, &child_r);
	child->flags &= ~x3flag_needsizealloc;
    }
}

x3type x3aligntype = { x3align_sizereq,
		       x3align_sizealloc,
		       x3add_default };

x3widget *x3align(x3widget *parent, x3alignment alignment)
{
    x3widget *result = (x3widget *)malloc(sizeof(x3widget_align));
    x3widget_init(result, &x3aligntype);
    x3add(parent, result);
    x3qsizereq(result);
    ((x3widget_align *)result)->alignment = alignment;
    return result;
}

typedef struct {
    x3widget base;
    int t, b, l, r;
} x3widget_pad;

void x3pad_sizereq(x3widget *w)
{
    x3widget_pad *z = (x3widget_pad *)w;
    w->sizerequest.x0 = 0;
    w->sizerequest.y0 = 0;
    w->sizerequest.x1 = 0;
    w->sizerequest.y1 = 0;
    if (w->n_children) {
	x3widget *child = w->children[0];
	int childw = child->sizerequest.x1 - child->sizerequest.x0;
	int childh = child->sizerequest.y1 - child->sizerequest.y0;
	w->sizerequest.x1 = childw + z->l + z->r;
	w->sizerequest.y1 = childh + z->t + z->b;
    }
}

void x3pad_sizealloc(x3widget *w, x3rect *r)
{
    x3widget_pad *z = (x3widget_pad *)w;
    x3rect child_r = *r;

    printf("pad sizealloc = (%g, %g) - (%g, %g)\n",
	   r->x0, r->y0, r->x1, r->y1);
    if (w->n_children) {
	x3widget *child = w->children[0];
	child_r.x0 += z->l;
	child_r.x1 -= z->r;
	child_r.y0 += z->t;
	child_r.y1 -= z->b;

	child->type->sizealloc(child, &child_r);
	child->flags &= ~x3flag_needsizealloc;
    }
}

x3type x3padtype = { x3pad_sizereq,
		     x3pad_sizealloc,
		     x3add_default };

x3widget *x3pad(x3widget *parent, int t, int b, int l, int r)
{
    x3widget *result = (x3widget *)malloc(sizeof(x3widget_pad));
    x3widget_init(result, &x3padtype);
    x3add(parent, result);
    x3qsizereq(result);
    ((x3widget_pad *)result)->t = t;
    ((x3widget_pad *)result)->b = b;
    ((x3widget_pad *)result)->l = l;
    ((x3widget_pad *)result)->r = r;
    return result;
}

#endif

void x3initqs(void)
{
    n_x3needshow = 0;
    x3needshow = (x3widget **)malloc(sizeof(x3widget *) *
				     (n_x3needshow_max = 16));

#if defined(X3_CARBON) || defined(X3_WIN32)
    n_x3needsizereqs = 0;
    x3needsizereqs = (x3widget **)malloc(sizeof(x3widget *) *
					 (n_x3needsizereqs_max = 16));

    n_x3needsizeallocs = 0;
    x3needsizeallocs = (x3widget **)malloc(sizeof(x3widget *) *
					   (n_x3needsizeallocs_max = 16));
#endif
}

void x3qshow(x3widget *w)
{
    if (n_x3needshow == n_x3needshow_max)
	x3needshow = (x3widget **)realloc(x3needshow,
					  sizeof(x3widget *) *
					  (n_x3needshow_max <<= 1));
    x3needshow[n_x3needshow++] = w;
}

void x3sync(void)
{
    int i;

#if defined(X3_CARBON) || defined(X3_WIN32)

    for (i = n_x3needsizereqs - 1; i >= 0; i--) {
	x3widget *w = x3needsizereqs[i];
	w->type->sizereq(w);
	w->flags &= ~x3flag_needsizereq;
	w->flags |= x3flag_needsizealloc;
    }
    for (i = 0; i < n_x3needsizereqs; i++) {
	x3widget *w = x3needsizereqs[i];
	if (w->flags & x3flag_needsizealloc) {
	    w->type->sizealloc(w, NULL);
	    w->flags &= ~x3flag_needsizealloc;
	}
    }
    n_x3needsizereqs = 0;
#endif

    for (i = 0; i < n_x3needshow; i++)
	x3_window_show(x3needshow[i]);
    n_x3needshow = 0;
}
