#include <psemek/journal/journal.hpp>
#include <psemek/util/hash_table.hpp>

#include <algorithm>

namespace psemek::journal
{

	struct journal::impl
	{
		struct table
		{
			event_metadata metadata;
			mutable std::vector<event_data> events;
		};

		struct table_hash
		{
			std::uint64_t operator()(std::string const & name) const noexcept
			{
				return std::hash<std::string>{}(name);
			}

			std::uint64_t operator()(table const & table) const noexcept
			{
				return std::hash<std::string>{}(table.metadata.name);
			}
		};

		struct table_equal
		{
			bool operator()(std::string const & name1, table const & table2) const noexcept
			{
				return name1 == table2.metadata.name;
			}

			bool operator()(table const & table1, table const & table2) const noexcept
			{
				return table1.metadata.name == table2.metadata.name;
			}
		};

		bool enabled = true;
		util::hash_set<table, table_hash, table_equal> tables;

		void log_event(event const & event)
		{
			if (!enabled)
				return;

			auto table_it = tables.find(event.metadata.name);
			if (table_it == tables.end())
				table_it = tables.insert(table{event.metadata, {}}).first;
			table_it->events.push_back(event.data);
		}

		std::vector<std::pair<event_metadata const *, event_data const *>> select()
		{
			if (!enabled)
				return {};

			std::vector<std::pair<event_metadata const *, event_data const *>> result;

			for (auto const & table : tables)
				for (auto const & event : table.events)
					result.push_back({&table.metadata, &event});

			std::sort(result.begin(), result.end(), [](auto const & e1, auto const & e2){ return e1.second->time < e2.second->time; });

			return result;
		}
	};

	journal::journal()
		: pimpl_(make_impl())
	{}

	journal::~journal() = default;

	bool journal::enabled() const
	{
		return impl().enabled;
	}

	void journal::set_enabled(bool enabled)
	{
		impl().enabled = enabled;
	}

	void journal::log_event(event const & event)
	{
		impl().log_event(event);
	}

	std::vector<std::pair<event_metadata const *, event_data const *>> journal::select()
	{
		return impl().select();
	}

}
