#include <iostream>
#include <cstddef>

// Allgemein

template <auto MemFun>
class GetMemFunWrapper;

template <typename Ret, typename... Args, typename Klass, Ret (Klass::*MemFun) (Args...)>
class GetMemFunWrapper<MemFun> {
	public:
		static Ret function (Args... args, void* userData) {
			return (static_cast<Klass*> (userData)->*MemFun) (static_cast<Args&&> (args)...);
		}
};

template <auto MemFun>
static constexpr auto wrapMemFun = &GetMemFunWrapper< MemFun>::function;

// Button-Spezifisch

static constexpr std::size_t maxCallbacks = 4;

class Button {
	public:
		using ClickCallback = bool (*) (int, void*);
		using DoubleClickCallback = int (*) (short, long, void*);


		Button () : numClickCallbacks (0), numDoubleClickCallbacks(0) {}

		void addClickCallback (ClickCallback cb, void *userData) {
			if (numClickCallbacks >= maxCallbacks) abort ();
			clickUserData [numClickCallbacks] = userData;
			clickCallbacks [numClickCallbacks++] = cb;
		}
		void addDoubleClickCallback (DoubleClickCallback cb, void *userData) {
			if (numDoubleClickCallbacks >= maxCallbacks) abort ();
			doubleClickUserData [numDoubleClickCallbacks] = userData;
			doubleClickCallbacks [numDoubleClickCallbacks++] = cb;
		}

		bool onClick (int x) {
			// Beispiel: Iteriere solange die Observer bis einer "true" zurück gibt
			for (std::size_t i = 0; i < numClickCallbacks; ++i) {
				if (clickCallbacks [i] (x, clickUserData [i]))
					return true;
			}
			return false;
		}
		int onDoubleClick (short x, long y) {
			// Beispiel: Summiere die Rückgabewerte der Observer
			int sum = 0;
			for (std::size_t i = 0; i < numClickCallbacks; ++i) {
				sum += doubleClickCallbacks [i] (x, y, doubleClickUserData [i]);
			}
			return sum;

		}
	private:
		ClickCallback clickCallbacks [maxCallbacks];
		DoubleClickCallback doubleClickCallbacks [maxCallbacks];
		void* clickUserData [maxCallbacks];
		void* doubleClickUserData [maxCallbacks];
		std::size_t numClickCallbacks, numDoubleClickCallbacks;
};

// Screen-Spezifisch

class Screen {
	public:
		Screen () {
			btn0.addClickCallback (wrapMemFun<&Screen::onButton0Click>, this);
			btn0.addDoubleClickCallback (wrapMemFun<&Screen::onButton0DoubleClick>, this);
			btn1.addClickCallback (wrapMemFun<&Screen::onButton1Click>, this);
			btn1.addDoubleClickCallback (wrapMemFun<&Screen::onButton1DoubleClick>, this);

		}
		bool onButton0Click (int x) {
			std::cout << "Button 0 Click(" << x << ")" << std::endl;
			return true;
		}
		int onButton0DoubleClick (short x, long y) {
			std::cout << "Button 0 DoubleClick(" << x << ", " << y << ")" << std::endl;
			return 0;
		}
		bool onButton1Click (int x) {
			std::cout << "Button 1 Click(" << x << ")" << std::endl;
			return true;
		}
		int onButton1DoubleClick (short x, long y) {
			std::cout << "Button 1 DoubleClick(" << x << ", " << y << ")" << std::endl;
			return 0;
		}
		Button btn0, btn1;
};

int main () {
	Screen s;
	s.btn0.onClick (42);
	s.btn0.onDoubleClick (12, 3456);

	s.btn1.onClick (1);
	s.btn1.onDoubleClick (2, 3);
}