#import "Group.h"
#import "GraphicView.h"
#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <dpsclient/wraps.h>
#import <objc/List.h>

/* Optimally viewed in a wide window.  Make your window big enough so that this comment fits entirely on one line w/o wrapping. */

#define GROUP_CACHE_THRESHOLD 4

@implementation Group : Graphic
/*
 * This Graphic is used to create heirarchical groups of other Graphics.
 * It simply keeps a list of all the Graphics in the group and resizes
 * and translates them as the Group object itself is resized and moved.
 * It also passes messages sent to the Group onto its members.
 *
 * For efficiency, we cache the group whenever it passes the caching
 * threshold.  Thus, grouping becomes a tool to let the user have some
 * control over the memory/speed tradeoff (which can be different
 * depending on the kind of drawing the user is making).
 */

/* Factory method */

- initList:list
/*
 * Creates a new grouping with list containing the list of Graphics
 * in the group.  Groups of Groups is perfectly allowable.  We have
 * to keep track of the largest linewidth in the group as well as
 * whether any of the elements of the group have arrows since both
 * of those attributes affect the extended bounds of the Group.
 * We set any objects which might be cacheing (notably subgroups of
 * this group) to be not cacheable since it is no use for them to
 * cache themselves when we are caching them as well.
 */
{
    id g;
    int i;
    NXRect r;

    [super init];

    i = [list count];
    g = [list objectAt:--i];
    [g getBounds:&bounds];
    gFlags.arrow = [g lineArrow];
    linewidth = [g lineWidth];
    while (i) {
	g = [list objectAt:--i];
	[g getBounds:&r];
	[g setCacheable:NO];
	if (!r.size.width) r.size.width = 1.0;
	if (!r.size.height) r.size.height = 1.0;
	NXUnionRect(&r, &bounds);
	if (!gFlags.arrow && [g lineArrow]) gFlags.arrow = [g lineArrow];
	if ([g lineWidth] > linewidth) linewidth = [g lineWidth];
    }

    components = list;
    lastRect = bounds;

    return self;
}

- free
{
    [components freeObjects];
    [components free];
    [cache free];
    return [super free];
}

/* Public methods */

- transferSubGraphicsTo:list at:(int)position
{
    int i;

    i = [components count];
    while (i--) [list insertObject:[[components removeLastObject] setCacheable:YES] at:position];

    return self;
}

/* Methods overridden from superclass */

- setCacheable:(BOOL)flag
/*
 * Sets whether we do caching of this Group or not.
 */
{
    dontCache = flag ? NO : YES;
    if (dontCache) {
	[cache free];
	cache = nil;
    }
    return self;
}

- (BOOL)isCacheable
{
    return !dontCache;
}

- draw
/*
 * Individually scales and translates each Graphic in the group and draws
 * them.  This is done this way so that ungrouping is trivial.  Note that
 * if we are caching, we need to take the extra step of translating
 * everything to the origin, drawing them in the cache, then translating
 * them back.
 */
{
    int i;
    Graphic *g;
    NXRect eb, b;
    float sx = 1.0, sy = 1.0, tx, ty;
    BOOL changed, changedSize, caching = NO;

    if (bounds.size.width < 1.0 || bounds.size.height < 1.0 || !components) return self;

    changedSize = lastRect.size.width != bounds.size.width || lastRect.size.height != bounds.size.height;
    changed = changedSize || lastRect.origin.x != bounds.origin.x || lastRect.origin.y != bounds.origin.y;

    if ((changedSize || !cache) && NXDrawingStatus == NX_DRAWING) {
	[cache free];
	cache = nil;
	if (DrawStatus != Resizing && !dontCache && [components count] > GROUP_CACHE_THRESHOLD) {
	    caching = YES;
	    [self getExtendedBounds:&eb];
	    cache = [[NXImage allocFromZone:[self zone]] initSize:&eb.size];
	    [cache lockFocus];
	    [[[NXApp focusView] window] reenableDisplay];	/* workaround for AppKit bug */
	    b = eb; b.origin.x = b.origin.y = 0.0;
	    PSsetalpha(0.0);
	    PSsetgray(NX_WHITE);
	    NXRectFill(&b);
	    PSsetalpha(1.0);
	}
    }

    if (changedSize) {
	sx = bounds.size.width / lastRect.size.width;
	sy = bounds.size.height / lastRect.size.height;
    }

    i = [components count];
    while (i) {
	g = [components objectAt:--i];
	if (changed) {
	    [g getBounds:&b];
	    tx = (bounds.origin.x + ((b.origin.x - lastRect.origin.x) / lastRect.size.width * bounds.size.width)) - b.origin.x;
	    ty = (bounds.origin.y + ((b.origin.y - lastRect.origin.y) / lastRect.size.height * bounds.size.height)) - b.origin.y;
	    b.origin.x = b.origin.x + tx;
	    b.origin.y = b.origin.y + ty;
	    b.size.width = b.size.width * sx;
	    b.size.height = b.size.height * sy;
	    [g setBounds:&b];
	} else if (caching) {
	    [g getBounds:&b];
	}
	if (NXDrawingStatus != NX_DRAWING || !cache || caching) {
	    if (caching) {
		b.origin.x -= eb.origin.x;
		b.origin.y -= eb.origin.y;
		[g setBounds:&b];
	    }
	    [g setGraphicsState];	/* does a gsave ... */
	    [g draw];
	    PSgrestore();		/* ... so we need this grestore */
	    if (caching) {
		b.origin.x += eb.origin.x;
		b.origin.y += eb.origin.y;
		[g setBounds:&b];
	    }
	}
    }

    if (cache && NXDrawingStatus == NX_DRAWING) {
	if (caching) {
	    [cache unlockFocus];
	} else {
	    [self getExtendedBounds:&eb];
	}
	[cache composite:NX_SOVER toPoint:&eb.origin];
    }

    lastRect = bounds;

    return self;
}

- (BOOL)hit:(const NXPoint *)point
/*
 * Gets a hit if any of the items in the group gets a hit.
 */
{
    id g;
    int i;
    NXPoint p;
    float px, py;

    if ([super hit:point]) {
	if (components) {
	    p = *point;
	    px = (p.x - bounds.origin.x) / bounds.size.width;
	    p.x = px * lastRect.size.width + lastRect.origin.x;
	    py = (p.y - bounds.origin.y) / bounds.size.height;
	    p.y = py * lastRect.size.height + lastRect.origin.y;
	    i = [components count];
	    while (i) {
		g = [components objectAt:--i];
		if ([g hit:&p]) return YES;
	    }
	} else {
	    return YES;
	}
    }

    return NO;
}

/* Compatibility methods */

- replaceWithImage
/*
 * Since we got rid of Tiff and PSGraphic and replaced them
 * with the unified Image graphic, we need to go through our
 * list and replace all of them with an Image graphic.
 */
{
    int i;
    id g, newg;

    for (i = [components count]-1; i >= 0; i--) {
	g = [components objectAt:i];
	newg = [g replaceWithImage];
	if (g != newg) {
	    if (g) {
		[components replaceObjectAt:i with:newg];
	    } else {
		[components removeObjectAt:i];
	    }
	}
    }

    return self;
}

/* Group must override all the setting routines to forward to components */

- makeGraphicsPerform:(SEL)aSelector with:(const void *)anArgument
{
    [components makeObjectsPerform:aSelector with:(id)anArgument];
    [cache free];
    cache = nil;
    return self;
}

- changeFont:sender
{
    return [self makeGraphicsPerform:@selector(changeFont:) with:sender];
}

- font
{
    int i;
    id gfont, font = nil;

    i = [components count];
    while (i--) {
	gfont = [[components objectAt:i] font];
	if (gfont) {
	    if (font && font != gfont) {
		font = nil;
		break;
	    } else {
		font = gfont;
	    }
	}
    }

    return font;
}

- setLineWidth:(const float *)value
{
    return [self makeGraphicsPerform:@selector(setLineWidth:) with:value];
}

- setGray:(const float *)value
{
    return [self makeGraphicsPerform:@selector(setGray:) with:value];
}

- setFillColor:(NXColor *)aColor
{
    return [self makeGraphicsPerform:@selector(setFillColor:) with:aColor];
}

- setFill:(int)mode
{
    return [self makeGraphicsPerform:@selector(setFill:) with:(void *)mode];
}

- setLineColor:(NXColor *)aColor
{
    return [self makeGraphicsPerform:@selector(setLineColor:) with:aColor];
}

- setLineCap:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineCap:) with:(void *)value];
}

- setLineArrow:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineArrow:) with:(void *)value];
}

- setLineJoin:(int)value
{
    return [self makeGraphicsPerform:@selector(setLineJoin:) with:(void *)value];
}

/* Archiving methods */

- write:(NXTypedStream *)stream
/*
 * Just writes out the components.
 */
{
    [super write:stream];
    NXWriteTypes(stream, "@", &components);
    return self;
}

- read:(NXTypedStream *)stream
{
    [super read:stream];
    NXReadTypes(stream, "@", &components);
    lastRect = bounds;
    return self;
}

@end

