// fut.cpp - Fusion transpiler
//
// Copyright (C) 2011-2026  Piotr Fusik
//
// This file is part of Fusion Transpiler,
// see https://github.com/fusionlanguage/fut
//
// Fusion Transpiler 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.
//
// Fusion Transpiler 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 Fusion Transpiler.  If not, see http://www.gnu.org/licenses/

#include <cstring>
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

#include "libfut.hpp"

static std::string slurp(std::ifstream &stream)
{
	std::ostringstream oss;
	oss << stream.rdbuf();
	return oss.str();
}

class FileGenHost : public FuConsoleHost
{
	std::vector<const char *> resourceDirs;
	std::string outputDirectory;
	std::string currentFilename;
	std::ofstream currentFile;

	void readResource(std::string_view name, const FuPrefixExpr *expr, std::vector<uint8_t> &content)
	{
		std::ifstream stream;
		for (const char *dir : resourceDirs) {
			stream.open(std::filesystem::path(dir) / name, std::ios_base::binary);
			if (stream) {
				std::string input = slurp(stream);
				content.assign(input.begin(), input.end());
				return;
			}
		}
		stream.open(std::string{name}, std::ios_base::binary);
		if (stream) {
			std::string input = slurp(stream);
			content.assign(input.begin(), input.end());
		}
		else
			reportStatementError(expr, std::format("File '{}' not found", name));
	}

protected:
	std::string_view toDirectory(std::string_view path) override
	{
		if (std::filesystem::is_directory(path))
			return path;
		outputDirectory = std::filesystem::path(path).parent_path().string();
		return outputDirectory;
	}

public:
	void addResourceDir(const char *path)
	{
		resourceDirs.push_back(path);
	}

	int getResourceLength(std::string_view name, const FuPrefixExpr *expr) override
	{
		auto p = getResources()->try_emplace(std::string(name));
		std::vector<uint8_t> &content = p.first->second;
		if (p.second)
			readResource(name, expr, content);
		return content.size();
	}

	std::ostream *createFile(std::string_view directory, std::string_view filename) override
	{
		currentFilename = directory.empty() ? filename : (std::filesystem::path(directory) / filename).string();
		currentFile.open(currentFilename, std::ios_base::binary);
		return &currentFile;
	}

	void closeFile() override
	{
		currentFile.close();
		if (hasErrors())
			std::remove(currentFilename.c_str());
		else if (!currentFile) {
			std::cerr << currentFilename << ": ERROR: " << strerror(errno) << '\n';
			setErrors(true);
		}
	}
};

static bool parseAndResolve(FuParser *parser, const std::vector<const char *> &files, FuSema *sema, FuConsoleHost *host)
{
	for (const char *file : files) {
		std::ifstream stream(file);
		std::string input = slurp(stream);
		if (!stream) {
			std::cerr << file << ": ERROR: " << strerror(errno) << '\n';
			return false;
		}
		parser->parse(file, reinterpret_cast<const uint8_t *>(input.data()), input.size());
	}
	if (host->hasErrors())
		return false;
	sema->process();
	return !host->hasErrors();
}

int main(int argc, char **argv)
{
	FileGenHost host;
	FuParser parser;
	std::vector<const char *> inputFiles;
	std::vector<const char *> referencedFiles;
	const char *lang = nullptr;
	const char *outputFile = nullptr;
	const char *namespace_ = "";
	for (int i = 1; i < argc; i++) {
		const char *arg = argv[i];
		if (arg[0] != '-')
			inputFiles.push_back(arg);
		else if (strcmp(arg, "--help") == 0) {
			FuConsoleHost::usage("fut");
			return 0;
		}
		else if (strcmp(arg, "--version") == 0) {
			std::cout << "Fusion Transpiler " << FuConsoleHost::version << " (C++)\n";
			return 0;
		}
		else if (arg[1] != '\0' && arg[2] == '\0' && i + 1 < argc) {
			switch (arg[1]) {
			case 'l':
				lang = argv[++i];
				break;
			case 'o':
				outputFile = argv[++i];
				break;
			case 'n':
				namespace_ = argv[++i];
				break;
			case 'D': {
					const char *symbol = argv[++i];
					if (strcmp(symbol, "true") == 0 || strcmp(symbol, "false") == 0) {
						std::cerr << "fut: '" << symbol << "' is reserved\n";
						return 1;
					}
					parser.addPreSymbol(symbol);
				}
				break;
			case 'r':
				referencedFiles.push_back(argv[++i]);
				break;
			case 'I':
				host.addResourceDir(argv[++i]);
				break;
			default:
				std::cerr << "fut: ERROR: Unknown option: " << arg << '\n';
				return 1;
			}
		}
		else {
			std::cerr << "fut: ERROR: Unknown option: " << arg << '\n';
			return 1;
		}
	}
	if (outputFile == nullptr || inputFiles.empty()) {
		FuConsoleHost::usage("fut");
		return 1;
	}

	FuSema sema;
	parser.setHost(&host);
	sema.setHost(&host);
	std::shared_ptr<FuSystem> system = FuSystem::new_();
	FuScope *parent = system.get();
	FuProgram references;
	if (!referencedFiles.empty()) {
		references.init(parent, system.get(), &host);
		if (!parseAndResolve(&parser, referencedFiles, &sema, &host))
			return 1;
		parent = &references;
	}
	FuProgram program;
	program.init(parent, system.get(), &host);
	if (!parseAndResolve(&parser, inputFiles, &sema, &host))
		return 1;

	if (lang != nullptr) {
		host.emit(&program, lang, namespace_, outputFile);
		return host.hasErrors() ? 1 : 0;
	}
	for (ptrdiff_t i = strlen(outputFile); --i >= 0; ) {
		char c = outputFile[i];
		if (c == '.') {
			if (i >= 2
			 && (outputFile[i - 2] == '.' || outputFile[i - 2] == ',')
			 && strncmp(outputFile + i - 1, "d.ts", 4) == 0
			 && (outputFile[i + 3] == '\0' || outputFile[i + 3] == ','))
				continue;
			const char *extBegin = outputFile + i + 1;
			std::string outputBase { outputFile, extBegin };
			int exitCode = 0;
			for (;;) {
				const char *extEnd = strchr(extBegin, ',');
				if (extEnd == nullptr)
					break;
				std::string outputExt { extBegin, extEnd };
				host.emit(&program, outputExt, namespace_, outputBase + outputExt);
				if (host.hasErrors()) {
					host.setErrors(false);
					exitCode = 1;
				}
				extBegin = extEnd + 1;
			}
			host.emit(&program, extBegin, namespace_, outputBase + extBegin);
			return host.hasErrors() ? 1 : exitCode;
		}
		if (c == '/' || c == '\\' || c == ':')
			break;
	}
	std::cerr << "fut: ERROR: Don't know what language to translate to: no extension in '" << outputFile << "' and no '-l' option\n";
	return 1;
}