#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include <efsw/FileSystem.hpp>
#include <efsw/System.hpp>
#include <efsw/efsw.hpp>
#include <iostream>
#include <signal.h>
#include <cstring>
#include "stb_image.h"
#include "stb_image_write.h"

#include "bella_sdk/bella_engine.h"  // Core rendering engine
#include "bella_sdk/bella_scene.h"   // Scene management
#include "dl_core/dl_logging.h"      // Logging utilities
#include <thread>      // For std::this_thread (C++ standard library)
#include <chrono>      // For timing operations (C++ standard library)
#include <cstdio>  // For freopen
#include <regex>


std::string directory_path = ".";
bool STOP = false;
dl::bella_sdk::Engine engine;
std::string fileToRender = "";
bool pendingRender = false;
int width = 800;
int height = 600;

//void flip_image_horizontally(unsigned char* data, int width, int height, int channels);
void flip_image_vertically(unsigned char* data, int width, int height, int channels);


void sigend( int ) {
	std::cout << std::endl << "close file monitoring" << std::endl;
	STOP = true;
}


/*
 * MyEngineObserver Class
 * This class receives callbacks from the Bella rendering engine to track rendering progress.
 * It implements the EngineObserver interface and provides methods to:
 * - Handle render start/stop events
 * - Track rendering progress
 * - Handle error conditions
 * - Store and retrieve the current progress state
 */
struct MyEngineObserver : public dl::bella_sdk::EngineObserver
{
public:
	// Called when a rendering pass starts
	void onStarted(dl::String pass) override
	{
		//std::cout << "Started pass " << pass.buf() << std::endl;
	   // dl::logInfo("Started pass %s", pass.buf());
	}

	// Called to update the current status of rendering
	//void onStatus(String pass, String status) override
	//{
	//    logInfo("%s [%s]", status.buf(), pass.buf());
	//}

	// Called to update rendering progress (percentage, time remaining, etc)
	void onProgress(dl::String pass, dl::bella_sdk::Progress progress) override
	{
		//std::cout << progress.toString().buf() << std::endl;
		setString(new std::string(progress.toString().buf()));
		//dl::logInfo("%s [%s]", progress.toString().buf(), pass.buf());
	}

	//void onImage(String pass, Image image) override
	//{
	//    logInfo("We got an image %d x %d.", (int)image.width(), (int)image.height());
	//}  

	// Called when an error occurs during rendering
	void onError(dl::String pass, dl::String msg) override
	{
		//dl::logError("%s [%s]", msg.buf(), pass.buf());
	}

	// Called when a rendering pass completes
	void onStopped(dl::String pass) override
	{
		// dl::logInfo("Stopped %s", pass.buf());
		std::atomic_bool active_render = false;
	}

	// Returns the current progress as a string
	std::string getProgress() const {
		std::string* currentProgress = progressPtr.load();
		if (currentProgress) {
			return *currentProgress;
		}
		else {
			return "";
		}
	}

	// Cleanup resources in destructor
	~MyEngineObserver() {
		setString(nullptr);
	}
private:
	// Thread-safe pointer to current progress string
	std::atomic<std::string*> progressPtr{ nullptr };

	// Helper function to safely update the progress string
	void setString(std::string* newStatus) {
		std::string* oldStatus = progressPtr.exchange(newStatus);
		delete oldStatus;  // Clean up old string if it exists
	}
};

MyEngineObserver engineObserver;

//void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) {
//	// Construct the full path once
//	dl::String fullPath = dl::String(directory_path.c_str()) + "/" + dl::String(selected_name.c_str());
//
//	// Load and then Read using the SAME full path
//	engine.loadScene(fullPath);
//	if (!engine.scene().read(fullPath)) {
//		std::cout << "Failed to read scene: " << selected_name << std::endl;
//		return;
//	}
//
//	//dl::String belPath = dl::String(directory_path.c_str()) + "\\";
//	//engine.loadScene(belPath + dl::String(selected_name.c_str()));
//
//	//if (!engine.scene().read(dl::String(selected_name.c_str()))) return;
//
//	dl::bella_sdk::Node bPass = engine.scene().beautyPass();
//
//	// Setup the outputImagePath node as we did in the synchronous version
//	dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
//	pathNode["dir"] = dl::String(directory_path.c_str());
//	pathNode["name"] = "render_result";
//	pathNode["ext"] = ".png";
//	// In render_thread
//	pathNode["dir"] = dl::String("../renders/");
//	bPass["overridePath"] = pathNode;
//	bPass["timeLimit"] = 30.0f;
//
//	engine.start();
//
//	// While rendering, sleep and then tell the UI to refresh
//	while (engine.rendering()) {
//		std::this_thread::sleep_for(std::chrono::milliseconds(500));
//		// This is the key to seeing progress while it's "asynchronous"		
//		//screen.PostEvent(ftxui::Event::Custom);
//		//std::cout << "rendering...";
//	}
//	std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Finalize save
//}

void do_sync_render(std::string fileToRender)
{
	// This removes any trailing slashes from directory_path to prevent the \/ issue
	if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) {
		directory_path.pop_back();
	}
	// 1. Force the path to look in the 'test' folder specifically
	std::string fullPathStr = directory_path + "/" + fileToRender;
	dl::String fullPath(fullPathStr.c_str());

	std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl;

	engine.loadScene(fullPath);
	if (!engine.scene().read(fullPath)) {
		std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl;
		return;
	}

	// 2. Setup the output path using a relative path that goes OUTSIDE 'test'	
	dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
	pathNode["dir"] = dl::String(directory_path.c_str());
	//pathNode["dir"] = dl::String(".");
	pathNode["name"] = dl::String("render_result");
	pathNode["ext"] = dl::String(".png");

	// Get the global settings node instead of the beautyPass node
	dl::bella_sdk::Node settings = engine.scene().settings();
	dl::bella_sdk::Node cam = settings["camera"].asNode();
	auto inputs = settings.inputs(); // This gets all valid attribute names
	for (const auto& input : inputs) {
		std::cout << "Valid attribute: " << input.name() << std::endl;
	}

	if (cam) {
		auto inputs = cam.inputs();
		for (const auto& input : inputs) {
			// This will print every valid attribute name for YOUR camera
			std::cout << "Camera Attribute: " << input.name() << std::endl;
		}

		if (cam) {			
			std::cout << ">>> Set resolution to " << width <<"x" << height << " on camera : " << cam.name() << std::endl;
			float fWidth = static_cast<float>(width);
			float fHeight = static_cast<float>(height);
            cam["resolution"] = dl::Vec2{ fWidth, fHeight };
		}		
	}
	else {
		std::cout << ">>> ERROR: No camera found in settings!" << std::endl;
	}

	dl::bella_sdk::Node bPass = engine.scene().beautyPass();


	bPass["overridePath"] = pathNode;

	std::cout << ">>> STARTING BELLA ENGINE..." << std::endl;
	engine.start();

	// 3. Monitor progress
	while (engine.rendering()) {
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
		std::cout << "rendering..." << std::endl;
	}

	std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write
	std::cout << ">>> RENDER FINISHED. Saved in current directory" << std::endl;
}

void render_thread(dl::bella_sdk::Engine& engine, MyEngineObserver& engineObserver, std::string selected_name) {
	// This removes any trailing slashes from directory_path to prevent the \/ issue
	if (!directory_path.empty() && (directory_path.back() == '/' || directory_path.back() == '\\')) {
		directory_path.pop_back();
	}
	// 1. Force the path to look in the 'test' folder specifically
	std::string fullPathStr = directory_path + "/" + selected_name;
	dl::String fullPath(fullPathStr.c_str());

	std::cout << ">>> ATTEMPTING TO READ: " << fullPathStr << std::endl;

	engine.loadScene(fullPath);
	if (!engine.scene().read(fullPath)) {
		std::cout << ">>> ERROR: Bella could not read scene file!" << std::endl;
		return;
	}

	// 2. Setup the output path using a relative path that goes OUTSIDE 'test'
	// This assumes your 'renders' folder is in x64/release/renders
	dl::bella_sdk::Node pathNode = engine.scene().createNode("outputImagePath");
	pathNode["dir"] = dl::String("../renders/");
	pathNode["name"] = dl::String("render_result");
	pathNode["ext"] = dl::String(".png");

	dl::bella_sdk::Node bPass = engine.scene().beautyPass();
	bPass["overridePath"] = pathNode;

	std::cout << ">>> STARTING BELLA ENGINE..." << std::endl;
	engine.start();

	// 3. Monitor progress
	while (engine.rendering()) {
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
		std::cout << "rendering..." << std::endl;
	}

	std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // Give Windows time to finish the file write
	std::cout << ">>> RENDER FINISHED. Check x64/release/renders/" << std::endl;
}


/// Processes a file action
class UpdateListener : public efsw::FileWatchListener {
  public:
	UpdateListener() {}

	std::string getActionName( efsw::Action action ) {
		switch ( action ) {
			case efsw::Actions::Add:
				
				return "Add";
			case efsw::Actions::Modified:
				return "Modified";
			case efsw::Actions::Delete:
				return "Delete";
			case efsw::Actions::Moved:
				return "Moved";
			default:
				return "Bad Action";
		}
	}

	void handleFileAction( efsw::WatchID watchid, const std::string& dir,
						   const std::string& filename, efsw::Action action,
						   std::string oldFilename = "" ) override {
		std::cout << "Watch ID " << watchid << " DIR ("
				  << dir + ") FILE (" +
						 ( oldFilename.empty() ? "" : "from file " + oldFilename + " to " ) +
						 filename + ") has event "
				  << getActionName( action ) << std::endl;
		
		if (action == efsw::Actions::Add || action == efsw::Actions::Modified) {				
			directory_path = dir;
			std::string fullPath = dir + filename;
			int width, height, channels;
			if (filename.substr(filename.length() - 3) == "bsz" ) {
				std::cout << "Converting " << filename << " to a PNG" << std::endl;
				// 2. Start the thread and DETACH it
				   // Note: We pass 'screen' as a reference so render_thread can call PostEvent
				fileToRender = filename;
				pendingRender = true;
				//std::thread(render_thread, std::ref(engine), std::ref(engineObserver), filename).detach();
			}			
		}
	}
};

efsw::WatchID handleWatchID( efsw::WatchID watchid ) {
	switch ( watchid ) {
		case efsw::Errors::FileNotFound:
		case efsw::Errors::FileRepeated:
		case efsw::Errors::FileOutOfScope:
		case efsw::Errors::FileRemote:
		case efsw::Errors::WatcherFailed:
		case efsw::Errors::Unspecified: {
			std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl;
			break;
		}
		default: {
			std::cout << "Added WatchID: " << watchid << std::endl;
		}
	}

	return watchid;
}

void flip_image_vertically(unsigned char* data, int width, int height, int channels) {
	// ... (your vertical flip implementation from before)
	int row_stride = width * channels;
	unsigned char* temp_row = new unsigned char [row_stride];
	for (int y = 0; y < height / 2; ++y) {
		unsigned char* top_row = data + y * row_stride;
		unsigned char* bottom_row = data + (height - 1 - y) * row_stride;
		std::memcpy(temp_row, top_row, row_stride);
		std::memcpy(top_row, bottom_row, row_stride);
		std::memcpy(bottom_row, temp_row, row_stride);
	}
	delete[] temp_row;
}

//int main( int argc, char** argv ) {
#include "dl_core/dl_main.inl"
#include "dl_core/dl_args.h"
int DL_main(dl::Args& args) {
	signal( SIGABRT, sigend );
	signal( SIGINT, sigend );
	signal( SIGTERM, sigend );
	// 1. Initialize engine and observer locally within the logic block
	
	engine.subscribe(&engineObserver);

	std::cout << "Press ^C to exit demo" << std::endl;

	bool commonTest = true;
	bool useGeneric = false;
	std::string path;

	args.add("wd", "watchdir", "", "mode file watch");
	args.add("w", "width", "800", "render width");
	args.add("h", "height", "600", "render height");

	/*if (argc >= 2) {
		path = std::string( argv[1] );

		if ( efsw::FileSystem::isDirectory( path ) ) {
			commonTest = false;
		}

		if ( argc >= 3 ) {
			if ( std::string( argv[2] ) == "true" ) {
				useGeneric = true;
			}
		}
	}*/
	//std::cout << "args: " << args. << std::endl;
	if (args.have("--watchdir")) {
		path = args.value("--watchdir").buf();
		std::cout << "Watching path: " << path << std::endl;
		if (efsw::FileSystem::isDirectory(path)) {
			commonTest = false;
			useGeneric = true; // Force generic for testing
		}
	}

	if (args.have("--width")) {
		width = std::stoi(args.value("--width").buf());
	}
	if (args.have("--height")) {
		height = std::stoi(args.value("--height").buf());
	}

	UpdateListener* ul = new UpdateListener();

	/// create the file watcher object
	efsw::FileWatcher fileWatcher( useGeneric );

	fileWatcher.followSymlinks( false );
	fileWatcher.allowOutOfScopeLinks( false );

	//std::cout << "commonTest: " << commonTest << std::endl;
	if ( commonTest ) {
		std::string CurPath( efsw::System::getProcessPath() );

		std::cout << "CurPath: " << CurPath.c_str() << std::endl;

		/// starts watching
		fileWatcher.watch();

		/// add a watch to the system
		handleWatchID( fileWatcher.addWatch( CurPath , ul, true ) );
		
	} else {
		if ( fileWatcher.addWatch( path, ul, true ) > 0 ) {
			fileWatcher.watch();
			/// add a watch to the system
			handleWatchID(fileWatcher.addWatch(path, ul, true));
			std::cout << "Watching directory: " << path.c_str() << std::endl;
			directory_path = path;
			if ( useGeneric ) {
				std::cout << "Using generic backend watcher" << std::endl;
			}
		} else {
			std::cout << "Error trying to watch directory: " << path.c_str() << std::endl;
			std::cout << efsw::Errors::Log::getLastErrorLog().c_str() << std::endl;
		}
	}

	while ( !STOP ) {
		if (pendingRender) {
			// We are now in the main thread. 
			// 1. Give the OS a moment to release file locks
			std::this_thread::sleep_for(std::chrono::milliseconds(500));

			// 2. Call render logic (not in a thread)
			// Note: You'll need to move render_thread logic into a regular function
			do_sync_render(fileToRender);

			pendingRender = false;
		}
		efsw::System::sleep( 100 );
	}

	return 0;
}

