/* container-animation.m,
 *
 * Animations are files that contain multiple images, which make up an
 * animation.  All of the frames must have the same dimensions.
 */

#include <assert.h>
#include "container-animation.h"


#ifndef ABS
# define ABS(x)		(((x) < 0) ? (-x) : (x))
#endif


@implementation SebAnimation
- setFrameDelay:(unsigned int)t LoopMethod:(enum SEB_ANIMATION_LOOP_METHOD)method
{
    delay = t;
    loop_method = method;
    return self;
}

- (unsigned int) frameDelay { return delay; }
- (enum SEB_ANIMATION_LOOP_METHOD) loopMethod { return loop_method; }

- (BOOL) addToList:(Sebum<SebImage> *)frame
{
    assert(frame);

    if (not [frame conformsTo:@protocol(SebImage)]) {
	fprintf(stderr, "[SebAnimation: %s] Attempted to add non-image %s (%s)!\n",
		[self name], [frame name], [[frame class] name]);
	return NO;
    }

    if (nelements == 0) {
	w = [frame width];
	h = [frame height];
    }
    else if ([frame width] != w || [frame height] != h) {
	fprintf(stderr, "[SebAnimation: %s] Attempted to add %s, but has differing dimensions!\n",
		[self name], [frame name]);
	return NO;
    }

    return [super addToList:frame];
}

- (unsigned int) numFrames { return nelements; }

- (Sebum<SebImage> *) getFrame:(int)nth
{
    /* Use negative numbers to denote from back of list. */
    if (nelements == 0)
	return nil;
    if (nth < 0)
	nth += nelements;

    assert((nth >= 0) && ((unsigned)nth < nelements));
    return element[nth];
}

- (unsigned int) nextFrame:(unsigned int)curr
{
    if (curr+1 >= nelements)		/* Loop. */
	return 0;
    else
	return curr+1;
}

- (void) setPivotX:(int)x Y:(int)y
{
    unsigned int i;

    for (i = 0; i < nelements; i++)
	[(<SebImage>)element[i] setPivotX:x Y:y];
}

	/* Dimensions. */
- (unsigned int) width  { return w; }
- (unsigned int) height { return h; }

	/* Draw, with transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Angle:(double)theta 
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y Angle:theta];
}

	/* Draw, with transparency and translucency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y Alpha:alpha];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha Angle:(double)theta
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y Alpha:alpha Angle:theta];
}

	/* Draw, with transparency and tinting. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y Tint:r:g:b:alpha];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha Angle:(double)theta
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y Tint:r:g:b:alpha Angle:theta];
}

	/* Draw, without transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y W:(int)w_ H:(int)h_
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawTo:dest X:x Y:y W:w_ H:h_];
}

- (void) drawFromX:(int)sx Y:(int)sy To:(void *)dest X:(int)x Y:(int)y W:(int)w_ H:(int)h_
{
    assert(nelements > 0);
    [(<SebImage>)element[0] drawFromX:sx Y:sy To:dest X:x Y:y W:w_ H:h_];
}
@end

/*--------------------------------------------------------------*/

@implementation SebAnimator
- (const char *) name { return [anim name]; }

- setAnimation:(SebAnimation *)anime;
{
    anim = anime;
    frame = 0;
    frame_tics = 0;
    loop_method = [anim loopMethod];
    return self;
}

- setLoopMethod:(enum SEB_ANIMATION_LOOP_METHOD)method
{
    loop_method = method;
    return self;
}

- (void) updateFrame
{
    frame_tics--;
    if (frame_tics > 0)
	return;

    if (loop_method == LOOP_NONE) {
	int next_frame = [anim nextFrame:frame];

	/* Don't loop. */
	if (next_frame == 0) {
	    frame_tics = 0;
	    return;
	}

	frame = next_frame;
    }
    else if (loop_method == LOOP_FORWARD) {
	frame = [anim nextFrame:frame];
    }
    else if (loop_method == LOOP_PING_PONG) {
	int num_frames = [anim numFrames];

	if (frame+1 >= num_frames)
	    frame = -num_frames+1;
	else
	    frame++;
    }

    frame_tics = [anim frameDelay];
}

- (BOOL) animationEnded
{
    if (loop_method == LOOP_NONE &&
	(unsigned)frame+1 == [anim numFrames] &&
	frame_tics == 0)
	return YES;
    else
	return NO;
}

- (void) setPivotX:(int)x Y:(int)y
{
    assert(anim);
    [anim setPivotX:x Y:y];
}

	/* Dimensions. */
- (unsigned int) width  { return [anim width]; }
- (unsigned int) height { return [anim height]; }

	/* Draw, with transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y
{
    assert(anim);
    /* ABS corrects the frame for when ping-pong-ing. */
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y];
    [self updateFrame];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Angle:(double)theta 
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y Angle:theta];
    [self updateFrame];
}

	/* Draw, with transparency and translucency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y Alpha:alpha];
    [self updateFrame];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha Angle:(double)theta
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y Alpha:alpha Angle:theta];
    [self updateFrame];
}

	/* Draw, with transparency and tinting. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha;
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y Tint:r:g:b:alpha];
    [self updateFrame];
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha Angle:(double)theta
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y Tint:r:g:b:alpha Angle:theta];
    [self updateFrame];
}

	/* Draw, without transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y W:(int)w H:(int)h
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawTo:dest X:x Y:y W:w H:h];
    [self updateFrame];
}

- (void) drawFromX:(int)sx Y:(int)sy To:(void *)dest X:(int)x Y:(int)y W:(int)w H:(int)h
{
    assert(anim);
    [[anim getFrame:ABS(frame)] drawFromX:sx Y:sy To:dest X:x Y:y W:w H:h];
    [self updateFrame];
}
@end
