/* widget-packingbox.m,
 *
 *  1) h/v_padding is the spacing between the edge of the children
 *     widgets and the packing box.
 *
 *  2) child_spacing is the space between adjacent widgets.
 */

#include <allegro.h>
#include <assert.h>
#include "gui/widget-packingbox.h"
#include "gui/gui-clip.h"


/*--------------------------------------------------------------*/
/* Abstract Packing Box Class.					*/
/*--------------------------------------------------------------*/


@implementation PackingBox
	/* Initialisation. */
- (id) init
{
    int i;

    [super init];

    child_spacing = 0;
    for (i = 0; i <= CHILDREN_PER_PACKING_BOX; i++) {
	/* Last child is a terminator. */
	child[i] = nil;
    }

    return self;
}

- (id) free
{
    int i;

    for (i = 0; child[i]; i++)
	child[i] = [child[i] free];
    return [super free];
}

- (void) insertWidget:(Widget *)widget
{
    int i = [self numChildren];

    assert(i < CHILDREN_PER_PACKING_BOX);
    child[i] = widget;
    [widget setParentWidget:self];
}

- (id) setX:(int)x_ Y:(int)y_ W:(int)w_ H:(int)h_
{
    [super setX:x_ Y:y_ W:w_ H:h_];
    [self calculateChildrenDimensions];
    return self;
}

- (id) setChildSpacing:(int)cs { child_spacing = cs; return self; }

	/* Update. */
- (void) receiveMessage:(int)msg :(int)data
{
    int i;

    if (msg == MSG_REDRAW)
	flags |= FLAG_DIRTY;

    /* Relay the message to all the children. */
    for (i = 0; child[i]; i++)
	[child[i] receiveMessage:msg :data];
}

- (void) receiveMessage:(int)msg :(int)data fromChild:(Widget *)widget
{
    if (parent)
	[parent receiveMessage:msg :data fromChild:widget];
}

- (Widget *) getWidgetBounding:(int)mx :(int)my
{
    if ([super getWidgetBounding:mx :my]) {
	int i;

	for (i = 0; child[i]; i++) {
	    Widget *widget = [child[i] getWidgetBounding:mx :my];
	    if (widget)
		return widget;
	}
    }

    return nil;
}

	/* Drawing. */
- (BOOL) draw:(BITMAP *)bmp :(BOOL)marked_dirty
{
    clip_t old_clip;
    int i;

    /* If we've marked it dirty, tell children not to remark. */
    marked_dirty = [super draw:bmp :marked_dirty];

    save_clip(bmp, &old_clip);
    for (i = 0; child[i]; i++) {
	int x_, y_, w_, h_;

	/* Set the clip here for convenience. */
	[child[i] getX:&x_ Y:&y_ W:&w_ H:&h_];
	set_clip_wh(bmp, x_, y_, w_, h_);

	[child[i] draw:bmp :marked_dirty];
    }
    restore_clip(bmp, &old_clip);

    return NO;
}

	/* Utils. */
- (int) numChildren
{
    int i;
    for (i = 0; child[i]; i++);
    return i;
}

- (void) calculateChildrenDimensions { /* Replace me! */ }
@end


/*------------------------------------------------------------*/
/* Horizontal Packing Box.                                    */
/*------------------------------------------------------------*/


@implementation HPackingBox
#if 0
	/* Initialisation. */
- (void) insertWidget:(Widget *)widget
{
    [super insertWidget:widget];

    /* HPackingBoxes inherit the max height of their children. */
    if (max_width > 0 || [widget maxWidth] > 0)
	max_width += [widget maxWidth];
    max_height = MAX(max_height, [widget maxHeight]);
}
#endif

	/* Utils. */
- (int) minWidth
{
    /* The minimum width of the packing box is the sum of:
        a) each child's minimum width.
	b) 2*h_padding.
	c) the spacing between each child. */
    int i, w_;

    for (i = 0, w_ = 0; child[i]; i++)
	w_ += [child[i] minWidth];

    w_ += 2*h_padding;
    w_ += MAX(0, (i-1)) * child_spacing;
    return w_;
}

- (int) minHeight
{
    /* The minimum height of the packing box is the sum of:
        a) the largest of the childrens' minimum heights. 
	b) 2*v_padding. */
    int i, h_;

    for (i = 0, h_ = 0; child[i]; i++)
	h_ = MAX(h_, [child[i] minHeight]);
    h_ += 2*v_padding;
    return h_;
}

- (void) calculateChildrenDimensions
{
    int i, excess_w, num_children;
    BOOL child_is_full[CHILDREN_PER_PACKING_BOX];

    num_children = [self numChildren];
    excess_w = w - [self minWidth];

    if (excess_w <= 0) {
	fprintf(stderr, "%s (%s) doesn't have enough space!\n", name, [[self class] name]);
	assert(NO);
    }

    for (i = 0; child[i]; i++) {
	[child[i] setToMinDimensions];
	child_is_full[i] = NO;
    }

    while (num_children > 0 && excess_w > 0) {
	int used, distribution_w = excess_w / num_children;

	if (distribution_w <= 0)
	    break;

	for (i = 0; child[i]; i++) {
	    if (child_is_full[i])
		continue;

	    used = [child[i] incrementW:distribution_w];
	    excess_w -= used;

	    if (used < distribution_w) {
		child_is_full[i] = YES;
		num_children--;
	    }
	}
    }

    {					/* Position the children. */
	int x_ = x + h_padding + excess_w/2;
	int y_ = y + v_padding;
	int h_ = h - v_padding*2;

	for (i = 0; child[i]; i++) {
	    [child[i] setX:x_ Y:y_ W:-1 H:h_];
	    x_ += [child[i] width] + child_spacing;
	}
    }

    flags |= FLAG_DIRTY;
}
@end


/*--------------------------------------------------------------*/
/* Vertical Packing Box.					*/
/*--------------------------------------------------------------*/


@implementation VPackingBox
#if 0
	/* Initialisation. */
- (void) insertWidget:(Widget *)widget
{
    [super insertWidget:widget];

    /* VPackingBoxes inherit the max width of their children. */
    max_width = MAX(max_width, [widget maxWidth]);
    if (max_height > 0 || [widget maxHeight] > 0)
	max_height += [widget maxHeight];
}
#endif

	/* Utils. */
- (int) minWidth
{
    /* The minimum width of the packing box is the sum of:
        a) the largest of the childrens' minimum widths.
	b) 2*h_padding. */
    int i, w_;

    for (i = 0, w_ = 0; child[i]; i++)
	w_ = MAX(w_, [child[i] minWidth]);
    w_ += 2*h_padding;
    return w_;
}

- (int) minHeight
{
    /* The minimum height of the packing box is the sum of:
        a) each child's minimum width.
	b) 2*h_padding.
	c) the spacing between each child. */
    int i, h_;

    for (i = 0, h_ = 0; child[i]; i++)
	h_ += [child[i] minHeight];

    h_ += 2*v_padding;
    h_ += MAX(0, (i-1)) * child_spacing;
    return h_;
}

- (void) calculateChildrenDimensions
{
    int i, excess_h, num_children;
    BOOL child_is_full[CHILDREN_PER_PACKING_BOX];

    num_children = [self numChildren];
    excess_h = h - [self minHeight];
    assert(excess_h >= 0);

    for (i = 0; child[i]; i++) {
	[child[i] setToMinDimensions];
	child_is_full[i] = NO;
    }

    while (num_children > 0 && excess_h > 0) {
	int used, distribution_h = excess_h / num_children;

	if (distribution_h <= 0)
	    break;

	for (i = 0; child[i]; i++) {
	    if (child_is_full[i])
		continue;

	    used = [child[i] incrementH:distribution_h];
	    excess_h -= used;

	    if (used < distribution_h) {
		child_is_full[i] = YES;
		num_children--;
	    }
	}
    }

    {					/* Position the children. */
	int x_ = x + h_padding;
	int y_ = y + v_padding + excess_h/2;
	int w_ = w - h_padding*2;

	for (i = 0; child[i]; i++) {
	    [child[i] setX:x_ Y:y_ W:w_ H:-1];
	    y_ += [child[i] height] + child_spacing;
	}
    }

    flags |= FLAG_DIRTY;
}
@end
