/* seborrhea-spline.m,
 */

#include <allegro.h>
#include <assert.h>
#include <math.h>
#include "linklist.h"
#include "seborrhea.h"
#include "seborrhea/seborrhea-spline.h"
#include "waypoint.h"


#ifndef SQ
# define SQ(x)	((x) * (x))
#endif

static BOOL seborrhea_spline_load(const char *filename, const char *child_name, unsigned int index,
				  Sebum<SebContainer> *parent, DirectoryList *dirlist);

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

@implementation SebSpline
static void seborrhea_spline_loop(Sebum *seb, unsigned int argc, Token *argv[])
{
    enum SEB_SPLINE_LOOP_METHOD method;
    assert(seb && [seb isKindOf:[SebSpline class]] && "Received sebum is not a spline!");
    assert(argc == 2 && "Wrong number of arguments received for spline-loop!");
    assert([argv[1] isType:TOKEN_SYMBOL] && "Wrong types of arguments received for spline-loop!");

    method = (streq([argv[1] getSymbol], "loop-forward") ? SEB_SPLINE_LOOP_FORWARD : SEB_SPLINE_LOOP_NONE);
    [(SebSpline *)seb setLoop:method];
}

+ (void) load
{
    seborrhea_register_filetype_handler("spline", seborrhea_spline_load);
    seborrhea_register_metafile_property("spline-loop", seborrhea_spline_loop);
}

- free
{
    FREE_SPLINE(waypoint_array);
    return [super free];
}

- setLoop:(enum SEB_SPLINE_LOOP_METHOD)method
{
    assert(method <= SEB_SPLINE_LOOP_FORWARD);
    loop = method;
    return self;
}

- (BOOL) loadFromFile:(const char *)filename
{
    assert(filename);
    assert(not waypoint_array && not spline_length);

    spline_length = load_spline(filename, &waypoint_array, NULL);
    return (spline_length > 0) ? YES : NO;
}

- (BOOL) loadFromMemory:(void *)buffer :(int)buffer_size
{
    assert(buffer);
    assert(not waypoint_array && not spline_length);
    spline_length = load_spline_from_memory(buffer, buffer_size, &waypoint_array, NULL);
    return (spline_length > 0) ? YES : NO;
}

- (int) follow:(unsigned int *)progress :(double *)x :(double *)y :(double)speed
	      :(double)x_displacement :(double)y_displacement
{
    /* Follow, follow, follow, follow, follow the yellow brick road... */
    double dest_x, dest_y;

    if (*progress >= spline_length)
	return PATH_REACHED_WAYPOINT;

    dest_x = x_displacement + waypoint_array[*progress].x;
    dest_y = y_displacement + waypoint_array[*progress].y;

    /* Destination is two (or more) steps away. */
    if (SQ(dest_x - *x) + SQ(dest_y - *y) > SQ(speed)) {
	double theta = atan2(*y-dest_y, dest_x-*x);
	*x += speed * cos(theta);
	*y -= speed * sin(theta);
	return PATH_NORMAL;
    }

    /* Within one step to the next waypoint. */
    else {
	*x = dest_x;
	*y = dest_y;
	(*progress)++;

	if (*progress < spline_length)
	    return PATH_REACHED_WAYPOINT;
	else {
	    if (loop == SEB_SPLINE_LOOP_FORWARD) {
		*progress = 0;
		return PATH_REACHED_WAYPOINT;
	    }
	    else {
		/* No loop. */
		return PATH_ENDED;
	    }
	}
    }
}

- (double) angleBetween:(unsigned int)alpha :(unsigned int)beta
{
    double deltax, deltay;
    assert(waypoint_array);

    if (alpha >= spline_length || beta >= spline_length)
	return 0.0;

    deltax = waypoint_array[beta].x - waypoint_array[alpha].x;
    deltay = waypoint_array[alpha].y - waypoint_array[beta].y;
    return atan2(deltay, deltax);
}
@end

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

static BOOL seborrhea_spline_load(const char *filename, const char *child_name, unsigned int index,
				  Sebum<SebContainer> *parent, DirectoryList *dirlist)
{
    SebSpline *seb;
    assert(filename && child_name && parent && dirlist);

    seb = [[SebSpline alloc] initWithName:child_name];
    if (not seb)
	goto error;

    if ([[dirlist class] isReal]) {
	if (not [seb loadFromFile:filename])
	    goto error;
    }
    else {
	void *buffer;
	unsigned int buffer_size;

	if (not [(<VirtualDirectoryList>)dirlist openFile:filename :index :&buffer :&buffer_size])
	    goto error;
	if (not [seb loadFromMemory:buffer :buffer_size]) {
	    free(buffer);
	    goto error;
	}
	else
	    free(buffer);
    }

    [parent addToList:seb];
    return YES;

 error:
    fprintf(stderr, "[SebSpline] Error loading %s\n", filename);
    [seb free];
    return NO;
}
