// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#pragma once

#include <objc/runtime.h>
#include <objc/message.h>
#include <tuple>
#include <string>
#include <optional>
#include <cmath>
#include <cassert>

//------------------------------------------------------------------------------------
namespace VSTGUI {

//------------------------------------------------------------------------------------
template<typename T>
struct ObjCVariable
{
	ObjCVariable (__unsafe_unretained id obj, Ivar ivar) : obj (obj), ivar (ivar) {}
	ObjCVariable (ObjCVariable&& o) { *this = std::move (o); }

	ObjCVariable& operator= (ObjCVariable&& o)
	{
		obj = o.obj;
		ivar = o.ivar;
		o.obj = nullptr;
		o.ivar = nullptr;
		return *this;
	}

	T get () const
	{
		auto offset = ivar_getOffset (ivar);
		return *reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
	}

	void set (const T& value)
	{
		auto offset = ivar_getOffset (ivar);
		auto storage = reinterpret_cast<T*> (((__bridge uintptr_t)obj) + offset);
		*storage = value;
	}

private:
	__unsafe_unretained id obj;
	Ivar ivar {nullptr};
};

//------------------------------------------------------------------------------------
struct ObjCInstance
{
	ObjCInstance (__unsafe_unretained id obj, Class superClass = nullptr) : obj (obj)
	{
		os.super_class = superClass;
	}

	template<typename T>
	std::optional<ObjCVariable<T>> getVariable (const char* name) const
	{
		if (__strong auto ivar = class_getInstanceVariable (object_getClass (obj), name))
		{
			return {ObjCVariable<T> (obj, ivar)};
		}
		return {};
	}

	template<typename Func, typename... T>
	void callSuper (SEL selector, T... args) const
	{
		void (*f) (__unsafe_unretained id, SEL, T...) =
			(void (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
		f (getSuper (), selector, args...);
	}

	template<typename Func, typename R, typename... T>
	R callSuper (SEL selector, T... args) const
	{
		R (*f)
		(__unsafe_unretained id, SEL, T...) =
			(R (*) (__unsafe_unretained id, SEL, T...))objc_msgSendSuper;
		return f (getSuper (), selector, args...);
	}

private:
	id getSuper () const
	{
		if (os.receiver == nullptr)
		{
			os.receiver = obj;
		}
		if (os.super_class == nullptr)
		{
			os.super_class = class_getSuperclass (object_getClass (obj));
		}
		return (__bridge id) (&os);
	}

	__unsafe_unretained id obj;
	mutable objc_super os {};
};

//------------------------------------------------------------------------
template<typename T>
struct RuntimeObjCClass
{
	using Base = RuntimeObjCClass<T>;

	static id alloc ()
	{
		static auto allocSel = sel_registerName ("alloc");
		if (auto method = class_getClassMethod (instance ().cl, allocSel))
		{
			if (auto methodImpl = method_getImplementation (method))
			{
				using fn2 = id (*) (id, SEL);
				auto alloc = (fn2)methodImpl;
				return alloc (instance ().cl, allocSel);
			}
		}
		return nil;
	}

	RuntimeObjCClass ()
	{
		cl = T::CreateClass ();
		superClass = class_getSuperclass (cl);
	}

	virtual ~RuntimeObjCClass () noexcept
	{
		if (cl)
			objc_disposeClassPair (cl);
	}

	static ObjCInstance makeInstance (__unsafe_unretained id obj)
	{
		return ObjCInstance (obj, instance ().superClass);
	}

protected:
	static T& instance ()
	{
		static T gInstance;
		return gInstance;
	}

	Class getClass () const { return cl; }
	Class getSuperClass () const { return superClass; }

private:
	Class cl {nullptr};
	Class superClass {nullptr};
};

//------------------------------------------------------------------------------------
struct ObjCClassBuilder
{
	ObjCClassBuilder& init (const char* name, Class baseClass);

	template<typename Func>
	ObjCClassBuilder& addMethod (SEL selector, Func imp);
	template<typename T>
	ObjCClassBuilder& addIvar (const char* name);

	ObjCClassBuilder& addProtocol (const char* name);
	ObjCClassBuilder& addProtocol (Protocol* proto);

	Class finalize ();

private:
	static Class generateUniqueClass (const std::string& inClassName, Class baseClass);

	template<typename Func>
	ObjCClassBuilder& addMethod (SEL selector, Func imp, const char* types);

	ObjCClassBuilder& addIvar (const char* name, size_t size, uint8_t alignment, const char* types);

	template<typename R, typename... T>
	static constexpr std::tuple<R, T...> functionArgs (R (*) (T...))
	{
		return std::tuple<R, T...> ();
	}

	template<typename... T>
	static constexpr std::tuple<T...> functionArgs (void (*) (T...))
	{
		return std::tuple<T...> ();
	}

	template<typename R, typename... T>
	static constexpr bool isVoidReturnType (R (*) (T...))
	{
		return false;
	}

	template<typename... T>
	static constexpr bool isVoidReturnType (void (*) (T...))
	{
		return true;
	}

	template<typename Proc>
	static std::string encodeFunction (Proc proc)
	{
		std::string result;
		if (isVoidReturnType (proc))
			result = "v";
		std::apply ([&] (auto&&... args) { ((result += @encode (decltype (args))), ...); },
					functionArgs (proc));
		return result;
	}

	Class cl {nullptr};
	Class baseClass {nullptr};
};

//------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::init (const char* name, Class bc)
{
	baseClass = bc;
	cl = generateUniqueClass (name, baseClass);
	return *this;
}

//------------------------------------------------------------------------------------
inline Class ObjCClassBuilder::generateUniqueClass (const std::string& inClassName, Class baseClass)
{
	std::string className (inClassName);
	int32_t iteration = 0;
	while (objc_lookUpClass (className.data ()) != nil)
	{
		iteration++;
		className = inClassName + "_" + std::to_string (iteration);
	}
	Class resClass = objc_allocateClassPair (baseClass, className.data (), 0);
	return resClass;
}

//-----------------------------------------------------------------------------
inline Class ObjCClassBuilder::finalize ()
{
	objc_registerClassPair (cl);

	auto res = cl;
	baseClass = cl = nullptr;
	return res;
}

//-----------------------------------------------------------------------------
template<typename Func>
inline ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp, const char* types)
{
	auto res = class_addMethod (cl, selector, IMP (imp), types);
	assert (res == true);
	(void)res;
	return *this;
}

//-----------------------------------------------------------------------------
template<typename Func>
ObjCClassBuilder& ObjCClassBuilder::addMethod (SEL selector, Func imp)
{
	return addMethod (selector, imp, encodeFunction (imp).data ());
}

//------------------------------------------------------------------------
template<typename T>
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name)
{
	return addIvar (name, sizeof (T), static_cast<uint8_t> (std::log2 (sizeof (T))), @encode (T));
}

//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addIvar (const char* name, size_t size,
													uint8_t alignment, const char* types)
{
	auto res = class_addIvar (cl, name, size, alignment, types);
	assert (res == true);
	(void)res;
	return *this;
}

//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (const char* name)
{
	if (auto protocol = objc_getProtocol (name))
		return addProtocol (protocol);
	return *this;
}

//-----------------------------------------------------------------------------
inline ObjCClassBuilder& ObjCClassBuilder::addProtocol (Protocol* proto)
{
	auto res = class_addProtocol (cl, proto);
	assert (res == true);
	(void)res;
	return *this;
}

//------------------------------------------------------------------------------------
} // VSTGUI
