# -*- coding: ibm850 -*-

template_typed = """
#ifdef TYPED_METHOD_BIND
template<class T $ifret ,class R$ $ifargs ,$ $arg, class P@$>
class MethodBind$argc$$ifret R$$ifconst C$ : public MethodBind {
public:

	$ifret R$ $ifnoret void$ (T::*method)($arg, P@$) $ifconst const$;
	virtual Variant::Type _gen_argument_type(int p_arg) const { return _get_argument_type(p_arg); }
#ifdef DEBUG_METHODS_ENABLED
	virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const {
		$ifret if (p_arg==-1) return GetTypeInfo<R>::METADATA;$
		$arg if (p_arg==(@-1)) return GetTypeInfo<P@>::METADATA;
		$
		return GodotTypeInfo::METADATA_NONE;
	}
#endif
	Variant::Type _get_argument_type(int p_argument) const {
		$ifret if (p_argument==-1) return (Variant::Type)GetTypeInfo<R>::VARIANT_TYPE;$
		$arg if (p_argument==(@-1)) return (Variant::Type)GetTypeInfo<P@>::VARIANT_TYPE;
		$
		return Variant::NIL;
	}
	virtual PropertyInfo _gen_argument_type_info(int p_argument) const {
		$ifret if (p_argument==-1) return GetTypeInfo<R>::get_class_info();$
		$arg if (p_argument==(@-1)) return GetTypeInfo<P@>::get_class_info();
		$
		return PropertyInfo();
	}
	virtual String get_instance_class() const {
		return T::get_class_static();
	}

	virtual Variant call(Object* p_object,const Variant** p_args,int p_arg_count, Variant::CallError& r_error) {

		T *instance=Object::cast_to<T>(p_object);
		r_error.error=Variant::CallError::CALL_OK;
#ifdef DEBUG_METHODS_ENABLED

		ERR_FAIL_COND_V(!instance,Variant());
		if (p_arg_count>get_argument_count()) {
			r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
			r_error.argument=get_argument_count();
			return Variant();

		}
		if (p_arg_count<(get_argument_count()-get_default_argument_count())) {

			r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
			r_error.argument=get_argument_count()-get_default_argument_count();
			return Variant();
		}
		$arg CHECK_ARG(@);
		$
#endif
		$ifret Variant ret = $(instance->*method)($arg, _VC(@)$);
		$ifret return Variant(ret);$
		$ifnoret return Variant();$
	}

#ifdef PTRCALL_ENABLED
	virtual void ptrcall(Object*p_object,const void** p_args,void *r_ret) {

		T *instance=Object::cast_to<T>(p_object);
		$ifret PtrToArg<R>::encode( $ (instance->*method)($arg, PtrToArg<P@>::convert(p_args[@-1])$) $ifret ,r_ret)$ ;
	}
#endif
	MethodBind$argc$$ifret R$$ifconst C$ () {
#ifdef DEBUG_METHODS_ENABLED
		_set_const($ifconst true$$ifnoconst false$);
#endif
		_generate_argument_types($argc$);

		$ifret _set_returns(true); $
	};
};

template<class T $ifret ,class R$ $ifargs ,$ $arg, class P@$>
MethodBind* create_method_bind($ifret R$ $ifnoret void$ (T::*p_method)($arg, P@$) $ifconst const$ ) {

	MethodBind$argc$$ifret R$$ifconst C$<T $ifret ,R$ $ifargs ,$ $arg, P@$> * a = memnew( (MethodBind$argc$$ifret R$$ifconst C$<T $ifret ,R$ $ifargs ,$ $arg, P@$>) );
	a->method=p_method;
	return a;
}
#endif
"""

template = """#pragma once

#ifndef TYPED_METHOD_BIND
$iftempl template<$ $ifret class R$ $ifretargs ,$ $arg, class P@$ $iftempl >$
class MethodBind$argc$$ifret R$$ifconst C$ : public MethodBind {

public:

	StringName type_name;
	$ifret R$ $ifnoret void$ (__UnexistingClass::*method)($arg, P@$) $ifconst const$;

	virtual Variant::Type _gen_argument_type(int p_arg) const { return _get_argument_type(p_arg); }
#ifdef DEBUG_METHODS_ENABLED
	virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const {
		$ifret if (p_arg==-1) return GetTypeInfo<R>::METADATA;$
		$arg if (p_arg==(@-1)) return GetTypeInfo<P@>::METADATA;
		$
		return GodotTypeInfo::METADATA_NONE;
	}
#endif

	Variant::Type _get_argument_type(int p_argument) const {
		$ifret if (p_argument==-1) return (Variant::Type)GetTypeInfo<R>::VARIANT_TYPE;$
		$arg if (p_argument==(@-1)) return (Variant::Type)GetTypeInfo<P@>::VARIANT_TYPE;
		$
		return Variant::NIL;
	}

	virtual PropertyInfo _gen_argument_type_info(int p_argument) const {
		$ifret if (p_argument==-1) return GetTypeInfo<R>::get_class_info();$
		$arg if (p_argument==(@-1)) return GetTypeInfo<P@>::get_class_info();
		$
		return PropertyInfo();
	}

	virtual String get_instance_class() const {
		return type_name;
	}

	virtual Variant call(Object* p_object,const Variant** p_args,int p_arg_count, Variant::CallError& r_error) {

		__UnexistingClass *instance = (__UnexistingClass*)p_object;

		r_error.error=Variant::CallError::CALL_OK;
#ifdef DEBUG_METHODS_ENABLED

		ERR_FAIL_COND_V(!instance,Variant());
		if (p_arg_count>get_argument_count()) {
			r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
			r_error.argument=get_argument_count();
			return Variant();
		}

		if (p_arg_count<(get_argument_count()-get_default_argument_count())) {

			r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
			r_error.argument=get_argument_count()-get_default_argument_count();
			return Variant();
		}

		$arg CHECK_ARG(@);
		$
#endif
		$ifret Variant ret = $(instance->*method)($arg, _VC(@)$);
		$ifret return Variant(ret);$
		$ifnoret return Variant();$
	}
#ifdef PTRCALL_ENABLED
	virtual void ptrcall(Object*p_object,const void** p_args,void *r_ret) {
		__UnexistingClass *instance = (__UnexistingClass*)p_object;
		$ifret PtrToArg<R>::encode( $ (instance->*method)($arg, PtrToArg<P@>::convert(p_args[@-1])$) $ifret ,r_ret) $ ;
	}
#endif
	MethodBind$argc$$ifret R$$ifconst C$ () {
#ifdef DEBUG_METHODS_ENABLED
		_set_const($ifconst true$$ifnoconst false$);
#endif
		_generate_argument_types($argc$);
		$ifret _set_returns(true); $


	};
};

template<class T $ifret ,class R$ $ifargs ,$ $arg, class P@$>
MethodBind* create_method_bind($ifret R$ $ifnoret void$ (T::*p_method)($arg, P@$) $ifconst const$ ) {

	MethodBind$argc$$ifret R$$ifconst C$ $iftempl <$  $ifret R$ $ifretargs ,$ $arg, P@$ $iftempl >$ * a = memnew( (MethodBind$argc$$ifret R$$ifconst C$ $iftempl <$ $ifret R$ $ifretargs ,$ $arg, P@$ $iftempl >$) );
	union {

		$ifret R$ $ifnoret void$ (T::*sm)($arg, P@$) $ifconst const$;
		$ifret R$ $ifnoret void$ (__UnexistingClass::*dm)($arg, P@$) $ifconst const$;
	} u;
	u.sm=p_method;
	a->method=u.dm;
	a->type_name=T::get_class_static();
	return a;
}
#endif
"""


template_typed_free_func = """
#ifdef TYPED_METHOD_BIND
template<class T $ifret ,class R$ $ifargs ,$ $arg, class P@$>
class FunctionBind$argc$$ifret R$$ifconst C$ : public MethodBind {
public:

	$ifret R$ $ifnoret void$ (*method) ($ifconst const$ T *$ifargs , $$arg, P@$);
	virtual Variant::Type _gen_argument_type(int p_arg) const { return _get_argument_type(p_arg); }
	virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const {
		$ifret if (p_arg==-1) return GetTypeInfo<R>::METADATA;$
		$arg if (p_arg==(@-1)) return GetTypeInfo<P@>::METADATA;
		$
		return GodotTypeInfo::METADATA_NONE;
	}
	Variant::Type _get_argument_type(int p_argument) const {
		$ifret if (p_argument==-1) return (Variant::Type)GetTypeInfo<R>::VARIANT_TYPE;$
		$arg if (p_argument==(@-1)) return (Variant::Type)GetTypeInfo<P@>::VARIANT_TYPE;
		$
		return Variant::NIL;
	}
	virtual PropertyInfo _gen_argument_type_info(int p_argument) const {
		$ifret if (p_argument==-1) return GetTypeInfo<R>::get_class_info();$
		$arg if (p_argument==(@-1)) return GetTypeInfo<P@>::get_class_info();
		$
		return PropertyInfo();
	}
	virtual String get_instance_class() const {
		return T::get_class_static();
	}

	virtual Variant call(Object* p_object,const Variant** p_args,int p_arg_count, Variant::CallError& r_error) {

		T *instance=Object::cast_to<T>(p_object);
		r_error.error=Variant::CallError::CALL_OK;
#ifdef DEBUG_METHODS_ENABLED

		ERR_FAIL_COND_V(!instance,Variant());
		if (p_arg_count>get_argument_count()) {
			r_error.error=Variant::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS;
			r_error.argument=get_argument_count();
			return Variant();

		}
		if (p_arg_count<(get_argument_count()-get_default_argument_count())) {

			r_error.error=Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
			r_error.argument=get_argument_count()-get_default_argument_count();
			return Variant();
		}
		$arg CHECK_ARG(@);
		$
#endif
		$ifret Variant ret = $(method)(instance$ifargs , $$arg, _VC(@)$);
		$ifret return Variant(ret);$
		$ifnoret return Variant();$
	}

#ifdef PTRCALL_ENABLED
	virtual void ptrcall(Object*p_object,const void** p_args,void *r_ret) {

		T *instance=Object::cast_to<T>(p_object);
		$ifret PtrToArg<R>::encode( $ (method)(instance$ifargs , $$arg, PtrToArg<P@>::convert(p_args[@-1])$) $ifret ,r_ret)$ ;
	}
#endif
	FunctionBind$argc$$ifret R$$ifconst C$ () {
#ifdef DEBUG_METHODS_ENABLED
		_set_const($ifconst true$$ifnoconst false$);
#endif
		_generate_argument_types($argc$);

		$ifret _set_returns(true); $
	};
};

template<class T $ifret ,class R$ $ifargs ,$ $arg, class P@$>
MethodBind* create_method_bind($ifret R$ $ifnoret void$ (*p_method)($ifconst const$ T *$ifargs , $$arg, P@$) ) {

	FunctionBind$argc$$ifret R$$ifconst C$<T $ifret ,R$ $ifargs ,$ $arg, P@$> * a = memnew( (FunctionBind$argc$$ifret R$$ifconst C$<T $ifret ,R$ $ifargs ,$ $arg, P@$>) );
	a->method=p_method;
	return a;
}
#endif
"""


def make_version(template, nargs, argmax, const, ret):

    intext = template
    from_pos = 0
    outtext = ""

    while True:
        to_pos = intext.find("$", from_pos)
        if to_pos == -1:
            outtext += intext[from_pos:]
            break
        else:
            outtext += intext[from_pos:to_pos]
        end = intext.find("$", to_pos + 1)
        if end == -1:
            break  # ignore
        macro = intext[to_pos + 1 : end]
        cmd = ""
        data = ""

        if macro.find(" ") != -1:
            cmd = macro[0 : macro.find(" ")]
            data = macro[macro.find(" ") + 1 :]
        else:
            cmd = macro

        if cmd == "argc":
            outtext += str(nargs)
        if cmd == "ifret" and ret:
            outtext += data
        if cmd == "ifargs" and nargs:
            outtext += data
        if cmd == "ifretargs" and nargs and ret:
            outtext += data
        if cmd == "ifconst" and const:
            outtext += data
        elif cmd == "ifnoconst" and not const:
            outtext += data
        elif cmd == "ifnoret" and not ret:
            outtext += data
        elif cmd == "iftempl" and (nargs > 0 or ret):
            outtext += data
        elif cmd == "arg,":
            for i in range(1, nargs + 1):
                if i > 1:
                    outtext += ", "
                outtext += data.replace("@", str(i))
        elif cmd == "arg":
            for i in range(1, nargs + 1):
                outtext += data.replace("@", str(i))
        elif cmd == "noarg":
            for i in range(nargs + 1, argmax + 1):
                outtext += data.replace("@", str(i))

        from_pos = end + 1

    return outtext


def run(target, source, env):

    versions = 13
    versions_ext = 6
    text = ""
    text_ext = ""
    text_free_func = "#ifndef METHOD_BIND_FREE_FUNC_H\n#define METHOD_BIND_FREE_FUNC_H\n"
    text_free_func += "\n//including this header file allows method binding to use free functions\n"
    text_free_func += (
        "//note that the free function must have a pointer to an instance of the class as its first parameter\n"
    )

    for i in range(0, versions + 1):

        t = ""
        t += make_version(template, i, versions, False, False)
        t += make_version(template_typed, i, versions, False, False)
        t += make_version(template, i, versions, False, True)
        t += make_version(template_typed, i, versions, False, True)
        t += make_version(template, i, versions, True, False)
        t += make_version(template_typed, i, versions, True, False)
        t += make_version(template, i, versions, True, True)
        t += make_version(template_typed, i, versions, True, True)
        if i >= versions_ext:
            text_ext += t
        else:
            text += t

        text_free_func += make_version(template_typed_free_func, i, versions, False, False)
        text_free_func += make_version(template_typed_free_func, i, versions, False, True)
        text_free_func += make_version(template_typed_free_func, i, versions, True, False)
        text_free_func += make_version(template_typed_free_func, i, versions, True, True)

    text_free_func += "#endif"

    with open(target[0], "w") as f:
        f.write(text)

    with open(target[1], "w") as f:
        f.write(text_ext)

    with open(target[2], "w") as f:
        f.write(text_free_func)


if __name__ == "__main__":
    from platform_methods import subprocess_main

    subprocess_main(globals())
