/*
 * 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 "MarkdownDataProvider.h"
#include "DataProvider.h"
#include "cpp-utils/debug.h"
#include "utils.h"
#include <algorithm>
#include <cassert>
#include <functional>
#include <optional>
#include <regex>
#include <vector>

namespace mirai
{

std::string MarkdownDataProvider::name() const
{
	return data.name;
}

void MarkdownDataProvider::save()
{
	std::ofstream file(filePath_);
	if (!file.is_open()) {
		throw std::runtime_error("can't create " + filePath_);
	}

	const std::string content = toMarkdown();

	file << content;
	file.close();
	setDirty(false);
};

void MarkdownDataProvider::load()
{
	std::ifstream file(filePath_);
	if (!file.is_open()) {
		return;
	}
	std::string content = "";
	std::string line;

	while (std::getline(file, line)) {
		content += line + "\n";
	}
	file.close();
	data = parseMarkdown(content);
};

TaskData MarkdownDataProvider::insertTask(const TaskData &taskData)
{
	data.tasks.push_back(taskData);
	setDirty(true);
	return taskData;
}

void MarkdownDataProvider::updateTask(int taskId, UpdatableTaskData updateData)
{
	auto taskData = ptrFindFirst(data.tasks, [&](const TaskData &task) {
		return task.id == taskId;
	});
	assert(taskData != nullptr); // Shouldn't be possible
	if (updateData.title.has_value()) {
		taskData->title = updateData.title.value();
	}
	if (updateData.state.has_value()) {
		taskData->state = updateData.state.value();
	}
	if (updateData.dayId.has_value()) {
		taskData->dayId = updateData.dayId.value();
	}
	if (updateData.eventId.has_value()) {
		taskData->eventId = updateData.eventId.value();
	}
	setDirty(true);
}

std::optional<TaskData> MarkdownDataProvider::getTaskById(int taskId)
{
	return findFirst(data.tasks, [&](const TaskData &task) {
		return task.id == taskId;
	});
}

std::vector<TaskData> MarkdownDataProvider::getTasksByEventId(int eventId)
{
	return findAll(data.tasks, [&](const TaskData &task) {
		return task.eventId == eventId;
	});
}

std::vector<TaskData> MarkdownDataProvider::getTasksByDayId(int dayId)
{
	return findAll(data.tasks, [&](const TaskData &task) {
		return task.dayId == dayId;
	});
}

std::vector<TaskData> MarkdownDataProvider::getTasksWithoutDate()
{
	return findAll(data.tasks, [](const TaskData &t) {
		return t.dayId == std::nullopt && t.eventId == std::nullopt;
	});
}

std::vector<TaskData> MarkdownDataProvider::getTasksByDate(Date date)
{
	auto day = getDayByDate(date);
	if (!day.has_value()) {
		return {};
	}
	return findAll(data.tasks, [&](const TaskData &t) {
		return t.dayId == day.value().id;
	});
}

std::vector<TaskData> MarkdownDataProvider::getTasks()
{
	return data.tasks;
}

void MarkdownDataProvider::removeTaskById(int taskId)
{
	data.tasks.erase(
		std::remove_if(
			data.tasks.begin(), data.tasks.end(),
			[&](const TaskData &task) {
				return task.id == taskId;
			}
		),
		data.tasks.end()
	);
	setDirty(true);
}

DayData MarkdownDataProvider::insertDay(const DayData &dayData)
{
	data.days.push_back(dayData);
	setDirty(true);
	return dayData;
}

void MarkdownDataProvider::updateDay(int dayId, UpdatableDayData updateData)
{
	auto dayData = ptrFindFirst(data.days, [&](const DayData &day) {
		return day.id == dayId;
	});
	assert(dayData != nullptr); // Shouldn't be possible
	if (updateData.date.has_value()) {
		dayData->date = updateData.date.value();
	}
	setDirty(true);
}

void MarkdownDataProvider::removeDayById(int dayId)
{
	data.days.erase(
		std::remove_if(
			data.days.begin(), data.days.end(),
			[&](const DayData &day) {
				return day.id == dayId;
			}
		),
		data.days.end()
	);
	setDirty(true);
}

std::vector<DayData> MarkdownDataProvider::getDays()
{
	return data.days;
}

std::optional<DayData> MarkdownDataProvider::getDayByDate(const Date &date)
{
	return findFirst(data.days, [&](const DayData &day) {
		return day.date == date;
	});
}

EventData MarkdownDataProvider::insertEvent(const EventData &eventData)
{
	data.events.push_back(eventData);
	setDirty(true);
	return eventData;
}

void MarkdownDataProvider::updateEvent(int eventId, UpdatableEventData updateData)
{
	auto eventData = ptrFindFirst(data.events, [&](const EventData &event) {
		return event.id == eventId;
	});
	assert(eventData != nullptr); // Shouldn't be possible
	if (updateData.title.has_value()) {
		eventData->title = updateData.title.value();
	}
	if (updateData.dayId.has_value()) {
		eventData->dayId = updateData.dayId.value();
	}
	if (updateData.startsAt.has_value()) {
		eventData->startsAt = updateData.startsAt.value();
	}
	if (updateData.endsAt.has_value()) {
		eventData->endsAt = updateData.endsAt.value();
	}
	setDirty(true);
}

void MarkdownDataProvider::removeEventById(int eventId)
{
	data.events.erase(
		std::remove_if(
			data.events.begin(), data.events.end(),
			[&](const EventData &event) {
				return event.id == eventId;
			}
		),
		data.events.end()
	);
	setDirty(true);
}

std::optional<EventData> MarkdownDataProvider::getEventById(int eventId)
{
	return findFirst(data.events, [&](const EventData &event) {
		return event.id == eventId;
	});
}

std::vector<EventData> MarkdownDataProvider::getEventsByDate(Date date)
{
	auto day = getDayByDate(date);
	if (!day.has_value()) {
		return {};
	}
	return findAll(data.events, [&](const EventData &event) {
		return event.dayId == day.value().id;
	});
}

std::vector<EventData> MarkdownDataProvider::getEvents()
{
	return data.events;
}
} // namespace mirai
