/**
Internal module for pushing and getting variants (from $(STDMODULE _variant)).
Currently, only $(STDREF _variant,Algebraic) is supported.
*/
module luad.conversions.variant;

import luad.c.all;

import luad.stack;
import luad.base;
import std.traits;
import std.variant;
import std.typetuple;

void pushVariant(T)(lua_State* L, ref T value) if(isVariant!T)
{
	if(!value.hasValue())
	{
		lua_pushnil(L);
		return;
	}

	foreach(Type; value.AllowedTypes)
	{
		if(auto v = value.peek!Type)
		{
			pushValue(L, *v);
			return;
		}
	}

	assert(false);
}

T getVariant(T)(lua_State* L, int idx) if (isVariant!T)
{
	auto t = lua_type(L, idx);

	foreach(Type; T.AllowedTypes)
		if(t == luaTypeOf!Type)
			return T(getValue!Type(L, idx));

	assert(false); // TODO: runtime error
}

bool isAllowedType(T)(lua_State* L, int idx) {
	auto t = lua_type(L, idx);

	foreach(Type; T.AllowedTypes)
		if(t == luaTypeOf!Type)
			return true;

	return false;
}

template isVariant(T)
{
	enum isVariant = is(typeof(isVariantImpl(T.init)));
	static if(isVariant)
		static assert(
			T.AllowedTypes.length > 0,
			"Variant is not supported - use an instance of VariantN with an explicit AllowedTypes list, such as Algebraic"
		);
}

private void isVariantImpl(size_t max, AllowedTypes...)(const VariantN!(max, AllowedTypes) v){}

version(unittest) import luad.testing;

unittest
{
	lua_State* L = luaL_newstate();
	scope(success) lua_close(L);
	luaL_openlibs(L);

	version(none)
	{
		Variant v = 123;
		pushValue(L, v);
		assert(popValue!int(L) == 123);
	}

	alias Algebraic!(real, string, bool) BasicLuaType;

	BasicLuaType v = "test";
	pushValue(L, v);
	assert(lua_isstring(L, -1));
	assert(getValue!string(L, -1) == "test");
	assert(popValue!BasicLuaType(L) == "test");

	v = 2.3L;
	pushValue(L, v);
	assert(lua_isnumber(L, -1));
	lua_setglobal(L, "num");

	unittest_lua(L, `
		assert(num == 2.3)
	`);

	v = true;
	pushValue(L, v);
	assert(lua_isboolean(L, -1));
	assert(popValue!bool(L));

	struct S
	{
		int i;
		double n;
		string s;

		void f(){}
	}
	pushValue(L, Algebraic!(S, int)(S(1, 2.3, "hello")));
	assert(lua_istable(L, -1));
	lua_setglobal(L, "struct");

	unittest_lua(L, `
		for key, expected in pairs{i = 1, n = 2.3, s = "hello"} do
			local value = struct[key]
			assert(
				value == expected,
				("bad table pair: '%s' = '%s' (expected '%s')"):format(key, value, expected)
			)
		end
	`);
}

