/* ScummVM - Scumm Interpreter
 * Copyright (C) 2006 The ScummVM project
 *
 * Copyright (C) 1999-2001 Sarien Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-9-1/engines/agi/checks.cpp $
 * $Id: checks.cpp 22638 2006-05-25 22:39:44Z wjpalenstijn $
 *
 */

#include "common/stdafx.h"
#include "agi/agi.h"

namespace Agi {

static int check_position(struct vt_entry *v) {
	debugC(4, kDebugLevelSprites, "check position @ %d, %d", v->x_pos, v->y_pos);

	if (v->x_pos < 0 ||
			v->x_pos + v->x_size > _WIDTH ||
			v->y_pos - v->y_size + 1 < 0 ||
			v->y_pos >= _HEIGHT ||
			((~v->flags & IGNORE_HORIZON) && v->y_pos <= game.horizon)) {
		debugC(4, kDebugLevelSprites, "check position failed: x=%d, y=%d, h=%d, w=%d",
				v->x_pos, v->y_pos, v->x_size, v->y_size);
		return 0;
	}

	/* MH1 needs this, but it breaks LSL1 */
	if (agi_get_release() >= 0x3000) {
		if (v->y_pos < v->y_size)
			return 0;
	}

	return 1;
}

/**
 * Check if there's another object on the way
 */
static int check_collision(struct vt_entry *v) {
	struct vt_entry *u;

	if (v->flags & IGNORE_OBJECTS)
		return 0;

	for (u = game.view_table; u < &game.view_table[MAX_VIEWTABLE]; u++) {
		if ((u->flags & (ANIMATED | DRAWN)) != (ANIMATED | DRAWN))
			continue;

		if (u->flags & IGNORE_OBJECTS)
			continue;

		/* Same object, check next */
		if (v->entry == u->entry)
			continue;

		/* No horizontal overlap, check next */
		if (v->x_pos + v->x_size < u->x_pos ||
				v->x_pos > u->x_pos + u->x_size)
			continue;

		/* Same y, return error! */
		if (v->y_pos == u->y_pos)
			goto return_1;

		/* Crossed the baseline, return error! */
		if ((v->y_pos > u->y_pos && v->y_pos2 < u->y_pos2) ||
				(v->y_pos < u->y_pos && v->y_pos2 > u->y_pos2)) {
			goto return_1;
		}
	}

	return 0;

 return_1:
	debugC(4, kDebugLevelSprites, "check returns 1 (object %d)", v->entry);
	return 1;
}

static int check_priority(struct vt_entry *v) {
	int i, trigger, water, pass, pri;
	uint8 *p0;

	if (~v->flags & FIXED_PRIORITY) {
		/* Priority bands */
		v->priority = game.pri_table[v->y_pos];
	}

	trigger = 0;
	water = 0;
	pass = 1;

	if (v->priority == 0x0f)
		goto _check_ego;

	water = 1;

	p0 = &game.sbuf[v->x_pos + v->y_pos * _WIDTH];

	for (i = 0; i < v->x_size; i++, p0++) {
		pri = *p0 >> 4;

		if (pri == 0) {	/* unconditional black. no go at all! */
			pass = 0;
			break;
		}

		if (pri == 3)	/* water surface */
			continue;

		water = 0;

		if (pri == 1) {	/* conditional blue */
			if (v->flags & IGNORE_BLOCKS)
				continue;

			debugC(4, kDebugLevelSprites, "Blocks observed!");
			pass = 0;
			break;
		}

		if (pri == 2) {	/* trigger */
			debugC(4, kDebugLevelSprites, "stepped on trigger");
			if (!debug_.ignoretriggers)
				trigger = 1;
		}
	}

	if (pass) {
		if (!water && v->flags & ON_WATER)
			pass = 0;
		if (water && v->flags & ON_LAND)
			pass = 0;
	}

      _check_ego:
	if (v->entry == 0) {
		setflag(F_ego_touched_p2, trigger ? true : false);
		setflag(F_ego_water, water ? true : false);
	}

	return pass;
}

/*
 * Public functions
 */

/**
 * Update position of objects
 * This function updates the position of all animated, updating view
 * table entries according to its motion type, step size, etc. The
 * new position must be valid according to the sprite positioning
 * rules, otherwise the previous position will be kept.
 */
void update_position() {
	struct vt_entry *v;
	int x, y, old_x, old_y, border;

	game.vars[V_border_code] = 0;
	game.vars[V_border_touch_ego] = 0;
	game.vars[V_border_touch_obj] = 0;

	for (v = game.view_table; v < &game.view_table[MAX_VIEWTABLE]; v++) {
		if ((v->flags & (ANIMATED | UPDATE | DRAWN)) != (ANIMATED | UPDATE | DRAWN)) {
			continue;
		}

		if (v->step_time_count != 0) {
			if (--v->step_time_count != 0)
				continue;
		}

		v->step_time_count = v->step_time;

		x = old_x = v->x_pos;
		y = old_y = v->y_pos;

		/* If object has moved, update its position */
		if (~v->flags & UPDATE_POS) {
			int dx[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
			int dy[9] = { 0, -1, -1, 0, 1, 1, 1, 0, -1 };
			x += v->step_size * dx[v->direction];
			y += v->step_size * dy[v->direction];
		}

		/* Now check if it touched the borders */
		border = 0;

		/* Check left/right borders */
		if (x < 0) {
			x = 0;
			border = 4;
		} else if (x <= 0 && agi_get_release() == 0x3086) {	/* KQ4 */
			x = 0;	/* See bug #590462 */
			border = 4;
		} else if (v->entry == 0 && x == 0 && v->flags & ADJ_EGO_XY) {
			/* Extra test to walk west clicking the mouse */
			x = 0;
			border = 4;
		} else if (x + v->x_size > _WIDTH) {
			x = _WIDTH - v->x_size;
			border = 2;
		}

		/* Check top/bottom borders. */
		if (y - v->y_size + 1 < 0) {
			y = v->y_size - 1;
			border = 1;
		} else if (y > _HEIGHT - 1) {
			y = _HEIGHT - 1;
			border = 3;
		} else if ((~v->flags & IGNORE_HORIZON) && y <= game.horizon) {
			debugC(4, kDebugLevelSprites, "y = %d, horizon = %d", y, game.horizon);
			y = game.horizon + 1;
			border = 1;
		}

		/* Test new position. rollback if test fails */
		v->x_pos = x;
		v->y_pos = y;
		if (check_collision(v) || !check_priority(v)) {
			v->x_pos = old_x;
			v->y_pos = old_y;
			border = 0;
			fix_position(v->entry);
		}

		if (border != 0) {
			if (is_ego_view(v)) {
				game.vars[V_border_touch_ego] = border;
			} else {
				game.vars[V_border_code] = v->entry;
				game.vars[V_border_touch_obj] = border;
			}
			if (v->motion == MOTION_MOVE_OBJ) {
				in_destination(v);
			}
		}

		v->flags &= ~UPDATE_POS;
	}
}

/**
 * Adjust position of a sprite
 * This function adjusts the position of a sprite moving it until
 * certain criteria is matched. According to priority and control line
 * data, a sprite may not always appear at the location we specified.
 * This behaviour is also known as the "Budin-Sonneveld effect".
 *
 * @param n view table entry number
 */
void fix_position(int n) {
	struct vt_entry *v = &game.view_table[n];
	int count, dir, size;

	debugC(4, kDebugLevelSprites, "adjusting view table entry #%d (%d,%d)", n, v->x_pos, v->y_pos);

	/* test horizon */
	if ((~v->flags & IGNORE_HORIZON) && v->y_pos <= game.horizon)
		v->y_pos = game.horizon + 1;

	dir = 0;
	count = size = 1;

	while (!check_position(v) || check_collision(v) || !check_priority(v)) {
		switch (dir) {
		case 0:	/* west */
			v->x_pos--;
			if (--count)
				continue;
			dir = 1;
			break;
		case 1:	/* south */
			v->y_pos++;
			if (--count)
				continue;
			dir = 2;
			size++;
			break;
		case 2:	/* east */
			v->x_pos++;
			if (--count)
				continue;
			dir = 3;
			break;
		case 3:	/* north */
			v->y_pos--;
			if (--count)
				continue;
			dir = 0;
			size++;
			break;
		}

		count = size;
	}

	debugC(4, kDebugLevelSprites, "view table entry #%d position adjusted to (%d,%d)", n, v->x_pos, v->y_pos);
}

} // End of namespace Agi
