/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/assert.hpp>
#include <ox/std/def.hpp>
#include <ox/std/defines.hpp>
#include <ox/std/error.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/vector.hpp>

namespace ox {

class SignalHandler;

#ifndef OX_OS_BareMetal

namespace detail {

template<typename T>
struct isError {
	static constexpr bool value = false;
};

template<>
struct isError<Error> {
	static constexpr bool value = true;
};

}

template<class... Args>
class Signal {
	protected:
		struct BaseSlot {
			virtual ~BaseSlot() = default;
			virtual void call(Args...) = 0;
			virtual void cleanup(Signal*) noexcept {}
			[[nodiscard]]
			virtual const void *receiver() const noexcept { return nullptr; }
		};

		template<typename F>
		struct FunctionSlot: public BaseSlot {
			F f;

			explicit FunctionSlot(F f) {
				this->f = f;
			}

			void call(Args... args) final {
				if constexpr(detail::isError<decltype(f(args...))>::value) {
					OX_THROW_ERROR(f(args...));
				} else {
					f(args...);
				}
			}
		};

		template<typename T, typename Method>
		struct MethodSlot: public BaseSlot {
			T m_receiver = nullptr;
			Method m_methodPtr;

			MethodSlot(T receiver, Method methodPtr) {
				m_receiver = receiver;
				m_methodPtr = methodPtr;
			}

			void call(Args... args) final {
				if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
					OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
				} else {
					f(args...);
				}
			}

			void cleanup(Signal *signal) noexcept final {
				std::ignore = m_receiver->destruction.disconnectSignal(signal);
				//if (err) {
				//	oxErrorf("Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free. ({})", toStr(err));
				//}
			}

			[[nodiscard]]
			const void *receiver() const noexcept final {
				return m_receiver;
			}
		};

		template<typename SignalT, typename Method>
		struct SignalMethodSlot: public BaseSlot {
			const SignalT *m_receiver = nullptr;
			Method m_methodPtr;

			SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
				m_receiver = receiver;
				m_methodPtr = methodPtr;
			}

			void call(Args... args) final {
				if constexpr(detail::isError<decltype((m_receiver->*(m_methodPtr))(args...))>::value) {
					OX_THROW_ERROR((m_receiver->*(m_methodPtr))(args...));
				} else {
					(m_receiver->*(m_methodPtr))(args...);
				}
			}

			void cleanup(Signal*) noexcept final {
			}

			[[nodiscard]]
			const void *receiver() const noexcept final {
				return m_receiver;
			}
		};

		mutable Vector<UPtr<BaseSlot>> m_slots;

	public:
		~Signal() noexcept;

		void connect(Error(*f)(Args...)) const noexcept;

		template<typename T, typename Method>
		void connect(const T *receiver, Method methodPtr) const noexcept;

		template<typename T, typename Method>
		void connect(T *receiver, Method methodPtr) const noexcept;

		template<class... SubArgs, typename Method>
		void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;

		template<class... SubArgs>
		Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;

		Error disconnectObject(const void *receiver) const noexcept;

		[[nodiscard]]
		size_t connectionCnt() const noexcept {
			return m_slots.size();
		}

		void emit(Args... args) const;

		Error emitCheckError(Args... args) const noexcept;
};

extern template class Signal<const SignalHandler*>;

template<class... Args>
Signal<Args...>::~Signal() noexcept {
	for (auto &slot : m_slots) {
		slot->cleanup(this);
	}
}

template<class... Args>
void Signal<Args...>::connect(Error(*f)(Args...)) const noexcept {
	m_slots.emplace_back(new FunctionSlot<decltype(f)>(f));
}

template<class... Args>
template<typename T, typename Method>
void Signal<Args...>::connect(const T *receiver, Method methodPtr) const noexcept {
	receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
	m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
}

template<class... Args>
template<typename T, typename Method>
void Signal<Args...>::connect(T *receiver, Method methodPtr) const noexcept {
	receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
	m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
}

template<class... Args>
template<class... SubArgs, typename Method>
void Signal<Args...>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
	m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
}

template<class... Args>
template<class... SubArgs>
Error Signal<Args...>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
	return disconnectObject(receiver);
}

template<class... Args>
Error Signal<Args...>::disconnectObject(const void *receiver) const noexcept {
	for (auto i = 0u; i < m_slots.size(); ++i) {
		const auto &slot = m_slots[i];
		if (slot->receiver() == receiver) {
			OX_RETURN_ERROR(m_slots.erase(i));
			--i;
		}
	}
	return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
}

template<class... Args>
void Signal<Args...>::emit(Args... args) const {
	for (auto &f : m_slots) {
		f->call(args...);
	}
}

template<class... Args>
Error Signal<Args...>::emitCheckError(Args... args) const noexcept {
	try {
		for (auto &f : m_slots) {
			f->call(args...);
		}
		return {};
	} catch (const ox::Exception &ex) {
		return ox::Error(ex.errCode, ex.msg, ex.src);
	}
}

#else

template<typename T>
class Signal;

#endif

template<class... Args>
class Signal<Error(Args...)> {
	protected:
		struct BaseSlot {
			virtual ~BaseSlot() = default;
			virtual Error call(Args...) noexcept = 0;
			virtual void cleanup(Signal*) noexcept {}
			[[nodiscard]]
			virtual const void *receiver() const noexcept { return nullptr; }
		};

		struct FunctionSlot: public BaseSlot {
			Error (*f)(Args...);

			explicit FunctionSlot(Error (*f)(Args...)) noexcept {
				this->f = f;
			}

			Error call(Args... args) noexcept final {
				return f(ox::forward<Args>(args)...);
			}
		};

		template<typename T, typename Method>
		struct MethodSlot: public BaseSlot {
			T m_receiver = nullptr;
			Method m_methodPtr;

			MethodSlot(T receiver, Method methodPtr) {
				m_receiver = receiver;
				m_methodPtr = methodPtr;
			}

			Error call(Args... args) noexcept final {
				return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
			}

			void cleanup(Signal *signal) noexcept final {
				std::ignore = m_receiver->destruction.disconnectSignal(signal);
				//oxErrorf("{}", toStr(err));
				//oxAssert(err, "Signal could not notify receiver that it is being destroyed. Destruction of receiver will cause use-after-free.");
			}

			[[nodiscard]]
			const void *receiver() const noexcept final {
				return m_receiver;
			}
		};

		template<typename SignalT, typename Method>
		struct SignalMethodSlot: public BaseSlot {
			const SignalT *m_receiver = nullptr;
			Method m_methodPtr;

			SignalMethodSlot(const SignalT *receiver, Method methodPtr) {
				m_receiver = receiver;
				m_methodPtr = methodPtr;
			}

			Error call(Args... args) noexcept final {
				return (m_receiver->*(m_methodPtr))(ox::forward<Args>(args)...);
			}

			void cleanup(Signal*) noexcept final {
			}

			[[nodiscard]]
			const void *receiver() const noexcept final {
				return m_receiver;
			}
		};

		mutable Vector<UPtr<BaseSlot>> m_slots;

	public:
		~Signal() noexcept;

		void connect(Error(*f)(Args...)) const noexcept;

		template<typename T, typename Method>
		void connect(const T *receiver, Method methodPtr) const noexcept;

		template<typename T, typename Method>
		void connect(T *receiver, Method methodPtr) const noexcept;

		template<class... SubArgs, typename Method>
		void connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept;

		template<class... SubArgs>
		Error disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept;

		Error disconnectObject(const void *receiver) const noexcept;

		[[nodiscard]]
		size_t connectionCnt() const noexcept {
			return m_slots.size();
		}

		void emit(Args... args) const noexcept;

		Error emitCheckError(Args... args) const noexcept;
};

extern template class Signal<Error(const SignalHandler*)>;

class SignalHandler {
	public:
		Signal<Error(const SignalHandler*)> destruction;

		constexpr SignalHandler() noexcept = default;
		SignalHandler(const SignalHandler&) = delete;
		SignalHandler(SignalHandler&) = delete;
		SignalHandler(SignalHandler&&) = delete;

		virtual ~SignalHandler() noexcept;
};

template<class... Args>
Signal<Error(Args...)>::~Signal() noexcept {
	for (auto &slot : m_slots) {
		slot->cleanup(this);
	}
}

template<class... Args>
void Signal<Error(Args...)>::connect(Error(*f)(Args...)) const noexcept {
	m_slots.emplace_back(new FunctionSlot(f));
}

template<class... Args>
template<typename T, typename Method>
void Signal<Error(Args...)>::connect(const T *receiver, Method methodPtr) const noexcept {
	receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
	m_slots.emplace_back(new MethodSlot<const T*, Method>(receiver, methodPtr));
}

template<class... Args>
template<typename T, typename Method>
void Signal<Error(Args...)>::connect(T *receiver, Method methodPtr) const noexcept {
	receiver->destruction.connect(this, &Signal<Error(Args...)>::disconnectObject);
	m_slots.emplace_back(new MethodSlot<T*, Method>(receiver, methodPtr));
}

template<class... Args>
template<class... SubArgs, typename Method>
void Signal<Error(Args...)>::connect(const Signal<SubArgs...> *receiver, Method methodPtr) const noexcept {
	m_slots.emplace_back(new SignalMethodSlot<Signal<SubArgs...>, Method>(receiver, methodPtr));
}

template<class... Args>
template<class... SubArgs>
Error Signal<Error(Args...)>::disconnectSignal(const Signal<SubArgs...> *receiver) const noexcept {
	return disconnectObject(receiver);
}

template<class... Args>
Error Signal<Error(Args...)>::disconnectObject(const void *receiver) const noexcept {
	for (auto i = 0u; i < m_slots.size(); ++i) {
		const auto &slot = m_slots[i];
		if (slot->receiver() == receiver) {
			OX_RETURN_ERROR(m_slots.erase(i));
			--i;
		}
	}
	return ox::Error(1, "Signal::disconnectObject: Receiver was not found among this Signal's slots");
}

template<class... Args>
void Signal<Error(Args...)>::emit(Args... args) const noexcept {
	for (auto &f : m_slots) {
		std::ignore = f->call(ox::forward<Args>(args)...);
	}
}

template<class... Args>
Error Signal<Error(Args...)>::emitCheckError(Args... args) const noexcept {
	for (auto &f : m_slots) {
		OX_RETURN_ERROR(f->call(ox::forward<Args>(args)...));
	}
	return {};
}

}
