#ifndef __EVENT_BUS__H__
#define __EVENT_BUS__H__

#include <functional>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>

#include "i_log_lines_events.h"

using namespace std;

class LogLines;

/* EventBus manages event reporting that can effect separate components. In
 * particular, when new lines are inserted, added, or deleted from the loglines,
 * things like the filter runner's matches and pinned lines need to adjust
 */
class EventBus : public ILogLinesEvents {
public:
	// singleton design
	static EventBus* _() {
		static EventBus _event_bus;
		return &_event_bus;
	}

protected:
	// disallow instances
	EventBus() {}
	virtual ~EventBus() {
		assert(empty(&_ll_events));
	}

public:
	// add target to the broadcast list for src
	virtual void enregister(const LogLines* src, ILogLinesEvents* target) {
		unique_lock<shared_mutex> ul(_m);
		assert(!_ll_events[src].count(target));
		_ll_events[src].insert(target);
	}

	// erases target wherever it exists
	virtual void deregister(ILogLinesEvents* target) {
		unique_lock<shared_mutex> ul(_m);
		for (auto& x : _ll_events) {
			x.second.erase(target);
		}
	}

	// erases target in the broadcast list for src
	virtual void deregister(const LogLines* src, ILogLinesEvents* target) {
		unique_lock<shared_mutex> ul(_m);
		// broadcaster deregistered
		if (!_ll_events.count(src)) return;

		assert(_ll_events[src].count(target));
		_ll_events[src].erase(target);
	}

        // an existing line was changed
        virtual void edit_line(LogLines* ll, size_t pos, Line* line) override {
		shared_lock<shared_mutex> ul(_m);
		if (!_ll_events.count(ll)) return;
		for (const auto& x : _ll_events[ll]) {
			x->edit_line(ll, pos, line);
		}
	}


        // a new line was added
        virtual void append_line(LogLines* ll, size_t pos,
				 Line* line) override {
		shared_lock<shared_mutex> ul(_m);
		if (!_ll_events.count(ll)) return;
		for (const auto& x : _ll_events[ll]) {
			x->append_line(ll, pos, line);
		}
	}


        // an insertion event
        virtual void insertion(LogLines* ll, size_t pos,
			       size_t amount) override {
		shared_lock<shared_mutex> ul(_m);
		if (!_ll_events.count(ll)) return;
		for (const auto& x : _ll_events[ll]) {
			x->insertion(ll, pos, amount);
		}
	}

        // a deletion event
        virtual void deletion(LogLines* ll, size_t pos,
			      size_t amount) override {
		shared_lock<shared_mutex> ul(_m);
		if (!_ll_events.count(ll)) return;
		for (const auto& x : _ll_events[ll]) {
			x->deletion(ll, pos, amount);
		}
	}

        // clear all lines
        virtual void clear_lines(LogLines* ll) override {
		shared_lock<shared_mutex> ul(_m);
		if (!_ll_events.count(ll)) return;
		for (const auto& x : _ll_events[ll]) {
			x->clear_lines(ll);
		}
	}

	// announce that a loglines no longer will make events
	void eventmaker_finished(LogLines* src) {
		unique_lock<shared_mutex> ul(_m);
		_ll_events.erase(src);
	}

	/* bool function to use in an assert on destructors of classes that
	 * register for event busses to make sure they unregistered */
	bool present(ILogLinesEvents* dst) {
		shared_lock<shared_mutex> ul(_m);
		if (present(&_ll_events, dst)) return true;
		return false;
	}

protected:
	/* template helper to implement present check */
	template <typename T, typename R>
	bool present(map<T, set<R>>* m, R dst) {
		for (const auto& x : *m) {
			for (const auto& y: x.second) {
				if (y == dst) return true;
			}
		}
		return false;
	}

	/* template helper to implement empty check during destruction */
	template <typename T, typename R>
	bool empty(map<T, set<R>>* m) {
		shared_lock<shared_mutex> ul(_m);
		for (const auto& x : *m) {
			if (x.second.size()) return false;
		}
		return true;
	}

	shared_mutex _m;

	map<const LogLines*, set<ILogLinesEvents*>> _ll_events;

};

#endif  // __EVENT_BUS__H__
