/*
 * 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 "Event.hpp"

namespace OpenVulkano
{
	template<typename T>
	class Observable final
	{
		T object;

		void Notify() { OnChange(object); }

	public:
		template<typename = std::enable_if_t<std::is_default_constructible_v<T>>>
		Observable() {}

		Observable(const T& initValue) : object(initValue) {}

		Observable(T&& initValue) : object(std::forward(initValue)) {}

		//TODO make this somehow only work for none string types to prevent issues?
		/*template<typename = std::enable_if_t<std::is_default_constructible_v<T>>>
		explicit Observable(const std::string& observableName) : OnChange(observableName) {}*/

		Observable(const T& initValue, const std::string& observableName)
				: object(initValue), OnChange(observableName)
		{}

		Observable(T&& initValue, const std::string& observableName)
				: object(initValue), OnChange(observableName)
		{}

		template<typename = std::enable_if_t<std::is_copy_assignable_v<T>>>
		auto& operator = (const T& val) { object = val; Notify(); return *this; }

		template<typename = std::enable_if_t<std::is_move_assignable_v<T>>>
		auto& operator = (T&& val) { object = val; Notify(); return *this; }

		template<typename = std::enable_if_t<std::is_copy_assignable_v<T>>>
		operator T() const { return object; }

		operator const T&() const { return object; }

		bool operator ==(const T& other) const { return object == other; }
		bool operator !=(const T& other) const { return object != other; }
		bool operator <(const T& other) const { return object < other; }
		bool operator <=(const T& other) const { return object <= other; }
		bool operator >(const T& other) const { return object > other; }
		bool operator >=(const T& other) const { return object >= other; }
		bool operator !() const { return !object; }

		template<std::enable_if<std::is_convertible_v<T, bool>>>
		operator bool() const { return static_cast<bool>(object); }

		auto& operator ++() { ++object; Notify(); return *this; }
		auto& operator --() { --object; Notify(); return *this; }
		auto& operator ++(int) { object++; Notify(); return *this; }
		auto& operator --(int) { object--; Notify(); return *this; }

		auto& operator +=(const T& other) { object += other; Notify(); return *this; }
		auto& operator -=(const T& other) { object -= other; Notify(); return *this; }
		auto& operator *=(const T& other) { object *= other; Notify(); return *this; }
		auto& operator /=(const T& other) { object /= other; Notify(); return *this; }

		const T* operator ->() const { return &object; }

		void MutatingAccess(const std::function<bool(T&)> accessFunction) { if (accessFunction(object)) Notify(); }

		Event<const T&> OnChange;
	};
}
