/* map.m,
 *
 * The map.  What did you expect?
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "map.h"
#include "seborrhea/container-animation.h"
#include "tile.h"


#define ON_MAP(x,y)		((x) >= 0 && (x) < MAP_W_TILES && (y) >= 0 && (y) < map_length)
#define foreach_tile(x,y)	for (y = 0; y < map_length; y++) foreach_x(x)
#define foreach_x(x)		for (x = 0; x < MAP_W_TILES; x++)


Map *current_map;
double offsetX, offsetY;


@implementation Map
- init
{
    [super init];

    map_length = -1;
    current_scroll_rate = 0.0;
    desired_scroll_rate = DEFAULT_SCROLL_RATE;
    pixels_to_stop_scrolling = screen_h/2;

    return self;
}

- free
{
    while (map_length > 0) {
	int x;
	map_length--;

	foreach_x (x) {
	    /* Free the animator classes. */
	    if (base_tiles[map_length][x] &&
		[base_tiles[map_length][x] conformsTo:@protocol(UniqueSebum)])
		[base_tiles[map_length][x] free];
	    if (parallax_tiles[map_length][x] &&
		[parallax_tiles[map_length][x] conformsTo:@protocol(UniqueSebum)])
		[parallax_tiles[map_length][x] free];
	}

	free(base_tiles[map_length]);
	free(parallax_tiles[map_length]);
    }

    if (base_tiles)
	free(base_tiles);
    if (parallax_tiles)
	free(parallax_tiles);

    return [super free];
}

- (id) reset
{
    int x, y;

    foreach_tile (x, y) {
	base_tiles[y][x] = get_tile_by_index(0);
	parallax_tiles[y][x] = nil;
    }

    return self;
}

- (id) resize:(int)new_length
{
    void *p, *q;
    int x, y;

    /* Free. */
    if (map_length > new_length) {
	for (y = new_length; y < map_length; y++) {
	    free(base_tiles[y]);
	    free(parallax_tiles[y]);
	}
    }

    p = realloc(base_tiles,     new_length * sizeof(Sebum **));
    q = realloc(parallax_tiles, new_length * sizeof(Sebum **));
    if (p && q) {
	base_tiles = p;
	parallax_tiles = q;
    }
    else
	goto error;

    for (y = 0; y < new_length; y++) {
	/* We are in yet-to-be-allocated area.  Tell realloc. */
	if (y >= map_length) {
	    base_tiles[y] = NULL;
	    parallax_tiles[y] = NULL;
	}

	p = realloc(base_tiles[y],     MAP_W_TILES * sizeof(Sebum *));
	q = realloc(parallax_tiles[y], MAP_W_TILES * sizeof(Sebum *));
	if (p && q) {
	    base_tiles[y] = p;
	    parallax_tiles[y] = q;

	    /* We are still in the old map area, keep the data. */
	    if (y < map_length)
		continue;

	    foreach_x (x) {
		base_tiles[y][x] = get_tile_by_index(0);
		parallax_tiles[y][x] = nil;
	    }
	}
	else
	    goto error;
    }

    map_length = new_length;
    return self;

 error:
    fprintf(stderr, "Error allocating memory for new map size.\n");
    return nil;
}

- (void) drawParallaxLayer:(BITMAP *)dest :(BOOL)parallaxiness
{
    int x, y, yend, xx, yy;
    assert(dest);

    y = MAX(0, (offset_y - SCREEN_H) / TILE_H);
    yend = MIN((offset_y + 2*SCREEN_H) / TILE_H + 1, map_length);
    for (; y < yend; y++) {
	foreach_x (x) {
	    if (not parallax_tiles[y][x])
		continue;

	    xx = x * TILE_W - offset_x;
	    yy = y * TILE_H - offset_y;

	    /* We don't want to see parallax in REdit, otherwise
	       it edits funny.

	       The formula for yy is:
	       screen_h_2 + (screen_h_4 * (yy - screen_h_2)/screen_h_2)
	       but can be simplified to: */
	    if (parallaxiness) {
		yy = screen_h/2 + (yy/2 - screen_h/4);
		xx -= 16;
		[parallax_tiles[y][x] drawTo:dest X:xx Y:yy];
	    }
	    else {
		int ww = (x == MAP_W_TILES-1) ? TILE_W/2 : TILE_W;
		[parallax_tiles[y][x] drawTo:dest X:xx Y:yy W:ww H:TILE_H];
	    }
	}
    }
}

- (void) drawTiles:(BITMAP *)dest
{
    int x, y, yend, xx, yy;
    assert(dest);

    yend = MIN((offset_y + SCREEN_H) / TILE_H + 1, map_length);
    for (y = offset_y / TILE_H; y < yend; y++) {
	foreach_x (x) {
	    if (not base_tiles[y][x])
		continue;

	    xx = x * TILE_W - offset_x;
	    yy = y * TILE_H - offset_y;

	    if (spawn_stars) {
		/* If the map has stars, the tiles can be transparent,
		   so use draw_sprite. */
		[base_tiles[y][x] drawTo:dest X:xx Y:yy];
	    }
	    else {
		/* Version 0.2 increased the map width by 1/2 a tile.
		   This is so REdit looks right. */
		int ww = (x == MAP_W_TILES-1) ? TILE_W/2 : TILE_W;
		[base_tiles[y][x] drawTo:dest X:xx Y:yy W:ww H:TILE_H];
	    }
	}
    }

#if 0
    clear_to_color(dest, makecol(0x10, 0x10, 0x20));
#endif
}

- (int) length { return map_length; }
- (double) offsetX { return offset_x; }
- (double) offsetY { return offset_y; }

- (void) setOffsetX:(double)ox Y:(double)oy
{
    offsetX = offset_x = ox;
    offsetY = offset_y = oy;
}

- (double) scrollRate { return current_scroll_rate; }

- (void) setScrollRate:(double)new_rate :(BOOL)absolute
{
    if (absolute)
	desired_scroll_rate = new_rate;
    else
	desired_scroll_rate += new_rate;
}

- (void) update
{
    if (not (game_flags & FLAG_PLAYERS_ALIVE)) {
	if (pixels_to_stop_scrolling <= 0)
	    desired_scroll_rate = 0;
	else
	    pixels_to_stop_scrolling--;
    }

    if (ABS(desired_scroll_rate - current_scroll_rate) < 0.05)
	current_scroll_rate = desired_scroll_rate;
    else if (desired_scroll_rate > current_scroll_rate)
	current_scroll_rate += 0.05;
    else if (desired_scroll_rate < current_scroll_rate)
	current_scroll_rate -= 0.05;

    offset_y += current_scroll_rate;
    if (offset_y < 0.0) {
	offset_y = 0.0;
	current_scroll_rate = 0.0;
	desired_scroll_rate = 0.0;
    }

    /* For convenience.  Note: assuming only that this function is
       only called by the active map. */
    offsetX = offset_x;
    offsetY = offset_y;
}

- (const char *)musicDirname { return music_dirname ? music_dirname : "level1"; }
- (id) setStars:(BOOL)want_stars { spawn_stars = want_stars; return self; }
- (BOOL) stars { return spawn_stars; }

	/* Load/Save. */
- (int) appendToIndex:(Sebum<SebImage> ***)tiles :(Sebum<SebImage> **)index :(int)len
{
    int i, x, y;
    const char *name;

    foreach_tile (x, y) {
	if (not tiles[y][x])
	    continue;

	name = [tiles[y][x] name];

	for (i = 0; i <= len; i++) {
	    if (i == len) {
		/* Tile not found in index. */
		assert(len < 1024);
		index[len] = tiles[y][x];
		len++;
		break;
	    }

	    /* This tile is already indexed. */
	    else if (streq(name, [index[i] name]))
		break;
	}
    }

    return len;
}

- (void) writeMap:(Sebum<SebImage> ***)tiles
		 :(Sebum<SebImage> **)index :(int)index_len :(PACKFILE *)fp
{
    int i, x, y;
    const char *name;
    assert(fp);

    foreach_tile (x, y) {
	if (not tiles[y][x]) {
	    pack_iputw(-1, fp);
	    continue;
	}

	name = [tiles[y][x] name];

	for (i = 0; i < index_len; i++) {
	    if (streq(name, [index[i] name])) {
		pack_iputw(i, fp);
		break;
	    }
	}
    }
}

- (BOOL) readMap:(const char *)version :(Sebum<SebImage> ***)tiles
		:(Sebum<SebImage> **)index :(int)index_len :(PACKFILE *)fp
{
    int i, x, y;
    BOOL error = NO;
    assert(fp);

    foreach_tile (x, y) {
	/* v0.1 maps were only 5 tiles wide -> skip one tile. */
	if (streq(version, "0.1") && x == 5) {
	    if (x == 5)
		continue;
	}

	/* -1 denotes tile = nil.  Since we are using words, be
	   careful not to mangle the -ve sign when we read it back. */
	i = (int)((short)pack_igetw(fp));
	if (i >= index_len || not index[i]) {
	    printf("Tile missing at (%d, %d).\n", x, y);
	    [self setTileAt:tiles :x :y to:nil];
	    error = YES;
	}
	else {
	    /* Go through setTileAt because we need to create
	       SebAnimator classes. */
	    if (i < 0)
		[self setTileAt:tiles :x :y to:nil];
	    else
		[self setTileAt:tiles :x :y to:index[i]];
	}
    }

    return error;
}

	/* For use in map editor only! */
- (void) saveTo:(PACKFILE *)fp
{
    /* XXX: inefficient, but good enough? */
    Sebum<SebImage> *index[1024];
    int index_len = 0, i;

    assert(fp);

    /* Do we want stars? */
    pack_iputw(spawn_stars, fp);

    /* Index each Sebum. */
    index_len = [self appendToIndex:base_tiles :index :0];
    if (spawn_stars)
	index_len = [self appendToIndex:parallax_tiles :index :index_len];

    /* How many tile types there are. */
    pack_iputw(index_len, fp);

    /* Write out the index of sebums. */
    for (i = 0; i < index_len; i++) {
	char name[1024];
	snprintf(name, sizeof name, "/tiles/%s\n", [index[i] name]);
	pack_fputs(name, fp);
    }

    /* How long the map is. */
    pack_iputw(map_length, fp);

    /* The tiles and parallax tiles if necessary. */
    [self writeMap:base_tiles :index :index_len :fp];
    if (spawn_stars)
	[self writeMap:parallax_tiles :index :index_len :fp];
}

- (void) loadFrom:(PACKFILE *)fp version:(const char *)version
{
    Sebum<SebImage> *index[1024];
    int index_len, i;
    BOOL error = NO;
    assert(fp);

    if (strneq(version, "0.1")) {
	spawn_stars = pack_igetw(fp);
    }
    else
	spawn_stars = NO;

    /* How many tile types there are. */
    index_len = pack_igetw(fp);
    assert(index_len < 1024);

    /* What Sebum number n refers to. */
    for (i = 0; i < index_len; i++) {
	char name[1024];	/* XXX? */
	pack_fgets(name, sizeof name, fp);
	index[i] = get_tile_by_name(name);

	if (not index[i])
	    fprintf(stderr, "[Map] Missing tile %s.\n", name);
    }

    /* How long the map is. */
    [self resize:pack_igetw(fp)];

    /* The map tiles. */
    error = [self readMap:version :base_tiles :index :index_len :fp];
    if (spawn_stars) {
	if ([self readMap:version :parallax_tiles :index :index_len :fp])
	    error = YES;
    }

    if (error) {
	fprintf(stderr,
		"Errors occured while loading the map.\n"
		"Are you missing some datafiles?\n");
    }

    desired_scroll_rate = DEFAULT_SCROLL_RATE;
}

- (BOOL) setTileAt:(Sebum<SebImage> ***)tiles :(int)x :(int)y to:(Sebum<SebImage> *)tile
{
    if (not ON_MAP(x,y))
	return NO;

    if ([tile isKindOf:[SebAnimation class]]) {
	/* This is an animation.  Create an animator if necessary. */
	if (not [tiles[y][x] conformsTo:@protocol(UniqueSebum)])
	    tiles[y][x] = [SebAnimator new];

	[(SebAnimator *)tiles[y][x] setAnimation:(SebAnimation *)tile];
    }
    else {
	if ([tiles[y][x] conformsTo:@protocol(UniqueSebum)])
	    [tiles[y][x] free];

	tiles[y][x] = tile;
    }
    return YES;
}

- (BOOL) setParallaxTileAt:(int)x :(int)y to:(Sebum<SebImage> *)tile
{
    return [self setTileAt:parallax_tiles :x :y to:tile];
}

- (BOOL) setTileAt:(int)x :(int)y to:(Sebum<SebImage> *)tile
{
    return [self setTileAt:base_tiles :x :y to:tile];
}

- (void) pan:(int)deltay
{
    int x, y;

    /* Positive y for down. */
    if (deltay < 0) {		/* Pan up. */
	deltay = -deltay;
	for (y = 0; y < map_length - deltay; y++) {
	    foreach_x (x) {
		if ([base_tiles[y][x] conformsTo:@protocol(UniqueSebum)])
		    [base_tiles[y][x] free];

		base_tiles[y][x] = base_tiles[y+deltay][x];
	    }
	}

	for (; y < map_length; y++)
	    foreach_x (x) base_tiles[y][x] = get_tile_by_index(0);
    }
    else if (deltay > 0) {		/* Pan down. */
	for (y = map_length-1; y >= deltay; y--) {
	    foreach_x (x) {
		if ([base_tiles[y][x] conformsTo:@protocol(UniqueSebum)])
		    [base_tiles[y][x] free];

		base_tiles[y][x] = base_tiles[y-deltay][x];
	    }
	}

	for (; y >= 0; y--)
	    foreach_x (x) base_tiles[y][x] = get_tile_by_index(0);
    }
}

- (void) setMusicDirname:(const char *)fn
{
    /* Note that we receive a filename, so need to strip it off. */
    char temp[PATH_MAX];
    char *filename_without_path, *dirname = temp;

    if (music_dirname)
	free(music_dirname);

    strcpy(temp, fn);
    filename_without_path = strrchr(temp, '/');
    if (filename_without_path) {
	*filename_without_path = '\0';

	dirname = strrchr(temp, '/');
	if (dirname)
	    *dirname++;
    }

    music_dirname = strdup(dirname);
}
@end
