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

#ifndef ALCACHOFA_DEBUG_H
#define ALCACHOFA_DEBUG_H

#include "alcachofa/alcachofa.h"

using namespace Common;

namespace Alcachofa {

class IDebugHandler {
public:
	virtual ~IDebugHandler() {}

	virtual void update() = 0;
};

class ClosestFloorPointDebugHandler final : public IDebugHandler {
	int32 _polygonI;
public:
	ClosestFloorPointDebugHandler(int32 polygonI) : _polygonI(polygonI) {}

	void update() override {
		auto mousePos2D = g_engine->input().debugInput().mousePos2D();
		auto mousePos3D = g_engine->input().debugInput().mousePos3D();
		auto floor = g_engine->player().currentRoom()->activeFloor();
		auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
		if (floor == nullptr || renderer == nullptr)
			return;

		Point target3D;
		if (_polygonI < 0 || (uint)_polygonI >= floor->polygonCount())
			target3D = floor->closestPointTo(mousePos3D);
		else
			target3D = floor->at((uint)_polygonI).closestPointTo(mousePos3D);
		renderer->debugPolyline(mousePos2D, g_engine->camera().transform3Dto2D(target3D));
	}
};

class FloorIntersectionsDebugHandler final : public IDebugHandler {
	int32 _polygonI;
	Point _fromPos3D;
public:
	FloorIntersectionsDebugHandler(int32 polygonI) : _polygonI(polygonI) {}

	void update() override {
		auto floor = g_engine->player().currentRoom()->activeFloor();
		auto renderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
		if (floor == nullptr || renderer == nullptr) {
			g_engine->console().attach("Either the room has no floor or the renderer is not a debug renderer");
			g_engine->setDebugMode(DebugMode::None, 0);
			return;
		}

		if (g_engine->input().debugInput().wasMouseLeftPressed())
			_fromPos3D = g_engine->input().debugInput().mousePos3D();
		renderer->debugPolyline(
			g_engine->camera().transform3Dto2D(_fromPos3D),
			g_engine->input().debugInput().mousePos2D(),
			kDebugRed);

		if (_polygonI >= 0 && (uint)_polygonI < floor->polygonCount())
			drawIntersectionsFor(floor->at((uint)_polygonI), renderer);
		else {
			for (uint i = 0; i < floor->polygonCount(); i++)
				drawIntersectionsFor(floor->at(i), renderer);
		}
	}

private:
	static constexpr float kMarkerLength = 16;

	void drawIntersectionsFor(const Polygon &polygon, IDebugRenderer *renderer) {
		auto &camera = g_engine->camera();
		auto mousePos3D = g_engine->input().debugInput().mousePos3D();
		for (uint i = 0; i < polygon._points.size(); i++) {
			if (!polygon.intersectsEdge(i, _fromPos3D, mousePos3D))
				continue;
			auto a = camera.transform3Dto2D(polygon._points[i]);
			auto b = camera.transform3Dto2D(polygon._points[(i + 1) % polygon._points.size()]);
			auto mid = (a + b) / 2;
			auto length = sqrtf(a.sqrDist(b));
			auto normal = a - b;
			normal = { normal.y, (int16)-normal.x };
			auto inner = mid + normal * (kMarkerLength / length);

			renderer->debugPolyline(a, b, kDebugGreen);
			renderer->debugPolyline(mid, inner, kDebugGreen);
		}
	}
};

class TeleportCharacterDebugHandler final : public IDebugHandler {
	MainCharacterKind _kind;
public:
	TeleportCharacterDebugHandler(int32 kindI) : _kind((MainCharacterKind)kindI) {}

	void update() override {
		g_engine->drawQueue().clear();
		g_engine->player().drawCursor(true);
		g_engine->drawQueue().draw();

		auto &input = g_engine->input().debugInput();
		if (input.wasMouseRightPressed()) {
			g_engine->setDebugMode(DebugMode::None, 0);
			return;
		}

		if (!input.wasMouseLeftPressed())
			return;
		auto floor = g_engine->player().currentRoom()->activeFloor();
		if (floor == nullptr || !floor->contains(input.mousePos3D()))
			return;

		if (_kind == MainCharacterKind::Filemon)
			teleport(g_engine->world().filemon(), input.mousePos3D());
		else if (_kind == MainCharacterKind::Mortadelo)
			teleport(g_engine->world().mortadelo(), input.mousePos3D());
		else {
			teleport(g_engine->world().filemon(), input.mousePos3D());
			teleport(g_engine->world().mortadelo(), input.mousePos3D());
		}
		g_engine->setDebugMode(DebugMode::None, 0);
	}

private:
	void teleport(MainCharacter &character, Point position) {
		auto currentRoom = g_engine->player().currentRoom();
		if (character.room() != currentRoom) {
			character.resetTalking();
			character.room() = currentRoom;
		}
		character.setPosition(position);
	}
};

class FloorColorDebugHandler final : public IDebugHandler {
	const FloorColorShape &_shape;
	const bool _useColor;
	Color _curColor = kDebugGreen;
	bool _isOnFloor = false;
	static constexpr size_t kBufferSize = 64;
	char _buffer[kBufferSize] = { 0 };

	FloorColorDebugHandler(const FloorColorShape &shape, bool useColor)
		: _shape(shape)
		, _useColor(useColor) {}
public:
	static FloorColorDebugHandler *create(int32 objectI, bool useColor) {
		const Room *room = g_engine->player().currentRoom();
		uint floorCount = 0;
		for (auto itObject = room->beginObjects(); itObject != room->endObjects(); ++itObject) {
			FloorColor *floor = dynamic_cast<FloorColor *>(*itObject);
			if (floor == nullptr)
				continue;
			if (objectI <= 0)
				// dynamic_cast is not possible due to Shape not having virtual methods
				return new FloorColorDebugHandler(*(FloorColorShape *)(floor->shape()), useColor);
			floorCount++;
			objectI--;
		}

		g_engine->console().debugPrintf("Invalid floor color index, there are %u floors in this room\n", floorCount);
		return nullptr;
	}

	void update() override {
		auto &input = g_engine->input().debugInput();
		if (input.wasMouseRightPressed()) {
			g_engine->setDebugMode(DebugMode::None, 0);
			return;
		}

		if (input.isMouseLeftDown()) {
			auto optColor = _shape.colorAt(input.mousePos3D());
			_isOnFloor = optColor.first;
			if (!_isOnFloor) {
				uint8 roomAlpha = (uint)(g_engine->player().currentRoom()->characterAlphaTint() * 255 / 100);
				optColor.second = Color { 255, 255, 255, roomAlpha };
			}

			_curColor = _useColor
				? Color { optColor.second.r, optColor.second.g, optColor.second.b, 255 }
			: Color { optColor.second.a, optColor.second.a, optColor.second.a, 255 };
			g_engine->world().mortadelo().color() =
				g_engine->world().filemon().color() =
				_useColor ? optColor.second : Color { 255, 255, 255, optColor.second.a };
		}

		snprintf(_buffer, kBufferSize, "r:%3d g:%3d b:%3d a:%3d",
			(int)_curColor.r, (int)_curColor.g, (int)_curColor.b, (int)_curColor.a);

		auto *debugRenderer = dynamic_cast<IDebugRenderer *>(&g_engine->renderer());
		g_engine->drawQueue().clear();
		g_engine->player().drawCursor(true);
		g_engine->renderer().setTexture(nullptr);
		g_engine->renderer().quad({ 0, 0 }, { 50, 50 }, _isOnFloor ? _curColor : kDebugGreen);
		g_engine->drawQueue().add<TextDrawRequest>(
			g_engine->globalUI().dialogFont(), _buffer, Point { 70, 20 }, 500, false, kWhite, -kForegroundOrderCount + 1);
		if (!_isOnFloor)
			g_engine->renderer().quad({ 5, 5 }, { 40, 40 }, _curColor);
		if (debugRenderer != nullptr)
			debugRenderer->debugShape(_shape, kDebugBlue);
		g_engine->drawQueue().draw();
	}
};

}

#endif // ALCACHOFA_DEBUG_H
