/*
 * 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 "TodoMd.h"
#include "TaskItem.h"
#include "cpp-utils/debug.h"
#include "utils.h"
#include <charconv>
#include <fstream>
#include <iostream>
#include <memory>
#include <ranges>
#include <stdexcept>
#include <string>
#include <string_view>

namespace mirai
{

Tags TodoMdFormat::extractTagsFromMetadata(std::string metadata)
{
	Tags tags;

	std::regex regex("(#([a-zA-Z0-1]+))");
	std::smatch matches;

	while (std::regex_search(metadata, matches, regex)) {
		if (!vectorUtils::contains(tags, matches[2].str())) {
			tags.push_back(matches[2]);
		}
		metadata = matches.suffix();
	}
	return tags;
}

std::string TodoMdFormat::fieldWithSpace(const std::string &field)
{
	if (field.length() == 0) {
		return "";
	}
	return (field + " ");
}

TaskItemData TodoMdFormat::stringToTask(const std::string &str, const std::string &date)
{
	std::smatch matches;
	std::regex regex("- \\[(\\s|X)\\] (([0-9]{2}:[0-9]{2})-([0-9]{2}:[0-9]{2}) > )?(.*?)( -- (.*))?"
	);
	std::regex_match(str, matches, regex);

	/*std::cout << "line " << str << std::endl;*/
	/*std::cout << "M 0 " << matches[0] << std::endl;*/
	/*std::cout << "M 1 " << matches[1] << std::endl;*/
	/*std::cout << "M 2 " << matches[2] << std::endl;*/
	/*std::cout << "M 3 " << matches[3] << std::endl;*/
	/*std::cout << "M 4 " << matches[4] << std::endl;*/
	/*std::cout << "M 5 " << matches[5] << std::endl;*/
	/*std::cout << "M 6 " << matches[6] << std::endl;*/
	/*std::cout << "M 7 " << matches[7] << std::endl;*/

	std::string text = stringUtils::trim(matches[5]);

	TaskItemData taskItem{
		.text = text,
		.state = str.substr(0, 5) == "- [X]" ? DONE : TODO,
		.tags = extractTagsFromMetadata(matches[7])
	};
	return taskItem;
}

Time stringToTime(const std::string &str)
{
	if (str.length() != 5) {
		throw std::runtime_error("Str time length must be 5 (got '" + str + "')");
	}
	if (str[2] != 'h') {
		throw std::runtime_error("Malformated time range");
	}
	Time time{};
	std::from_chars(str.data(), str.data() + 2, time.hour);
	std::from_chars(str.data() + 3, str.data() + 5, time.minute);

	return time;
}

EventData TodoMdFormat::stringToEvent(const std::string &str, const std::string &dateString)
{
	std::smatch matches;
	std::regex regex("> (([0-9]{2}h[0-9]{2})-([0-9]{2}h[0-9]{2}) )(.*?)( -- (.*))?");
	std::regex_match(str, matches, regex);

	/*std::cout << "line " << str << std::endl;*/
	/*std::cout << "M 0 " << matches[0] << std::endl;*/
	/*std::cout << "M 1 " << matches[1] << std::endl;*/
	/*std::cout << "M 2 " << matches[2] << std::endl;*/
	/*std::cout << "M 3 " << matches[3] << std::endl;*/
	/*std::cout << "M 4 " << matches[4] << std::endl;*/
	/*std::cout << "M 5 " << matches[5] << std::endl;*/
	/*std::cout << "M 6 " << matches[6] << std::endl;*/

	std::string text = stringUtils::trim(matches[4]);

	auto date = stringToDate(dateString);

	if (!date.has_value()) {
		throw std::runtime_error("Malformated date");
	}

	EventData eventData{
		.description = text,
		.date = date.value(),
		.startTime = stringToTime(matches[2]),
		.endTime = stringToTime(matches[3]),
		.tags = extractTagsFromMetadata(matches[6])
	};
	return eventData;
}

std::string TodoMdFormat::taskToString(const TaskItem &task)
{
	std::string str = task.getText();
	if (task.getTags().size() > 0) {
		str += " --";
		for (const std::string &tag : task.getTags()) {
			str += " #" + tag;
		}
	}
	str = (task.getState() == DONE ? "- [X] " : "- [ ] ") + str;
	return str;
}

std::string
TodoMdFormat::stringify(const std::string &name, const std::vector<std::unique_ptr<Day>> &days)
{
	std::string result = "# " + name + "\n\n";
	std::string currentDate = "";

	for (const auto &day : days) {
		auto &date = day->getDate();
		result +=
			"## " + std::format("{:02d}-{:02d}-{:02d}", date.year, date.month, date.day) + "\n\n";
		for (const auto &event : *(day->events())) {
			auto &start = event->getStartTime();
			auto &end = event->getEndTime();
			result += "> " +
					  std::format(
						  "{:02d}h{:02d}-{:02d}h{:02d} {}", start.hour, start.minute, end.hour,
						  end.minute, event->getText()
					  ) +
					  "\n";
			for (const auto &task : *(event->tasks())) {
				result += taskToString(*task) + '\n';
			}
			result += '\n';
		}
		for (const auto &task : *(day->tasks())) {
			result += taskToString(*task) + '\n';
		}
		result += '\n';
	}

	return result;
};

MiraiMarkdownFormatParseResult TodoMdFormat::parse(const std::string &content)
{
	cpputils::debug::Timer readMdFormatDuration;
	std::vector<TaskItem> taskItems;
	std::string line;

	std::stringstream contentStream(content);

	if (!std::getline(contentStream, line) || line.substr(0, 2) != "# ") {
		std::cerr << "Couldn't find the task list name" << std::endl;
	}

	std::string name = line.substr(2);
	std::string currentDateString = "";

	std::vector<mirai::DayData> daysData;
	mirai::DayData *currentDay = nullptr;
	mirai::EventData *currentEvent = nullptr;

	cpputils::debug::Timer stringToTaskDuration;
	stringToTaskDuration.reset();
	cpputils::debug::Timer gelinesDuration;
	while (std::getline(contentStream, line)) {
		if (std::string_view{line.data(), 3} == "## ") {
			currentDateString = line.substr(3);
			auto dateOpt = mirai::stringToDate(currentDateString);
			if (!dateOpt.has_value()) {
				throw std::runtime_error("Malformated date");
			}
			daysData.push_back({.date = dateOpt.value()});
			currentDay = &daysData.back();
		} else if (line.starts_with("> ")) {
			auto event = stringToEvent(line, currentDateString);
			if (currentDay) {
				currentDay->events.push_back(event);
				currentEvent = &currentDay->events.back();
			}
		} else if (line.starts_with("- [ ]") || line.starts_with("- [X]")) {
			stringToTaskDuration.start();
			TaskItemData taskItemData = stringToTask(line, currentDateString);
			stringToTaskDuration.stop();
			if (currentEvent) {
				currentEvent->tasks.push_back(taskItemData);
			} else if (currentDay) {
				currentDay->tasks.push_back(taskItemData);
			}
		} else if (cpputils::string::trim(line) == "") {
			currentEvent = nullptr;
		}
	}

	gelinesDuration.printTimeElapsed("getlinesDuration");
	stringToTaskDuration.printTimeElapsed("stringToTaskDuration");
	readMdFormatDuration.printTimeElapsed("Reading MD File duration");
	return {.name = name, .days = daysData};
}
} // namespace mirai
