/* 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.
 *
 */

#include "bladerunner/debugger.h"

#include "bladerunner/actor.h"
#include "bladerunner/bladerunner.h"
#include "bladerunner/boundingbox.h"
#include "bladerunner/combat.h"
#include "bladerunner/font.h"
#include "bladerunner/game_constants.h"
#include "bladerunner/game_flags.h"
#include "bladerunner/game_info.h"
#include "bladerunner/light.h"
#include "bladerunner/lights.h"
#include "bladerunner/regions.h"
#include "bladerunner/scene.h"
#include "bladerunner/scene_objects.h"
#include "bladerunner/settings.h"
#include "bladerunner/set.h"
#include "bladerunner/text_resource.h"
#include "bladerunner/vector.h"
#include "bladerunner/view.h"
#include "bladerunner/vqa_decoder.h"
#include "bladerunner/vqa_player.h"
#include "bladerunner/waypoints.h"
#include "bladerunner/zbuffer.h"

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

#include "graphics/surface.h"

namespace BladeRunner {

Debugger::Debugger(BladeRunnerEngine *vm) : GUI::Debugger() {
	_vm = vm;

	_viewSceneObjects = false;
	_viewActorsOnly = false;
	_viewObstacles = false;
	_viewUI = false;
	_viewZBuffer = false;

	registerCmd("anim", WRAP_METHOD(Debugger, cmdAnimation));
	registerCmd("chapter", WRAP_METHOD(Debugger, cmdChapter));
	registerCmd("draw", WRAP_METHOD(Debugger, cmdDraw));
	registerCmd("flag", WRAP_METHOD(Debugger, cmdFlag));
	registerCmd("goal", WRAP_METHOD(Debugger, cmdGoal));
	registerCmd("loop", WRAP_METHOD(Debugger, cmdLoop));
	registerCmd("pos", WRAP_METHOD(Debugger, cmdPosition));
	registerCmd("say", WRAP_METHOD(Debugger, cmdSay));
	registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
	registerCmd("var", WRAP_METHOD(Debugger, cmdVariable));
}

Debugger::~Debugger() {
}

bool Debugger::cmdAnimation(int argc, const char **argv) {
	if (argc != 2 && argc != 3) {
		debugPrintf("Get or set animation mode of the actor.\n");
		debugPrintf("Usage: %s <actorId> [<animationMode>]\n", argv[0]);
		return true;
	}

	int actorId = atoi(argv[1]);

	Actor *actor = nullptr;
	if (actorId >= 0 && actorId < (int)_vm->_gameInfo->getActorCount()) {
		actor = _vm->_actors[actorId];
	}

	if (actor == nullptr) {
		debugPrintf("Unknown actor %i\n", actorId);
		return true;
	}

	if (argc == 3) {
		int animationMode = atoi(argv[2]);
		debugPrintf("actorAnimationMode(%i) = %i\n", actorId, animationMode);
		actor->changeAnimationMode(animationMode);
		return false;
	}

	debugPrintf("actorAnimationMode(%i) = %i\n", actorId, actor->getAnimationMode());
	return true;
}

bool Debugger::cmdDraw(int argc, const char **argv) {
	if (argc != 2) {
		debugPrintf("Enables debug rendering of scene objects, obstacles, ui elements, zbuffer or disables debug rendering.\n");
		debugPrintf("Usage: %s (obj | actors | obstacles | ui | zbuf | reset)\n", argv[0]);
		return true;
	}

	Common::String arg = argv[1];
	if (arg == "obj") {
		_viewSceneObjects = !_viewSceneObjects;
		debugPrintf("Drawing scene objects = %i\n", _viewSceneObjects);
	} else if (arg == "actors") {
		_viewSceneObjects = !_viewSceneObjects;
		_viewActorsOnly = _viewSceneObjects;
		debugPrintf("Drawing scene actors = %i\n", _viewSceneObjects);
	} else if (arg == "obstacles") {
		_viewObstacles = !_viewObstacles;
		debugPrintf("Drawing obstacles = %i\n", _viewObstacles);
	} else if (arg == "ui") {
		_viewUI = !_viewUI;
		debugPrintf("Drawing UI elements = %i\n", _viewUI);
	} else if (arg == "zbuf") {
		_viewZBuffer = !_viewZBuffer;
		debugPrintf("Drawing Z buffer = %i\n", _viewZBuffer);
	} else if (arg == "reset") {
		_viewSceneObjects = false;
		_viewUI = false;
		_viewZBuffer = false;
		debugPrintf("Drawing scene objects = %i\n", _viewSceneObjects);
		debugPrintf("Drawing UI elements = %i\n", _viewUI);
		debugPrintf("Drawing Z buffer = %i\n", _viewZBuffer);
	}

	return true;
}

bool Debugger::cmdChapter(int argc, const char **argv) {
	if (argc != 2) {
		debugPrintf("Changes chapter of the game without changing scene.\n");
		debugPrintf("Usage: %s <chapter>\n", argv[0]);
		return true;
	}

	int chapter = atoi(argv[1]);
	if (chapter >= 1 && chapter <= 5) {
		_vm->_settings->setChapter(chapter);
	} else {
		debugPrintf("Chapter must be between 1 and 5\n");
	}

	return true;
}

bool Debugger::cmdFlag(int argc, const char **argv) {
	if (argc != 2 && argc != 3) {
		debugPrintf("Get or set game flag (boolean value).\n");
		debugPrintf("Usage: %s <id> [<value>]\n", argv[0]);
		return true;
	}

	int flag = atoi(argv[1]);
	int flagCount = _vm->_gameInfo->getFlagCount();
	if (flag > 0 && flag < flagCount) {
		if (argc == 3) {
			int value = atoi(argv[2]);
			if (value == 0) {
				_vm->_gameFlags->reset(flag);
			} else {
				_vm->_gameFlags->set(flag);
			}
		}
		debugPrintf("flag(%i) = %i\n", flag, _vm->_gameFlags->query(flag));
	} else {
		debugPrintf("Flag id must be between 0 and %i\n", flagCount - 1);
	}

	return true;
}

bool Debugger::cmdGoal(int argc, const char **argv) {
	if (argc != 2 && argc != 3) {
		debugPrintf("Get or set goal of the actor.\n");
		debugPrintf("Usage: %s <actorId> [<goal>]\n", argv[0]);
		return true;
	}

	int actorId = atoi(argv[1]);

	Actor *actor = nullptr;
	if (actorId >= 0 && actorId < (int)_vm->_gameInfo->getActorCount()) {
		actor = _vm->_actors[actorId];
	}

	if (actor == nullptr) {
		debugPrintf("Unknown actor %i\n", actorId);
		return true;
	}

	if (argc == 3) {
		int goal = atoi(argv[2]);
		debugPrintf("actorGoal(%i) = %i\n", actorId, goal);
		actor->setGoal(goal);
		return false;
	}

	debugPrintf("actorGoal(%i) = %i\n", actorId, actor->getGoal());
	return true;
}

bool Debugger::cmdLoop(int argc, const char **argv) {
	if (argc != 1 && argc != 2) {
		debugPrintf("Show scene loops or play scene loop.\n");
		debugPrintf("Usage: %s [<loopId>]\n", argv[0]);
		return true;
	}

	VQADecoder::LoopInfo &loopInfo = _vm->_scene->_vqaPlayer->_decoder._loopInfo;
	if (argc == 1) {
		debugPrintf("id start  end name\n");
		for (int i = 0; i < loopInfo.loopCount; ++i) {
			debugPrintf("%2d  %4d %4d %s\n", i, loopInfo.loops[i].begin, loopInfo.loops[i].end, loopInfo.loops[i].name.c_str());
		}
		return true;
	}

	int loopId = atoi(argv[1]);
	if (loopId >= 0 && loopId < loopInfo.loopCount) {
		_vm->_scene->loopStartSpecial(kSceneLoopModeOnce, loopId, false);
		return false;
	} else {
		debugPrintf("Unknown loop %i\n", loopId);
		return true;
	}
}

bool Debugger::cmdPosition(int argc, const char **argv) {
	if (argc != 2 && argc != 3 && argc != 7) {
		debugPrintf("Get or set position of the actor.\n");
		debugPrintf("Usage: %s <actorId> [(<setId> <x> <y> <z> <facing>)|<otherActorId>]\n", argv[0]);
		return true;
	}

	int actorId = atoi(argv[1]);

	Actor *actor = nullptr;
	if (actorId >= 0 && actorId < (int)_vm->_gameInfo->getActorCount()) {
		actor = _vm->_actors[actorId];
	}

	if (actor == nullptr) {
		debugPrintf("Unknown actor %i\n", actorId);
		return true;
	}

	if (argc == 2) {
		debugPrintf("actorSet(%i) = %i\n", actorId, actor->getSetId());
		debugPrintf("actorX(%i) = %f\n", actorId, actor->getX());
		debugPrintf("actorY(%i) = %f\n", actorId, actor->getY());
		debugPrintf("actorZ(%i) = %f\n", actorId, actor->getZ());
		debugPrintf("actorFacing(%i) = %i\n", actorId, actor->getFacing());
	}

	if (argc == 3) {
		int otherActorId = atoi(argv[2]);
		Actor *otherActor = nullptr;
		if (otherActorId >= 0 && otherActorId < (int)_vm->_gameInfo->getActorCount()) {
			otherActor = _vm->_actors[otherActorId];
		}

		if (otherActor == nullptr) {
			debugPrintf("Unknown actor %i\n", otherActorId);
			return true;
		}

		Vector3 position = otherActor->getXYZ();
		actor->setSetId(otherActor->getSetId());
		actor->setAtXYZ(position, otherActor->getFacing());
	}

	if (argc == 7) {
		int setId = atoi(argv[2]);
		Vector3 position(atof(argv[3]), atof(argv[4]), atof(argv[5]));
		int facing = atoi(argv[6]);

		actor->setSetId(setId);
		actor->setAtXYZ(position, facing);
	}
	return true;
}

bool Debugger::cmdSay(int argc, const char **argv) {
	if (argc != 3) {
		debugPrintf("Actor will say specified line.\n");
		debugPrintf("Usage: %s <actorId> <sentenceId>\n", argv[0]);
		return true;
	}

	int actorId = atoi(argv[1]);
	int sentenceId = atoi(argv[2]);

	Actor *actor = nullptr;
	if (actorId >= 0 && actorId < (int)_vm->_gameInfo->getActorCount()) {
		actor = _vm->_actors[actorId];
	}

	if (actor == nullptr) {
		debugPrintf("Unknown actor %i\n", actorId);
		return true;
	}

	actor->speechPlay(sentenceId, true);
	return false;
}

bool Debugger::cmdScene(int argc, const char **argv) {
	if (argc != 1 && argc != 3) {
		debugPrintf("Changes set and scene.\n");
		debugPrintf("Usage: %s [<setId> <sceneId>]\n", argv[0]);
		return true;
	}

	if (argc == 3) {
		int setId = atoi(argv[1]);
		int sceneId = atoi(argv[2]);
		_vm->_settings->setNewSetAndScene(setId, sceneId);
		return false;
	}

	debugPrintf("set = %i\nscene = %i\n", _vm->_scene->getSetId(), _vm->_scene->getSceneId());
	return true;
}

bool Debugger::cmdVariable(int argc, const char **argv) {
	if (argc != 2 && argc != 3) {
		debugPrintf("Get or set game variable (integer).\n");
		debugPrintf("Usage: %s <id> [<value>]\n", argv[0]);
		return true;
	}

	int variable = atoi(argv[1]);
	int variableCount = _vm->_gameInfo->getGlobalVarCount();
	if (variable >= 0 && variable < variableCount) {
		if (argc == 3) {
			_vm->_gameVars[variable] = atoi(argv[2]);
		}
		debugPrintf("variable(%i) = %i\n", variable, _vm->_gameVars[variable]);
	} else {
		debugPrintf("Variable id must be between 0 and %i\n", variableCount - 1);
	}
	return true;
}

void Debugger::drawBBox(Vector3 start, Vector3 end, View *view, Graphics::Surface *surface, int color) {
	Vector3 bfl = view->calculateScreenPosition(Vector3(start.x, start.y, start.z));
	Vector3 bfr = view->calculateScreenPosition(Vector3(start.x, end.y, start.z));
	Vector3 bbr = view->calculateScreenPosition(Vector3(end.x, end.y, start.z));
	Vector3 bbl = view->calculateScreenPosition(Vector3(end.x, start.y, start.z));

	Vector3 tfl = view->calculateScreenPosition(Vector3(start.x, start.y, end.z));
	Vector3 tfr = view->calculateScreenPosition(Vector3(start.x, end.y, end.z));
	Vector3 tbr = view->calculateScreenPosition(Vector3(end.x, end.y, end.z));
	Vector3 tbl = view->calculateScreenPosition(Vector3(end.x, start.y, end.z));

	surface->drawLine(bfl.x, bfl.y, bfr.x, bfr.y, color);
	surface->drawLine(bfr.x, bfr.y, bbr.x, bbr.y, color);
	surface->drawLine(bbr.x, bbr.y, bbl.x, bbl.y, color);
	surface->drawLine(bbl.x, bbl.y, bfl.x, bfl.y, color);

	surface->drawLine(tfl.x, tfl.y, tfr.x, tfr.y, color);
	surface->drawLine(tfr.x, tfr.y, tbr.x, tbr.y, color);
	surface->drawLine(tbr.x, tbr.y, tbl.x, tbl.y, color);
	surface->drawLine(tbl.x, tbl.y, tfl.x, tfl.y, color);

	surface->drawLine(bfl.x, bfl.y, tfl.x, tfl.y, color);
	surface->drawLine(bfr.x, bfr.y, tfr.x, tfr.y, color);
	surface->drawLine(bbr.x, bbr.y, tbr.x, tbr.y, color);
	surface->drawLine(bbl.x, bbl.y, tbl.x, tbl.y, color);
}

void Debugger::drawSceneObjects() {
	//draw scene objects
	int count = _vm->_sceneObjects->_count;
	if (count > 0) {
		for (int i = 0; i < count; i++) {
			SceneObjects::SceneObject *sceneObject = &_vm->_sceneObjects->_sceneObjects[_vm->_sceneObjects->_sceneObjectsSortedByDistance[i]];

			const BoundingBox &bbox = sceneObject->boundingBox;
			Vector3 a, b;
			bbox.getXYZ(&a.x, &a.y, &a.z, &b.x, &b.y, &b.z);
			Vector3 pos = _vm->_view->calculateScreenPosition(0.5 * (a + b));
			int color;

			if (_viewActorsOnly && sceneObject->type != kSceneObjectTypeActor)
				continue;

			switch (sceneObject->type) {
			case kSceneObjectTypeUnknown:
				break;
			case kSceneObjectTypeActor:
				color = 0x7C00; // 11111 00000 00000;
				drawBBox(a, b, _vm->_view, &_vm->_surfaceFront, color);
				_vm->_surfaceFront.frameRect(sceneObject->screenRectangle, color);
				_vm->_mainFont->drawColor(_vm->_textActorNames->getText(sceneObject->id - kSceneObjectOffsetActors), _vm->_surfaceFront, pos.x, pos.y, color);
				break;
			case kSceneObjectTypeItem:
				color = 0x03E0; // 00000 11111 00000
				char itemText[40];
				drawBBox(a, b, _vm->_view, &_vm->_surfaceFront, color);
				sprintf(itemText, "item %i", sceneObject->id - kSceneObjectOffsetItems);
				_vm->_surfaceFront.frameRect(sceneObject->screenRectangle, color);
				_vm->_mainFont->drawColor(itemText, _vm->_surfaceFront, pos.x, pos.y, color);
				break;
			case kSceneObjectTypeObject:
				color = 0x3DEF; //01111 01111 01111;
				//if (sceneObject->_isObstacle)
				//	color += 0b100000000000000;
				if (sceneObject->isClickable) {
					color = 0x03E0; // 00000 11111 00000;
				}
				drawBBox(a, b, _vm->_view, &_vm->_surfaceFront, color);
				_vm->_surfaceFront.frameRect(sceneObject->screenRectangle, color);
				_vm->_mainFont->drawColor(_vm->_scene->objectGetName(sceneObject->id - kSceneObjectOffsetObjects), _vm->_surfaceFront, pos.x, pos.y, color);
				break;
			}
		}
	}

	if (_viewActorsOnly)
		return;

	//draw regions
	for (int i = 0; i < 10; i++) {
		Regions::Region *region = &_vm->_scene->_regions->_regions[i];
		if (!region->present) continue;
		_vm->_surfaceFront.frameRect(region->rectangle, 0x001F); // 00000 00000 11111
	}

	for (int i = 0; i < 10; i++) {
		Regions::Region *region = &_vm->_scene->_exits->_regions[i];
		if (!region->present) continue;
		_vm->_surfaceFront.frameRect(region->rectangle, 0x7FFF); // 11111 11111 11111
	}

	//draw walkboxes
	for (int i = 0; i < _vm->_scene->_set->_walkboxCount; i++) {
		Set::Walkbox *walkbox = &_vm->_scene->_set->_walkboxes[i];

		for (int j = 0; j < walkbox->vertexCount; j++) {
			Vector3 start = _vm->_view->calculateScreenPosition(walkbox->vertices[j]);
			Vector3 end = _vm->_view->calculateScreenPosition(walkbox->vertices[(j + 1) % walkbox->vertexCount]);
			_vm->_surfaceFront.drawLine(start.x, start.y, end.x, end.y, 0x7FE0); // 11111 11111 00000
			Vector3 pos = _vm->_view->calculateScreenPosition(0.5 * (start + end));
			_vm->_mainFont->drawColor(walkbox->name, _vm->_surfaceFront, pos.x, pos.y, 0x7FE0); // 11111 11111 00000
		}
	}

	// draw lights
	for (int i = 0; i < (int)_vm->_lights->_lights.size(); i++) {
		Light *light = _vm->_lights->_lights[i];
		Matrix4x3 m = light->_matrix;
		m = invertMatrix(m);
		//todo do this properly
		Vector3 posOrigin = m * Vector3(0.0f, 0.0f, 0.0f);
		float t = posOrigin.y;
		posOrigin.y = posOrigin.z;
		posOrigin.z = -t;

		Vector3 posTarget = m * Vector3(0.0f, 0.0f, -100.0f);
		t = posTarget.y;
		posTarget.y = posTarget.z;
		posTarget.z = -t;

		Vector3 size = Vector3(5.0f, 5.0f, 5.0f);
		int colorR = (light->_color.r * 31.0f);
		int colorG = (light->_color.g * 31.0f);
		int colorB = (light->_color.b * 31.0f);
		int color = (colorR << 10) + (colorG << 5) + colorB;

		drawBBox(posOrigin - size, posOrigin + size, _vm->_view, &_vm->_surfaceFront, color);

		Vector3 posOriginT = _vm->_view->calculateScreenPosition(posOrigin);
		Vector3 posTargetT = _vm->_view->calculateScreenPosition(posTarget);
		_vm->_surfaceFront.drawLine(posOriginT.x, posOriginT.y, posTargetT.x, posTargetT.y, color);
		_vm->_mainFont->drawColor(light->_name, _vm->_surfaceFront, posOriginT.x, posOriginT.y, color);
	}

	//draw waypoints
	for (int i = 0; i < _vm->_waypoints->_count; i++) {
		Waypoints::Waypoint *waypoint = &_vm->_waypoints->_waypoints[i];
		if(waypoint->setId != _vm->_scene->getSetId()) {
			continue;
		}
		Vector3 pos = waypoint->position;
		Vector3 size = Vector3(3.0f, 3.0f, 3.0f);
		int color = 0x7FFF; // 11111 11111 11111
		drawBBox(pos - size, pos + size, _vm->_view, &_vm->_surfaceFront, color);
		Vector3 spos = _vm->_view->calculateScreenPosition(pos);
		char waypointText[40];
		sprintf(waypointText, "waypoint %i", i);
		_vm->_mainFont->drawColor(waypointText, _vm->_surfaceFront, spos.x, spos.y, color);
	}

	//draw combat cover waypoints
	for (int i = 0; i < (int)_vm->_combat->_coverWaypoints.size(); i++) {
		Combat::CoverWaypoint *cover = &_vm->_combat->_coverWaypoints[i];
		if (cover->setId != _vm->_scene->getSetId()) {
			continue;
		}
		Vector3 pos = cover->position;
		Vector3 size = Vector3(3.0f, 3.0f, 3.0f);
		int color = 0x7C1F; // 11111 00000 11111
		drawBBox(pos - size, pos + size, _vm->_view, &_vm->_surfaceFront, color);
		Vector3 spos = _vm->_view->calculateScreenPosition(pos);
		char coverText[40];
		sprintf(coverText, "cover %i", i);
		_vm->_mainFont->drawColor(coverText, _vm->_surfaceFront, spos.x, spos.y, color);
	}

	//draw combat flee waypoints
	for (int i = 0; i < (int)_vm->_combat->_fleeWaypoints.size(); i++) {
		Combat::FleeWaypoint *flee = &_vm->_combat->_fleeWaypoints[i];
		if (flee->setId != _vm->_scene->getSetId()) {
			continue;
		}
		Vector3 pos = flee->position;
		Vector3 size = Vector3(3.0f, 3.0f, 3.0f);
		int color = 0x03FF; // 00000 11111 11111
		drawBBox(pos - size, pos + size, _vm->_view, &_vm->_surfaceFront, color);
		Vector3 spos = _vm->_view->calculateScreenPosition(pos);
		char fleeText[40];
		sprintf(fleeText, "flee %i", i);
		_vm->_mainFont->drawColor(fleeText, _vm->_surfaceFront, spos.x, spos.y, color);
	}

#if 0
	//draw aesc
	for (uint i = 0; i < _screenEffects->_entries.size(); i++) {
		ScreenEffects::Entry &entry = _screenEffects->_entries[i];
		int j = 0;
		for (int y = 0; y < entry.height; y++) {
			for (int x = 0; x < entry.width; x++) {
				Common::Rect r((entry.x + x) * 2, (entry.y + y) * 2, (entry.x + x) * 2 + 2, (entry.y + y) * 2 + 2);

				int ec = entry.data[j++];
				Color256 color = entry.palette[ec];
				int bladeToScummVmConstant = 256 / 16;

				Graphics::PixelFormat _pixelFormat = createRGB555();
				int color555 = _pixelFormat.RGBToColor(
					CLIP(color.r * bladeToScummVmConstant, 0, 255),
					CLIP(color.g * bladeToScummVmConstant, 0, 255),
					CLIP(color.b * bladeToScummVmConstant, 0, 255));
				_surfaceFront.fillRect(r, color555);
			}
		}
	}
#endif
}

} // End of namespace BladeRunner