/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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.
 *
 */

/*
 * This code is based on original Soltys source code
 * Copyright (c) 1994-1995 Janus B. Wisniewski and L.K. Avalon
 */

#include "cge/walk.h"
#include "cge/cge_main.h"

namespace CGE {

Cluster::Cluster(CGEEngine *vm, int16 a, int16 b) : _vm(vm) {
	_pt = Common::Point(a, b);
}

Cluster::Cluster(CGEEngine *vm) : _vm(vm) {
	_pt = Common::Point(-1, -1);
}

uint8 &Cluster::cell() {
	return _vm->_clusterMap[_pt.y][_pt.x];
}

bool Cluster::isValid() const {
	return (_pt.x >= 0) && (_pt.x < kMapXCnt) && (_pt.y >= 0) && (_pt.y < kMapZCnt);
}

Walk::Walk(CGEEngine *vm, BitmapPtr *shpl)
	: Sprite(vm, shpl), _dir(kDirNone), _tracePtr(-1), _level(0), _target(-1, -1), _findLevel(-1), _here(vm), _vm(vm) {
	for (int i = 0; i < kMaxFindLevel; i++) {
		Cluster *tmpClust = new Cluster(_vm);
		_trace.push_back(tmpClust);
	}
}

Walk::~Walk() {
	for (uint idx = 0; idx < _trace.size(); ++idx)
		delete _trace[idx];
}

void Walk::tick() {
	if (_flags._hide)
		return;

	_here = _vm->XZ(_x + _w / 2, _y + _h);

	if (_dir != kDirNone) {
		_vm->_sys->funTouch();
		for (Sprite *spr = _vm->_vga->_showQ->first(); spr; spr = spr->_next) {
			if (distance(spr) < 2) {
				if (!spr->_flags._near) {
					_vm->feedSnail(spr, kNear);
					spr->_flags._near = true;
				}
			} else {
				spr->_flags._near = false;
			}
		}
	}

	if (_flags._hold || _tracePtr < 0) {
		park();
	} else {
		if (_here._pt == _trace[_tracePtr]->_pt) {
			if (--_tracePtr < 0)
				park();
		} else {
			Common::Point tmpPoint = _trace[_tracePtr]->_pt - _here._pt;
			int16 dx = tmpPoint.x;
			int16 dz = tmpPoint.y;
			Dir d = (dx) ? ((dx > 0) ? kDirEast : kDirWest) : ((dz > 0) ? kDirSouth : kDirNorth);
			turn(d);
		}
	}

	step();

	if ((_dir == kDirWest  && _x <= 0) ||
	    (_dir == kDirEast  && _x + _w >= kScrWidth) ||
	    (_dir == kDirSouth && _y + _w >= kWorldHeight - 2)) {
		park();
	} else {
		// take current Z position
		_z = _here._pt.y;
		_vm->_commandHandlerTurbo->addCommand(kCmdZTrim, -1, 0, this);    // update Hero's pos in show queue
	}
}

int Walk::distance(Sprite *spr) {
	int dx = spr->_x - (_x + _w - kWalkSide);
	if (dx < 0)
		dx = (_x + kWalkSide) - (spr->_x + spr->_w);

	if (dx < 0)
		dx = 0;

	dx /= kMapGridX;
	int dz = spr->_z - _z;
	if (dz < 0)
		dz = - dz;

	dx = dx * dx + dz * dz;
	for (dz = 1; dz * dz < dx; dz++)
		;

	return dz - 1;
}

void Walk::turn(Dir d) {
	Dir dir = (_dir == kDirNone) ? kDirSouth : _dir;
	if (d != _dir) {
		step((d == dir) ? (1 + dir + dir) : (9 + 4 * dir + d));
		_dir = d;
	}
}

void Walk::park() {
	if (_time == 0)
		_time++;

	if (_dir != kDirNone) {
		step(9 + 4 * _dir + _dir);
		_dir = kDirNone;
		_tracePtr = -1;
	}
}

void Walk::findWay(Cluster c) {
	if (c._pt == _here._pt)
		return;

	for (_findLevel = 1; _findLevel <= kMaxFindLevel; _findLevel++) {
		_target = _here._pt;
		int16 x = c._pt.x;
		int16 z = c._pt.y;

		if (find1Way(Cluster(_vm, x, z)))
			break;
	}

	_tracePtr = (_findLevel > kMaxFindLevel) ? -1 : (_findLevel - 1);
	if (_tracePtr < 0)
		noWay();
	_time = 1;
}

void Walk::findWay(Sprite *spr) {
	if (!spr || spr == this)
		return;

	int x = spr->_x;
	int z = spr->_z;
	if (spr->_flags._east)
		x += spr->_w + _w / 2 - kWalkSide;
	else
		x -= _w / 2 - kWalkSide;

	findWay(Cluster(_vm, (x / kMapGridX),
	                ((z < kMapZCnt - kDistMax) ? (z + 1)
	                 : (z - 1))));
}

bool Walk::lower(Sprite *spr) {
	return (spr->_y > _y + (_h * 3) / 5);
}

void Walk::reach(Sprite *spr, int mode) {
	if (spr) {
		_vm->_hero->findWay(spr);
		if (mode < 0) {
			mode = spr->_flags._east;
			if (lower(spr))
				mode += 2;
		}
	}
	// note: insert SNAIL commands in reverse order
	_vm->_commandHandler->insertCommand(kCmdPause, -1, 64, NULL);
	_vm->_commandHandler->insertCommand(kCmdSeq, -1, kTSeq + mode, this);
	if (spr) {
		_vm->_commandHandler->insertCommand(kCmdWait, -1, -1, _vm->_hero);
		//SNINSERT(SNWALK, -1, -1, spr);
	}
	// sequence is not finished,
	// now it is just at sprite appear (disappear) point
}

void Walk::noWay() {
	_vm->trouble(kSeqNoWay, kNoWay);
}

bool Cluster::chkBar() const {
	assert(_vm->_now <= kSceneMax);
	return (_pt.x == _vm->_barriers[_vm->_now]._horz) || (_pt.y == _vm->_barriers[_vm->_now]._vert);
}

bool Walk::find1Way(Cluster c) {
	const Cluster tab[4] = { Cluster(_vm, -1, 0), Cluster(_vm, 1, 0), Cluster(_vm, 0, -1), Cluster(_vm, 0, 1)};
	const int tabLen = 4;

	if (c._pt == _target)
		// Found destination
		return true;

	if (_level >= _findLevel)
		// Nesting limit
		return false;

	// Look for barriers
	if (c.chkBar())
		return false;

	if (c.cell())
		// Location is occupied
		return false;

	// Loop through each direction
	Cluster start = c;
	for (int i = 0; i < tabLen; i++) {
		// Reset to starting position
		c = start;

		do {
			c._pt += tab[i]._pt;
			if (!c.isValid())
				// Break to check next direction
				break;

			// Recursively check for further paths
			++_level;
			++start.cell();
			bool foundPath = find1Way(c);
			--start.cell();
			--_level;

			if (foundPath) {
				// Set route point
				_trace[_level]->_pt = start._pt;
				return true;
			}
		} while (!c.chkBar() && !c.cell());
	}

	return false;
}

} // End of namespace CGE
