/*
 * Mirai. Copyright (C) 2024 Vyn
 * This file is licensed under version 3 of the GNU General Public License (GPL-3.0-only)
 * The license can be found in the LICENSE file or at https://www.gnu.org/licenses/gpl-3.0.txt
 */

#include "Mirai.h"
#include "DataProvider.h"
#include "MarkdownDataProvider.h"
#include "Source.h"
#include "nlohmann/json.hpp"
#include <algorithm>
#include <iostream>
#include <memory>
#include <print>
#include <string>
#include <vector>

namespace mirai
{

void Mirai::loadConfig(const std::string &path)
{
	auto configJson = nlohmann::json::parse(R"(
		{
			"files": []
		}
	)");
	std::ifstream file(path);
	if (!file) {

		return;
	}
	configJson = nlohmann::json::parse(file);

	assert(configJson.is_object());
	assert(configJson["files"].is_array());
	auto jsonSources = configJson["files"];
	for (const auto &filePath : jsonSources) {
		assert(filePath.is_object());
		const auto name = filePath["name"].get<std::string>();
		const auto path = filePath["path"].get<std::string>();
		std::unique_ptr<DataProvider> file = std::make_unique<MarkdownDataProvider>(path);
		DataProvider *sourceDataProvider = file.release();
		sourceDataProvider->load();
		sources_.push_back(std::make_unique<Source>(
			SourceConstructor{.name = name, .sourceDataProvider = sourceDataProvider}
		));
	}
}

void Mirai::saveConfig()
{
	std::ofstream file(configPath_);
	if (!file.is_open()) {
		std::print(std::cerr, "Can't save config to {}", configPath_);
		return;
	}
	auto configJson = nlohmann::json::parse(R"(
		{
			"files": []
		}
	)");
	for (auto &source : sources_) {
		nlohmann::json jsonSource;
		jsonSource["name"] = source->name();
		auto dataProvider = dynamic_cast<MarkdownDataProvider *>(source->dataProvider());
		jsonSource["path"] = dataProvider->path();
		jsonSource["type"] = "FileSystemMarkdown";
		configJson["files"].push_back(jsonSource);
	}
	file << configJson.dump(4);
	file.close();
}

Mirai::Mirai(const std::string &configFilePath) : configPath_(configFilePath)
{
	loadConfig(configFilePath);
}

void Mirai::addSource(
	const std::string &name, const std::string &type, std::unique_ptr<DataProvider> &&source
)
{
	DataProvider *sourceDataProvider = source.release();
	sourceDataProvider->load();
	sources_.push_back(std::make_unique<Source>(
		SourceConstructor{.name = name, .sourceDataProvider = sourceDataProvider}
	));
	saveConfig();
	sourceAdded.emit(nullptr);
};

void Mirai::editSource(int id, const std::string &name, const std::string &path)
{
	auto source = getSourceById(id);
	source->setName(name);

	DataProvider *sourceDataProvider = source->dataProvider();
	saveConfig();
	sourceEdited.emit(nullptr);
}

void Mirai::deleteSource(int id)
{
	auto source = getSourceById(id);
	sources_.erase(
		std::remove_if(
			sources_.begin(), sources_.end(),
			[&](const std::unique_ptr<Source> &source) {
				return source->id == id;
			}
		),
		sources_.end()
	);

	saveConfig();
	sourceDeleted.emit(id);
}

void Mirai::unloadAllSources()
{
	sources_.clear();
}

void Mirai::save()
{
	for (auto &source : sources_) {
		if (source->isDirty()) {
			source->save();
		}
	}
}

std::vector<std::unique_ptr<Source>> &Mirai::getSources()
{
	return sources_;
}

Source *Mirai::getSourceById(int id)
{
	auto source = std::ranges::find_if(sources_, [&](const std::unique_ptr<Source> &source) {
		return source->id == id;
	});
	assert(source != sources_.end()); // This should not happen
	if (source == sources_.end()) {
		return nullptr;
	}
	return source->get();
}

Source *Mirai::getSourceByName(const std::string &name)
{
	auto source = std::ranges::find_if(sources_, [&](const std::unique_ptr<Source> &source) {
		return source->name() == name;
	});
	assert(source != sources_.end()); // This should not happen
	if (source == sources_.end()) {
		return nullptr;
	}
	return source->get();
}

void Mirai::onSourceAdded(std::function<void(Source *)> f)
{
	sourceAdded.registerCallback(f);
}

void Mirai::onSourceEdited(std::function<void(Source *)> f)
{
	sourceEdited.registerCallback(f);
}

void Mirai::onSourceDeleted(std::function<void(int)> f)
{
	sourceDeleted.registerCallback(f);
}

} // namespace mirai
