/* seborrhea-allegro.m,
 *
 * Seborrhea image and sample classes using Allegro to load and stuff.
 */

#include <allegro.h>
#include <assert.h>
#ifndef NO_FBLEND
# include "fblend/include/fblend.h"
#endif
#include "loadpng/loadpng.h"
#include "dirlist-zzip.h"
#include "rottrans.h"
#include "container-animation.h"
#include "seborrhea-allegro.h"
#include "zzpk.h"


#ifndef M_PI
# define M_PI		3.14159265358979323846	/* pi */
#endif

#ifndef rad2fix
# define rad2fix(theta)	(-ftofix((theta) * 128.0/M_PI))
#endif

/*--------------------------------------------------------------*/
/* XXX: Pivot lit sprite func.					*/
/*--------------------------------------------------------------*/

#ifndef M_SQRT2
# define M_SQRT2	1.41421356237309504880	/* sqrt(2) */
#endif

static inline void pivot_lit_sprite(BITMAP *dest, BITMAP *sprite, int x, int y, double theta, int alpha)
{
    unsigned int dim;
    BITMAP *spr;

    if (not dest || not sprite)
	return;

    dim = M_SQRT2 * MAX(sprite->w, sprite->h);
    spr = create_bitmap(dim, dim);
    clear_to_color(spr, makecol(0xff, 0x00, 0xff));
    dim /= 2;

    pivot_sprite(spr, sprite, dim, dim, sprite->w/2, sprite->h/2, rad2fix(theta));
    draw_lit_sprite(dest, spr, x-dim, y-dim, alpha);

    destroy_bitmap(spr);
}

/*------------------------------------------------------------*/
/* Images                                                     */
/*------------------------------------------------------------*/

@implementation SebImageAllegro
- free
{
    if (bitmap) {
	destroy_bitmap(bitmap);
	bitmap = NULL;
    }
    return [super free];
}

- (BOOL) grabFromBitmap:(BITMAP *)source
		  WithX:(unsigned int)sx Y:(unsigned int)sy W:(unsigned int)w H:(unsigned int)h
{
    assert(source);
    assert(not bitmap);

    bitmap = create_bitmap_ex(bitmap_color_depth(source), w, h);
    if (not bitmap)
	return NO;

    blit(source, bitmap, sx, sy, 0, 0, w, h);
    pivot_x = bitmap->w/2;
    pivot_y = bitmap->h/2;
    return YES;
}

- (BOOL) setBitmap:(BITMAP *)bmp
{
    assert(bmp);
    assert(not bitmap);
    bitmap = bmp;
    pivot_x = bitmap->w/2;
    pivot_y = bitmap->h/2;
    return YES;
}

- (void) setPivotX:(int)x Y:(int)y
{
    pivot_x = x;
    pivot_y = y;
}

	/* Dimensions. */
- (unsigned int) width  { return (bitmap) ? bitmap->w : 0; }
- (unsigned int) height { return (bitmap) ? bitmap->h : 0; }

	/* Draw, with transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    draw_sprite(destination, bitmap, x, y);
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Angle:(double)theta
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    pivot_sprite(destination, bitmap, x, y, pivot_x, pivot_y, rad2fix(theta));
}

	/* Draw, with transparency and translucency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

#ifndef NO_FBLEND
    fblend_trans(bitmap, destination, x, y, alpha);
#else  /* Normal Allegro Routines. */
    set_trans_blender(0xff, 0xff, 0xff, alpha);
    draw_trans_sprite(destination, bitmap, x, y);
#endif /* NO_FBLEND */
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha Angle:(double)theta
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    /* Neither FBlend nor Allegro has rotated transparent sprites.
       Use rottrans.c.  May be slow. */
    set_trans_blender(0xff, 0xff, 0xff, alpha);
    pivot_trans_sprite(destination, bitmap, x, y, pivot_x, pivot_y, rad2fix(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
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    /* Note: alpha for set_trans_blender is ignored. */
    set_trans_blender(r, g, b, 0x00);
    draw_lit_sprite(destination, bitmap, x, y, alpha);
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha Angle:(double)theta
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    set_trans_blender(r, g, b, 0x00);
    pivot_lit_sprite(destination, bitmap, x, y, theta, alpha);
}

	/* Draw, without transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y W:(int)w H:(int)h
{
    [self drawFromX:0 Y:0 To: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
{
#define MIN3(alpha,beta,gamma)	MIN(MIN(alpha,beta), gamma)

    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    w = MIN3(w, bitmap->w, destination->w - x);
    h = MIN3(h, bitmap->h, destination->h - y);

    blit(bitmap, destination, sx, sy, x, y, w, h);

#undef MIN3
}

- (BITMAP *) bitmap { return bitmap; } /* XXX */
@end


@implementation SebImageAllegroWithAlpha
	/* Draw, with transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    set_alpha_blender();
    draw_trans_sprite(destination, bitmap, x, y);
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Angle:(double)theta
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);

    set_alpha_blender();
    pivot_trans_sprite(destination, bitmap, x, y, pivot_x, pivot_y, rad2fix(theta));
}

	/* Draw, with transparency and translucency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha
{
    assert("[SebImageAllegroWithAlpha] drawToX:Y:A: not implemented!" && NO);
    (void)dest, (void)x, (void)y, (void)alpha;
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Alpha:(int)alpha Angle:(double)theta
{
    assert("[SebImageAllegroWithAlpha] drawToX:Y:A:Angle: not implemented!" && NO);
    (void)dest, (void)x, (void)y, (void)alpha, (void)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("[SebImageAllegroWithAlpha] drawToX:Y:Tint:::: not implemented!" && NO);
    (void)dest, (void)x, (void)y, (void)r, (void)g, (void)b, (void)alpha;
}

- (void) drawTo:(void *)dest X:(int)x Y:(int)y Tint:(int)r :(int)g :(int)b :(int)alpha Angle:(double)theta
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);
    assert("[SebImageAllegroWithAlpha] drawToX:Y:Tint::::Angle: not implemented!" && NO);
    (void)dest, (void)x, (void)y, (void)r, (void)g, (void)b, (void)alpha, (void)theta;
}

	/* Draw, without transparency. */
- (void) drawTo:(void *)dest X:(int)x Y:(int)y W:(int)w H:(int)h
{
    [self drawFromX:0 Y:0 To: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
{
    BITMAP *destination = dest;
    assert(destination);
    assert(bitmap);
    assert("[SebImageAllegroWithAlpha] drawFromX:Y:To:X:Y:W:H: not implemented!" && NO);
    (void)sx, (void)sy, (void)x, (void)y, (void)w, (void)h;
}
@end


static BITMAP *load_bitmap_somehow(const char *filename, unsigned int index, DirectoryList *dirlist, int *bpp)
{
    char *ext;
    assert(filename && dirlist);

    ext = strrchr(filename, '.') + 1;
    if (strneq(ext, "png")) {
	fprintf(stderr, "[SebImageAllegro] Only PNG is supported\n");
	return NULL;
    }

    if ([[dirlist class] isReal]) {	/* Load bitmap from file. */
	return load_png_ex(filename, NULL, bpp);
    }
    else {				/* Load bitmap from memory. */
	BITMAP *bmp;
	void *buffer = NULL;
	unsigned int buffer_size;

	if (not [(<VirtualDirectoryList>)dirlist openFile:filename :index :&buffer :&buffer_size])
	    return NULL;

	bmp = load_memory_png_ex(buffer, buffer_size, NULL, bpp);
	free(buffer);
	return bmp;
    }
}

static BOOL seborrhea_allegro_load_image(const char *filename, const char *child_name, unsigned int index, 
					 Sebum<SebContainer> *parent, DirectoryList *dirlist)
{
    BITMAP *src;
    SebImageAllegro *seb = nil;
    int bpp = 0;
    assert(child_name && parent);

    src = load_bitmap_somehow(filename, index, dirlist, &bpp);
    if (not src)
	goto error;

    if (bpp == 32)
	seb = [[SebImageAllegroWithAlpha alloc] initWithName:child_name];
    else
	seb = [[SebImageAllegro alloc] initWithName:child_name];

    if (not [seb setBitmap:src])
	goto error;

    [parent addToList:seb];
    return YES;

 error:
    fprintf(stderr, "[%s] %s did not load\n", [[seb class] name], filename);
    [seb free];
    if (src)
	destroy_bitmap(src);
    return NO;
}

static BOOL seborrhea_allegro_grid_grab(const char *filename, unsigned int index,
					unsigned int argc, Token *argv[],
					Sebum<SebContainer> *parent, DirectoryList *dirlist)
{
    const char *stem;
    int x, y, w, h, n, bpp;
    BITMAP *source;
    assert(filename && parent && dirlist);

    if (argc != 3 ||
	not [argv[0] isType:TOKEN_STRING] ||
	not [argv[1] isType:TOKEN_FIXNUM] ||
	not [argv[2] isType:TOKEN_FIXNUM])
	return NO;

    stem = [argv[0] getString];
    w    = [argv[1] getFixnum];
    h    = [argv[2] getFixnum];

    source = load_bitmap_somehow(filename, index, dirlist, &bpp);
    if (not source) {
	fprintf(stderr, "[SebImageAllegro] Error opening %s for grib-grab.\n", filename);
	return NO;
    }

    {					/* XXX: support for not re-opening same file. */
	for (y = 0, n = 0; y <= source->h - h; y += h) {
	    for (x = 0; x <= source->w - w; x += w, n++) {
		SebImageAllegro *seb;
		char name[1024];

		snprintf(name, sizeof name, "%s-%03d", stem, n);
		if (bpp == 32)
		    seb = [[SebImageAllegroWithAlpha alloc] initWithName:name];
		else
		    seb = [[SebImageAllegro alloc] initWithName:name];
		[seb grabFromBitmap:source WithX:x Y:y W:w H:h];

		[parent addToList:seb];
	    }
	}
    }

    destroy_bitmap(source);
    return YES;
}

static BOOL seborrhea_allegro_grab_row_animation(const char *filename, unsigned int index,
						 unsigned int argc, Token *argv[],
						 Sebum<SebContainer> *parent, DirectoryList *dirlist)
{
    /* XXX */
    const char *stem;
    int row, bpp;
    BITMAP *source;
    assert(filename && parent && dirlist);

    if (argc != 2 ||
	not [argv[0] isType:TOKEN_STRING] ||
	not [argv[1] isType:TOKEN_FIXNUM])
	return NO;

    stem = [argv[0] getString];
    row  = [argv[1] getFixnum];

    source = load_bitmap_somehow(filename, index, dirlist, &bpp);
    if (not source) {
	fprintf(stderr, "[SebImageAllegro] Error opening %s for row-animation.\n", filename);
	return NO;
    }

    {
	SebAnimation *anim;
	int grid_colour = getpixel(source, 0, 0);
	int x1=1, x2, y1, y2, n = 0;

	/* Find y1. */
	for (y1 = 0; y1 < source->h && row >= 0; y1++)
	    if (getpixel(source, x1, y1) == grid_colour)
		row--;

	/* Find y2. */
	for (y2 = y1 + 1; y2 < source->h; y2++)
	    if (getpixel(source, x1, y2) == grid_colour)
		break;
	if (y2 == source->h)
	    goto abort;
	y2--;
	assert(y2 > y1);

	/* Create the animation object. */
	anim = [[SebAnimation alloc] initWithName:stem];
	[parent addToList:anim];

	x2 = x1+1;

	while (x2 < source->w) {
	    char name[16];
	    SebImageAllegro *seb;

	    if (getpixel(source, x2, y1) != grid_colour) {
		x2++;
		continue;
	    }
	    
	    x2--;
	    assert(x2 > x1);

	    snprintf(name, sizeof name, "%03d", n);
	    seb = [[SebImageAllegro alloc] initWithName:name];
	    [seb grabFromBitmap:source WithX:x1 Y:y1 W:(x2-x1+1) H:(y2-y1+1)];
	    [anim addToList:seb];

	    /* Take into account 1 pixel border between 2 pictures. */
	    x1 = x2 + 2;
	    x2 = x1 + 1;
	    n++;
	}
    }

    destroy_bitmap(source);
    return YES;

 abort:

    destroy_bitmap(source);
    return NO;
}


/*------------------------------------------------------------*/
/* Samples                                                    */
/*------------------------------------------------------------*/

/* load_wav:
 *  Reads a RIFF WAV format sample file, returning a SAMPLE structure, 
 *  or NULL on error.  Stolen from allegro/src/sound.c and modified.
 */
static SAMPLE *load_wave_from_memory(const char *filename, DirectoryList *dirlist)
{
#ifndef NOZZIP
   ZZIP_FILE *f;
   char buffer[25];
   int i;
   int length, len;
   int freq = 22050;
   int bits = 8;
   int channels = 1;
   int s;
   SAMPLE *spl = NULL;
   assert(filename && dirlist);

   f = [dirlist openFile:filename];
   if (not f)
      return NULL;

   if (zzpk_fread(buffer, 12, f) != 12)	/* check RIFF header */
      goto getout;
   if (memcmp(buffer, "RIFF", 4) || memcmp(buffer+8, "WAVE", 4))
      goto getout;

   while (TRUE) {
      if (zzpk_fread(buffer, 4, f) != 4)
	 break;

      length = zzpk_igetl(f);          /* read chunk length */

      if (memcmp(buffer, "fmt ", 4) == 0) {
	 i = zzpk_igetw(f);            /* should be 1 for PCM data */
	 length -= 2;
	 if (i != 1) 
	    goto getout;

	 channels = zzpk_igetw(f);     /* mono or stereo data */
	 length -= 2;
	 if ((channels != 1) && (channels != 2))
	    goto getout;

	 freq = zzpk_igetl(f);         /* sample frequency */
	 length -= 4;

	 zzpk_igetl(f);                /* skip six bytes */
	 zzpk_igetw(f);
	 length -= 6;

	 bits = zzpk_igetw(f);         /* 8 or 16 bit data? */
	 length -= 2;
	 if ((bits != 8) && (bits != 16))
	    goto getout;
      }
      else if (memcmp(buffer, "data", 4) == 0) {
	 len = length / channels;

	 if (bits == 16)
	    len /= 2;

	 spl = create_sample(bits, ((channels == 2) ? TRUE : FALSE), freq, len);

	 if (spl) {
	    if (bits == 8) {
	       if (zzpk_fread(spl->data, length, f) < length) {
		  destroy_sample(spl);
		  spl = NULL;
	       }
	    }
	    else {
	       for (i=0; i<len*channels; i++) {
		  if ((s = zzpk_igetw(f)) == EOF) {
		     destroy_sample(spl);
		     spl = NULL;
		     break;
		  }
		  ((signed short *)spl->data)[i] = (signed short)s^0x8000;
	       }
	    }

	    length = 0;
	 }
      }

      while (length > 0) {             /* skip the remainder of the chunk */
	 if (zzpk_getc(f) == EOF)
	    break;

	 length--;
      }
   }

 getout:
   [dirlist closeFile:f];
   return spl;
#else
   return NULL;
#endif
}

@implementation SebSampleAllegro
- init
{
    [super init];
    default_volume = 128;
    return self;
}

- free
{
    if (sample)
	destroy_sample(sample);
    return [super free];
}

- setDefaultVolume:(unsigned int)volume
{
    default_volume = volume;
    return self;
}

- (unsigned int) defaultVolume { return default_volume; }

- (BOOL) loadFromFile:(const char *)file
{
    assert(file && not sample);
    sample = load_sample(file);
    return sample ? YES : NO;
}

- (BOOL) loadFromMemory:(const char *)file :(DirectoryList *)dirlist
{
    assert(file && not sample);
    sample = load_wave_from_memory(file, dirlist);
    return sample ? YES : NO;
}

- (BOOL) play { return [self playWithVolume:1.0 Pan:128]; }
- (BOOL) playWithPan:(int)pan { return [self playWithVolume:1.0 Pan:pan]; }
- (BOOL) playWithVolume:(double)volume { return [self playWithVolume:volume Pan:128]; }

- (BOOL) playWithVolume:(double)volume Pan:(int)pan
{
    if (sample) {
	int vol = default_volume * volume;
	vol = MID(0, vol, 255);
	play_sample(sample, vol, pan, 1000, 0);
	return YES;
    }
    else
	return NO;
}

- (SAMPLE *) sample { return sample; }
@end


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

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

    if ([[dirlist class] isReal]) {
	if (not [seb loadFromFile:filename])
	    goto error;
    }
    else {
	if (not [seb loadFromMemory:filename :dirlist])
	    goto error;
    }

    [parent addToList:seb];
    return YES;

 error:
    fprintf(stderr, "[SebSampleAllegro] %s did not load\n", filename);
    if (seb)
	[seb free];
    return NO;
}

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

BOOL seborrhea_allegro_init(void)
{
    _png_screen_gamma = 0.0;

    /* Register PNG for use with load_bitmap. */
    /* register_bitmap_file_type("png", load_png, NULL); */

    /* Images. */
    seborrhea_register_filetype_handler("png", seborrhea_allegro_load_image);
    seborrhea_register_metafile_command("grid-grab", seborrhea_allegro_grid_grab);
    seborrhea_register_metafile_command("row-animation", seborrhea_allegro_grab_row_animation);

    /* Samples. */
    seborrhea_register_filetype_handler("wav", seborrhea_allegro_load_sample);

    return YES;
}

void seborrhea_allegro_shutdown(void) {}
