#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <filesystem>
#include <vector>

#include <unistd.h>
#include <errno.h>

#include <sys/wait.h>
#include <sys/mount.h>
#include <dlfcn.h>

#include <lindows/lrss.h>

void PiiLog(const std::string& message);
[[noreturn]] void PiiAbortBoot();

/**
 i* State for a subsystem managed by LSMSS
 */
class PiiSubsystemState {
	// -- General info --
	std::string SubsystemName; // Subsystem display name in debug logs

	bool Running = false; // Subsystem is running

	// -- Linux information --
	pid_t LnxPid = 0;              // PID
	std::filesystem::path LnxPath; // Binary path
	
	public:

	// -- Constructors --
	PiiSubsystemState(const std::string& name, const std::filesystem::path& path) {
		this->SubsystemName = name;
		this->LnxPath = path;

		return;
	}

	// -- General --
	
	/**
	 * Start the subsystem process
	 */
	void Start() {
		PiiLog("Starting subsystem " + this->SubsystemName);
		if (pid_t child = fork()) {
			// save the process id
			this->LnxPid = child;
			this->Running = true;

			while (true) {
				int status;
				if (waitpid(this->LnxPid, &status, WUNTRACED) < 0) {
					perror("waitpid");
					PiiLog("Failed to start " + this->SubsystemName);
					this->Running = false;
					return;
				}

				if (WIFEXITED(status)) {
					PiiLog("Failed to start " + this->SubsystemName + " (Exit code " + std::to_string(WEXITSTATUS(status)) + ")");
					this->Running = false;
					return;
				}

				if (WIFSTOPPED(status)) {
					if (kill(this->LnxPid, SIGCONT) < 0) {
						perror("kill");
				       		PiiLog("Failed to start " + this->SubsystemName + ". Unknown state, boot process cannot continue.");
						PiiAbortBoot();
					}
					
					PiiLog("Started subsystem " + this->SubsystemName);
					return;
				}
			}

			return;
		} else {
			char* args[] = { strdup(this->LnxPath.c_str()), nullptr };
			execv(args[0], args);

			// this should never happen
			std::exit(1);
		}
	}
};


/**
 * Log something.
 *
 * \param message to log
 */
void PiiLog(const std::string& message) {
	std::cout << message << "\n";
	return;
}

/**
 * execute a program from an absolute path
 *
 * \param path absolute path to program (duh)
 * \return program return value, 1 if it does not exist or -1 on error
 */
int PiiExecuteProgram(const std::string& path) {
	if (pid_t child = fork()) {
		int exitcode = -1;
		waitpid(child, &exitcode, 0);

		return exitcode;
	} else {
		char* args[] = { strdup(path.c_str()), nullptr };
		execv(args[0], args); 

		// this should only be reached if we cant load the program
		std::exit(1);
	}
}

/**
 * Abort the boot process and drop the user into a recovery shell
 */
[[noreturn]]
void PiiAbortBoot() {
	PiiLog("Unable to complete preinitialization. You have been dropped into a minimal recovery environment. Good luck.");
	PiiExecuteProgram("/Windows/System32/bash.exe");
	std::exit(1);
}

int main() {
	std::vector<PiiSubsystemState> subsystems {};
	
	// -- just print shit --
	PiiLog("Starting Lindows Session Manager Subsystem");
	

	// -- dynamic linking fun --
	PiiLog("Regenerating dynamic linker cache");
	PiiExecuteProgram("/Windows/System32/ldconfig.exe");

	// -- registry --
	PiiLog("Starting Lindows Registry Subsystem");
	subsystems.emplace(subsystems.end(), "Lindows Registry Subsystem", "/Windows/System32/lrss.exe")
		->Start(); // We don't actually know if this started, we aren't in the mainloop. We just have to pray :>
	
	PiiLog("Loading Lindows registry");
	auto liblrssclient = dlopen("/Windows/System32/liblrssclient.so", RTLD_NOW);
	auto pLrcOpenRegistry = reinterpret_cast<decltype(LrcOpenRegistry)*>(dlsym(liblrssclient, "LrcOpenRegistry"));
	if (auto error = dlerror()) {
		PiiLog(error);
		PiiAbortBoot();
	}

	if (pLrcOpenRegistry()) {
		PiiLog("Failed to connect to registry subsystem.");
		PiiAbortBoot();
	}

	// TODO
	
	// -- swap --
	PiiLog("Creating pagefiles");
	// TODO
	
	// -- env --
	PiiLog("Setting environment variables");
	// TODO
	
	// we are never ok
	PiiAbortBoot();
	
	return 0;
}