/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include <complex>
#include <ctime>

#include <ox/logconn/logconn.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/uuid.hpp>
#include <keel/keel.hpp>
#include <turbine/turbine.hpp>

#include <studio/context.hpp>
#include <studioapp/studioapp.hpp>

#include "subcommands/change-format/change-format.hpp"

#include "configfile.hpp"
#include "font.hpp"
#include "studioui.hpp"

namespace studio {

static ox::Error convertStudioConfigV1ToStudioConfigV2(
		keel::Context&,
		StudioConfigV1 &src,
		StudioConfigV2 &dst) noexcept {
	dst.projects.emplace_back(StudioConfigV2::ProjectConfig{
		.projectPath = std::move(src.projectPath),
		.activeTabItemName = std::move(src.activeTabItemName),
		.openFiles = std::move(src.openFiles),
	});
	dst.showProjectExplorer = src.showProjectExplorer;
	return {};
}

static struct: Module {
	ox::String id() const noexcept final {
		return ox::String{"net.drinkingtea.studio"};
	}

	ox::Vector<Command> commands() const final {
		return {
			{
				"change-format",
				cmdChangeFormat,
			}
		};
	}
} constexpr mod;

static struct: keel::Module {
	ox::String id() const noexcept override {
		return ox::String{"net.drinkingtea.studio"};
	}

	ox::Vector<keel::Converter> converters() const noexcept override {
		return {
			keel::Converter::make<convertStudioConfigV1ToStudioConfigV2>(),
		};
	}
} constexpr kmod;


class StudioUIDrawer: public turbine::gl::Drawer {
	private:
		StudioUI &m_ui;
	public:
		explicit StudioUIDrawer(StudioUI &ui) noexcept: m_ui(ui) {
		}

		void draw(turbine::Context&) noexcept final {
			m_ui.draw();
		}
};

static void keyEventHandler(turbine::Context &ctx, turbine::Key const key, bool const down) noexcept {
	auto const sctx = turbine::applicationData<Context>(ctx);
	sctx->ui.handleKeyEvent(key, down);
}

static void mouseButtonEventHandler(turbine::Context &ctx, int const btn, bool const down) noexcept {
	auto const sctx = turbine::applicationData<studio::Context>(ctx);
	sctx->ui.handleMouseButtonEvent(btn, down);
}

[[nodiscard]]
ox::Vector<ox::SpanView<uint8_t>> WindowIcons() noexcept;

static ox::Error runStudio(
		ox::StringViewCR appName,
		ox::StringViewCR projectDataDir,
		ox::UPtr<ox::FileSystem> &&fs) noexcept {
	OX_REQUIRE_M(
		ctx,
		turbine::init(std::move(fs), appName, "net.drinkingtea.nostalgia.NostalgiaStudio"));
	oxLogError(turbine::setWindowIcon(*ctx, WindowIcons()));
	turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
	turbine::setKeyEventHandler(*ctx, keyEventHandler);
	turbine::setMouseButtonEventHandler(*ctx, mouseButtonEventHandler);
	turbine::requireRefreshWithin(*ctx, 0);
	StudioUI ui(*ctx, projectDataDir);
	StudioUIDrawer drawer(ui);
	turbine::gl::addDrawer(*ctx, &drawer);
	auto const err = turbine::run(*ctx);
	turbine::gl::removeDrawer(*ctx, &drawer);
	return err;
}

static ox::Error run(
		ox::StringViewCR appName,
		ox::StringViewCR projectDataDir,
		ox::SpanView<ox::CString> const &args) {
	keel::registerModule(kmod);
	registerModule(&mod);
	// seed UUID generator
	auto const time = std::time(nullptr);
	ox::UUID::seedGenerator({
		static_cast<uint64_t>(time),
		static_cast<uint64_t>(time << 1)
	});
	if (args.size() > 1 && ox::StringView{args[1]} == "cmd") {
		if (args.size() < 5) {
			return ox::Error{1, "insufficient arguments for sub-command"};
		}
		auto constexpr numCmdArgs = 5;
		ox::StringView const projectDir = args[2];
		ox::StringView const moduleId = args[3];
		ox::StringView const subCmd = args[4];
		for (auto const m : modules()) {
			if (m->id() == moduleId) {
				for (auto const &c : m->commands()) {
					if (c.name == subCmd) {
						auto const kctx = keel::init(
							ox::make_unique<ox::PassThroughFS>(projectDir),
							c.name);
						if (kctx.error) {
							return ox::Error{2, "failed to load project directory"};
						}
						Project project{*kctx.value, projectDir, projectDataDir};
						return c.func(
							project,
							args.size() > numCmdArgs ?
								 args + numCmdArgs : ox::SpanView<ox::CString>{});
					}
				}
				return ox::Error{1, "command not found"};
			}
		}
		return ox::Error{1, "module not found"};
	} else {
		// run app
		auto const err = runStudio(appName, projectDataDir, ox::UPtr<ox::FileSystem>{});
		oxAssert(err, "Something went wrong...");
		return err;
	}
	return {};
}

}

ox::Error run(
		ox::StringView const project,
		ox::StringView const appName,
		ox::StringView const projectDataDir,
		ox::SpanView<ox::CString> args) noexcept {
	return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, args);
}
