#include <iostream>
#include "EditorApp.hpp"
#include "systems/SceneSerializer.hpp"
#include "systems/TerrainTests.hpp"
#include "OgreRoot.h"

struct ExitAfterFirstFrameListener : public Ogre::FrameListener {
	Ogre::Root *root;
	bool triggered = false;
	ExitAfterFirstFrameListener(Ogre::Root *r) : root(r) {}
	bool frameRenderingQueued(const Ogre::FrameEvent &) override
	{
		if (!triggered) {
			triggered = true;
			root->queueEndRendering();
		}
		return true;
	}
};

/** Frame-driven terrain test runner.
 *  Terrain init needs the render loop active (GPU context).
 *  Runs the suite on the first frame, then exits. */
struct TerrainTestFrameListener : public Ogre::FrameListener {
	Ogre::Root *root;
	EditorApp &app;
	int iterations;
	bool triggered = false;
	int result = 0;
	TerrainTestFrameListener(Ogre::Root *r, EditorApp &a, int n)
		: root(r), app(a), iterations(n) {}
	bool frameRenderingQueued(const Ogre::FrameEvent &) override
	{
		if (triggered)
			return true;
		triggered = true;

		std::cout << "Running terrain tests ("
			  << iterations
			  << " iterations)..." << std::endl;
		result = TerrainTestRunner::run(app, iterations);
		if (result != 0)
			std::cerr << "TERRAIN TESTS FAILED" << std::endl;
		else
			std::cout << "TERRAIN TESTS: ALL "
				  << iterations
				  << " ITERATIONS PASSED" << std::endl;
		root->queueEndRendering();
		return true;
	}
};

int main(int argc, char *argv[])
{
	try {
		EditorApp app;

		// Parse command line arguments
		bool gameMode = false;
		bool debugBuoyancy = false;
		bool exitAfterFirstFrame = false;
		bool headless = false;
		int terrainTestIterations = 0;
		Ogre::String sceneFile;
		for (int i = 1; i < argc; i++) {
			Ogre::String arg = argv[i];
			if (arg == "--game") {
				gameMode = true;
			} else if (arg == "--debug-buoyancy") {
				debugBuoyancy = true;
			} else if (arg == "--exit-after-first-frame") {
				exitAfterFirstFrame = true;
			} else if (arg.find("--run-terrain-tests=") == 0) {
				Ogre::String val = arg.substr(20);
				terrainTestIterations = Ogre::StringConverter::parseInt(
					val, 1);
				if (terrainTestIterations < 1)
					terrainTestIterations = 1;
			} else if (arg == "--headless") {
				headless = true;
			} else if (arg.length() > 0 && arg[0] != '-') {
				sceneFile = arg;
			}
		}

		if (gameMode) {
			app.setGameMode(EditorApp::GameMode::Game);
		}

		if (debugBuoyancy) {
			app.setDebugBuoyancy(true);
		}

		app.setHeadless(headless);
		if (headless && terrainTestIterations > 0) {
			TerrainTestRunner::setHeadless(true);
		}

		app.initApp();

		// Use frame-driven terrain test if requested.
		// Terrain init requires an active render loop (GPU).
		TerrainTestFrameListener terrainTestListener(
			app.getRoot(), app,
			terrainTestIterations);
		if (terrainTestIterations > 0)
			app.getRoot()->addFrameListener(
				&terrainTestListener);

		// Auto-load scene if provided as argument (editor mode only)
		if (!sceneFile.empty() &&
		    app.getGameMode() == EditorApp::GameMode::Editor) {
			std::cout << "Auto-loading scene: " << sceneFile
				  << std::endl;
			SceneSerializer serializer(*app.getWorld(),
						   app.getSceneManager());
			if (!serializer.loadFromFile(sceneFile, nullptr)) {
				std::cerr << "Failed to load scene: "
					  << serializer.getLastError()
					  << std::endl;
			}
		}

		ExitAfterFirstFrameListener exitListener(app.getRoot());
		if (exitAfterFirstFrame)
			app.getRoot()->addFrameListener(&exitListener);
		app.getRoot()->startRendering();
		app.closeApp();

		/* Propagate terrain test result as process exit code so CI can
		 * detect failures. */
		if (terrainTestIterations > 0)
			return terrainTestListener.result;
	} catch (const std::exception &e) {
		std::cerr << "Error: " << e.what() << std::endl;
		return 1;
	}

	return 0;
}
