/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * This code is based on original Hugo Trilogy source code
 *
 * Copyright (c) 1989-1995 David P. Gray
 *
 */

// Find shortest route from hero to destination

#include "common/debug.h"
#include "common/system.h"

#include "hugo/hugo.h"
#include "hugo/game.h"
#include "hugo/route.h"
#include "hugo/object.h"
#include "hugo/inventory.h"
#include "hugo/mouse.h"

namespace Hugo {
Route::Route(HugoEngine *vm) : _vm(vm) {
	_oldWalkDirection = 0;
	_routeIndex       = -1;                         // Hero not following a route
	_routeType        = kRouteSpace;                // Hero walking to space
	_routeObjId       = -1;                         // Hero not walking to anything

	for (int i = 0; i < kMaxSeg; i++)
		_segment[i]._y = _segment[i]._x1 = _segment[i]._x2 = 0;

	_segmentNumb = 0;
	_routeListIndex = 0;
	_destX = _destY = 0;
	_heroWidth = 0;
	_routeFoundFl = false;
	_fullStackFl = false;
	_fullSegmentFl = false;
}

void Route::resetRoute() {
	_routeIndex = -1;
}

int16 Route::getRouteIndex() const {
	return _routeIndex;
}

/**
 * Face hero in new direction, based on cursor key input by user.
 */
void Route::setDirection(const uint16 keyCode) {
	debugC(1, kDebugRoute, "setDirection(%d)", keyCode);

	Object *obj = _vm->_hero;                     // Pointer to hero object

	// Set first image in sequence
	switch (keyCode) {
	case Common::KEYCODE_UP:
	case Common::KEYCODE_KP8:
		obj->_currImagePtr = obj->_seqList[SEQ_UP]._seqPtr;
		break;
	case Common::KEYCODE_DOWN:
	case Common::KEYCODE_KP2:
		obj->_currImagePtr = obj->_seqList[SEQ_DOWN]._seqPtr;
		break;
	case Common::KEYCODE_LEFT:
	case Common::KEYCODE_KP4:
		obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
		break;
	case Common::KEYCODE_RIGHT:
	case Common::KEYCODE_KP6:
		obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
		break;
	case Common::KEYCODE_HOME:
	case Common::KEYCODE_KP7:
		obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
		break;
	case Common::KEYCODE_END:
	case Common::KEYCODE_KP1:
		obj->_currImagePtr = obj->_seqList[SEQ_LEFT]._seqPtr;
		break;
	case Common::KEYCODE_PAGEUP:
	case Common::KEYCODE_KP9:
		obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
		break;
	case Common::KEYCODE_PAGEDOWN:
	case Common::KEYCODE_KP3:
		obj->_currImagePtr = obj->_seqList[SEQ_RIGHT]._seqPtr;
		break;
	default:
		break;
	}
}

/**
 * Set hero walking, based on cursor key input by user.
 * Hitting same key twice will stop hero.
 */
void Route::setWalk(const uint16 direction) {
	debugC(1, kDebugRoute, "setWalk(%d)", direction);

	Object *obj = _vm->_hero;                     // Pointer to hero object

	if (_vm->getGameStatus()._storyModeFl || obj->_pathType != kPathUser) // Make sure user has control
		return;

	if (!obj->_vx && !obj->_vy)
		_oldWalkDirection = 0;                      // Fix for consistent restarts

	if (direction != _oldWalkDirection) {
		// Direction has changed
		setDirection(direction);                    // Face new direction
		obj->_vx = obj->_vy = 0;
		switch (direction) {                        // And set correct velocity
		case Common::KEYCODE_UP:
		case Common::KEYCODE_KP8:
			obj->_vy = -kStepDy;
			break;
		case Common::KEYCODE_DOWN:
		case Common::KEYCODE_KP2:
			obj->_vy =  kStepDy;
			break;
		case Common::KEYCODE_LEFT:
		case Common::KEYCODE_KP4:
			obj->_vx = -kStepDx;
			break;
		case Common::KEYCODE_RIGHT:
		case Common::KEYCODE_KP6:
			obj->_vx =  kStepDx;
			break;
		case Common::KEYCODE_HOME:
		case Common::KEYCODE_KP7:
			obj->_vx = -kStepDx;
			// Note: in v1 Dos and v2 Dos, obj->vy is set to DY
			obj->_vy = -kStepDy / 2;
			break;
		case Common::KEYCODE_END:
		case Common::KEYCODE_KP1:
			obj->_vx = -kStepDx;
			// Note: in v1 Dos and v2 Dos, obj->vy is set to -DY
			obj->_vy =  kStepDy / 2;
			break;
		case Common::KEYCODE_PAGEUP:
		case Common::KEYCODE_KP9:
			obj->_vx =  kStepDx;
			// Note: in v1 Dos and v2 Dos, obj->vy is set to -DY
			obj->_vy = -kStepDy / 2;
			break;
		case Common::KEYCODE_PAGEDOWN:
		case Common::KEYCODE_KP3:
			obj->_vx =  kStepDx;
			// Note: in v1 Dos and v2 Dos, obj->vy is set to DY
			obj->_vy =  kStepDy / 2;
			break;
		default:
			break;
		}
		_oldWalkDirection = direction;
		obj->_cycling = kCycleForward;
	} else {
		// Same key twice - halt hero
		obj->_vy = 0;
		obj->_vx = 0;
		_oldWalkDirection = 0;
		obj->_cycling = kCycleNotCycling;
	}
}

/**
 * Recursive algorithm!  Searches from hero to dest_x, dest_y
 * Find horizontal line segment about supplied point and recursively
 * find line segments for each point above and below that segment.
 * When destination point found in segment, start surfacing and leave
 * a trail in segment[] from destination back to hero.
 *
 * Note:  there is a bug which allows a route through a 1-pixel high
 * narrow gap if between 2 segments wide enough for hero.  To work
 * around this, make sure any narrow gaps are 2 or more pixels high.
 * An example of this was the blocking guard in Hugo1/Dead-End.
 */
void Route::segment(int16 x, int16 y) {
	debugC(1, kDebugRoute, "segment(%d, %d)", x, y);

	// Note: use of static - can't waste stack
	static ImagePtr  p;                             // Ptr to _boundaryMap[y]
	static Segment  *segPtr;                        // Ptr to segment

	// Bomb out if stack exhausted
	// Vinterstum: Is this just a safeguard, or actually used?
	//_fullStackFl = _stackavail () < 256;
	_fullStackFl = false;

	// Find and fill on either side of point
	p = _boundaryMap[y];
	int16 x1, x2;                                   // Range of segment
	for (x1 = x; x1 > 0; x1--) {
		if (p[x1] == 0) {
			p[x1] = kMapFill;
		} else {
			break;
		}
	}
	for (x2 = x + 1; x2 < kXPix; x2++) {
		if (p[x2] == 0) {
			p[x2] = kMapFill;
		} else {
			break;
		}
	}
	x1++;
	x2--;

	// Discard path if not wide enough for hero - dead end
	if (_heroWidth > x2 - x1 + 1)
		return;

	// Have we found the destination yet?
	if (y == _destY && x1 <= _destX && x2 >= _destX)
		_routeFoundFl = true;

	// Bounds check y in case no boundary around screen
	if (y <= 0 || y >= kYPix - 1)
		return;

	if (_vm->_hero->_x < x1) {
		// Hero x not in segment, search x1..x2
		// Find all segments above current
		for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
			if (_boundaryMap[y - 1][x] == 0)
				segment(x, y - 1);
		}

		// Find all segments below current
		for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
			if (_boundaryMap[y + 1][x] == 0)
				segment(x, y + 1);
		}
	} else if (_vm->_hero->_x + kHeroMaxWidth > x2) {
		// Hero x not in segment, search x1..x2
		// Find all segments above current
		for (x = x2; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x >= x1; x--) {
			if (_boundaryMap[y - 1][x] == 0)
				segment(x, y - 1);
		}

		// Find all segments below current
		for (x = x2; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x >= x1; x--) {
			if (_boundaryMap[y + 1][x] == 0)
				segment(x, y + 1);
		}
	} else {
		// Organize search around hero x position - this gives
		// better chance for more direct route.
		for (x = _vm->_hero->_x; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
			if (_boundaryMap[y - 1][x] == 0)
				segment(x, y - 1);
		}

		for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x < _vm->_hero->_x; x++) {
			if (_boundaryMap[y - 1][x] == 0)
				segment(x, y - 1);
		}

		for (x = _vm->_hero->_x; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x <= x2; x++) {
			if (_boundaryMap[y + 1][x] == 0)
				segment(x, y + 1);
		}

		for (x = x1; !(_routeFoundFl || _fullStackFl || _fullSegmentFl) && x < _vm->_hero->_x; x++) {
			if (_boundaryMap[y + 1][x] == 0)
				segment(x, y + 1);
		}
	}

	// If found, surface, leaving trail back to hero
	if (_routeFoundFl) {
		// Bomb out if too many segments (leave one spare)
		if (_segmentNumb >= kMaxSeg - 1) {
			_fullSegmentFl = true;
		} else {
			// Create segment
			segPtr = &_segment[_segmentNumb];
			segPtr->_y  = y;
			segPtr->_x1 = x1;
			segPtr->_x2 = x2;
			_segmentNumb++;
		}
	}
}

/**
 * Create and return ptr to new node.  Initialize with previous node.
 * Returns 0 if MAX_NODES exceeded
 */
Common::Point *Route::newNode() {
	debugC(1, kDebugRoute, "newNode");

	_routeListIndex++;
	if (_routeListIndex >= kMaxNodes)               // Too many nodes
		return nullptr;                             // Incomplete route - failure

	_route[_routeListIndex] = _route[_routeListIndex - 1];  // Initialize with previous node
	return &_route[_routeListIndex];
}

/**
 * Construct route to cx, cy.  Return TRUE if successful.
 * 1.  Copy boundary bitmap to local byte map (include object bases)
 * 2.  Construct list of segments segment[] from hero to destination
 * 3.  Compress to shortest route in route[]
 */
bool Route::findRoute(const int16 cx, const int16 cy) {
	debugC(1, kDebugRoute, "findRoute(%d, %d)", cx, cy);

	// Initialize for search
	_routeFoundFl  = false;                         // Path not found yet
	_fullStackFl = false;                           // Stack not exhausted
	_fullSegmentFl  = false;                        // Segments not exhausted
	_segmentNumb = 0;                               // Segment index
	_heroWidth = kHeroMinWidth;                     // Minimum width of hero
	_destY = cy;                                    // Destination coords
	_destX = cx;                                    // Destination coords

	int16 herox1 = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1;        // Hero baseline
	int16 herox2 = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x2;        // Hero baseline
	int16 heroy  = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2;        // Hero baseline

	// Store all object baselines into objbound (except hero's = [0])
	Object  *obj;                                   // Ptr to object
	int i;
	for (i = 1, obj = &_vm->_object->_objects[i]; i < _vm->_object->_numObj; i++, obj++) {
		if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling != kCycleInvisible) && (obj->_priority == kPriorityFloating))
			_vm->_object->storeBoundary(obj->_oldx + obj->_currImagePtr->_x1, obj->_oldx + obj->_currImagePtr->_x2, obj->_oldy + obj->_currImagePtr->_y2);
	}

	// Combine objbound and boundary bitmaps to local byte map
	for (uint16 y = 0; y < kYPix; y++) {
		for (uint16 x = 0; x < kCompLineSize; x++) {
			uint16 boundIdx = y * kCompLineSize + x;
			for (i = 0; i < 8; i++)
				_boundaryMap[y][x * 8 + i] = ((_vm->_object->getObjectBoundary(boundIdx) | _vm->_object->getBoundaryOverlay(boundIdx)) & (0x80 >> i)) ? kMapBound : 0;
		}
	}

	// Clear all object baselines from objbound
	for (i = 0, obj = _vm->_object->_objects; i < _vm->_object->_numObj; i++, obj++) {
		if ((obj->_screenIndex == *_vm->_screenPtr) && (obj->_cycling != kCycleInvisible) && (obj->_priority == kPriorityFloating))
			_vm->_object->clearBoundary(obj->_oldx + obj->_currImagePtr->_x1, obj->_oldx + obj->_currImagePtr->_x2, obj->_oldy + obj->_currImagePtr->_y2);
	}

	// Search from hero to destination
	segment(herox1, heroy);

	// Not found or not enough stack or MAX_SEG exceeded
	if (!_routeFoundFl || _fullStackFl || _fullSegmentFl) {
		return false;
	}

	// Now find the route of nodes from destination back to hero
	// Assign first node as destination
	_route[0].x = _destX;
	_route[0].y = _destY;

	// Make a final segment for hero's base (we left a spare)
	_segment[_segmentNumb]._y  = heroy;
	_segment[_segmentNumb]._x1 = herox1;
	_segment[_segmentNumb]._x2 = herox2;
	_segmentNumb++;

	// Look in segments[] for straight lines from destination to hero
	for (i = 0, _routeListIndex = 0; i < _segmentNumb - 1; i++) {
		Common::Point *routeNode;                   // Ptr to route node
		if ((routeNode = newNode()) == nullptr)           // New node for new segment
			return false;                           // Too many nodes
		routeNode->y = _segment[i]._y;

		// Look ahead for furthest straight line
		for (int16 j = i + 1; j < _segmentNumb; j++) {
			Segment *segPtr = &_segment[j];
			// Can we get to this segment from previous node?
			if (segPtr->_x1 <= routeNode->x && segPtr->_x2 >= routeNode->x + _heroWidth - 1) {
				routeNode->y = segPtr->_y;          // Yes, keep updating node
			} else {
				// No, create another node on previous segment to reach it
				if ((routeNode = newNode()) == nullptr)   // Add new route node
					return false;                   // Too many nodes

				// Find overlap between old and new segments
				int16 x1 = MAX(_segment[j - 1]._x1, segPtr->_x1);
				int16 x2 = MIN(_segment[j - 1]._x2, segPtr->_x2);

				// If room, add a little offset to reduce staircase effect
				int16 dx = kHeroMaxWidth >> 1;
				if (x2 - x1 < _heroWidth + dx)
					dx = 0;

				// Bear toward final hero position
				if (j == _segmentNumb - 1)
					routeNode->x = herox1;
				else if (herox1 < x1)
					routeNode->x = x1 + dx;
				else if (herox1 > x2 - _heroWidth + 1)
					routeNode->x = x2 - _heroWidth - dx;
				else
					routeNode->x = herox1;
				i = j - 2;                          // Restart segment (-1 to offset auto increment)
				break;
			}
		}

		// Terminate loop if we've reached hero
		if (routeNode->x == herox1 && routeNode->y == heroy)
			break;
	}
	return true;
}

/**
 * Process hero in route mode - called from Move_objects()
 */
void Route::processRoute() {
	debugC(1, kDebugRoute, "processRoute");

	static bool turnedFl = false;                   // Used to get extra cycle for turning

	if (_routeIndex < 0)
		return;

	// Current hero position
	int16 herox = _vm->_hero->_x + _vm->_hero->_currImagePtr->_x1;
	int16 heroy = _vm->_hero->_y + _vm->_hero->_currImagePtr->_y2;
	Common::Point *routeNode = &_route[_routeIndex];

	// Arrived at node?
	if (abs(herox - routeNode->x) < kStepDx + 1 && abs(heroy - routeNode->y) < kStepDy) {
		// kStepDx too low
		// Close enough - position hero exactly
		_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
		_vm->_hero->_y = _vm->_hero->_oldy = routeNode->y - _vm->_hero->_currImagePtr->_y2;
		_vm->_hero->_vx = _vm->_hero->_vy = 0;
		_vm->_hero->_cycling = kCycleNotCycling;

		// Arrived at final node?
		if (--_routeIndex < 0) {
			// See why we walked here
			switch (_routeType) {
			case kRouteExit:                        // Walked to an exit, proceed into it
				setWalk(_vm->_mouse->getDirection(_routeObjId));
				break;
			case kRouteLook:                        // Look at an object
				if (turnedFl) {
					_vm->_object->lookObject(&_vm->_object->_objects[_routeObjId]);
					turnedFl = false;
				} else {
					setDirection(_vm->_object->_objects[_routeObjId]._direction);
					_routeIndex++;                  // Come round again
					turnedFl = true;
				}
				break;
			case kRouteGet:                         // Get (or use) an object
				if (turnedFl) {
					_vm->_object->useObject(_routeObjId);
					turnedFl = false;
				} else {
					setDirection(_vm->_object->_objects[_routeObjId]._direction);
					_routeIndex++;                  // Come round again
					turnedFl = true;
				}
				break;
			default:
				break;
			}
		}
	} else if (_vm->_hero->_vx == 0 && _vm->_hero->_vy == 0) {
		// Set direction of travel if at a node
		// Note realignment when changing to (thinner) up/down sprite,
		// otherwise hero could bump into boundaries along route.
		if (herox < routeNode->x) {
			setWalk(Common::KEYCODE_RIGHT);
		} else if (herox > routeNode->x) {
			setWalk(Common::KEYCODE_LEFT);
		} else if (heroy < routeNode->y) {
			setWalk(Common::KEYCODE_DOWN);
			_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
		} else if (heroy > routeNode->y) {
			setWalk(Common::KEYCODE_UP);
			_vm->_hero->_x = _vm->_hero->_oldx = routeNode->x - _vm->_hero->_currImagePtr->_x1;
		}
	}
}

/**
 * Start a new route from hero to cx, cy
 * go_for is the purpose, id indexes the exit or object to walk to
 * Returns FALSE if route not found
 */
bool Route::startRoute(const RouteType routeType, const int16 objId, int16 cx, int16 cy) {
	debugC(1, kDebugRoute, "startRoute(%d, %d, %d, %d)", routeType, objId, cx, cy);

	// Don't attempt to walk if user does not have control
	if (_vm->_hero->_pathType != kPathUser)
		return false;

	// if inventory showing, make it go away
	if (_vm->_inventory->getInventoryState() != kInventoryOff)
		_vm->_inventory->setInventoryState(kInventoryUp);

	_routeType = routeType;                         // Purpose of trip
	_routeObjId  = objId;                           // Index of exit/object

	// Adjust destination to center hero if walking to cursor
	if (_routeType == kRouteSpace)
		cx -= kHeroMinWidth / 2;

	bool foundFl = false;                           // TRUE if route found ok
	if ((foundFl = findRoute(cx, cy))) {            // Found a route?
		_routeIndex = _routeListIndex;              // Node index
		_vm->_hero->_vx = _vm->_hero->_vy = 0;      // Stop manual motion
	}

	return foundFl;
}

} // End of namespace Hugo
