{#
    Generate trampoline classes to be used for two purposes:

    * Allow python programs to override virtual functions
    * Allow python programs access to protected members

    This trampoline is used from two different places:
    - To generate a trampoline usable by the class itself
    - Generate a trampoline usable by child classes

    Sometimes these are the same trampoline. The exception is when
    a 'final' method is in the base class, then a separate

    Each trampoline type is placed in a different namespace
    to make our life easier.

    Trampoline functions can be disabled via RPY_DISABLE_name_[type_type..]
#}

{%- macro precomma(v) -%}
  {%- if v %}, {{ v }}{%- endif -%}
{%- endmacro -%}

{%- macro postcomma(v) -%}
  {%- if v %}{{ v }}, {% endif -%}
{%- endmacro -%}

{# delete any finals #}
{% for fn in cls.methods.public + cls.methods.protected if fn.final %}
#define RPYGEN_DISABLE_{{ trampoline_signature(fn) }}
{% endfor %}

{# delete any final/overrides that are private #}
{% for fn in cls.methods.private if fn.final or fn.override %}
#define RPYGEN_DISABLE_{{ trampoline_signature(fn) }}
{% endfor %}

{# include override files for each base -- TODO: exclude some bases? #}
{% if cls.x_inherits %}
{% for base in cls.x_inherits %}
#include <rpygen/{{ base.x_qualname_ }}.hpp>
{% endfor %}
{% endif %}

namespace rpygen {

{% if cls.namespace %}
using namespace {{ cls.namespace }};
{% endif %}

{% for using in header.using.values() if using.using_type != "typealias" %}
using {{ using.raw_type }};
{% endfor %}

{#
    Each trampoline has a configuration struct.

    - Stores the base class that the trampoline is wrapping
    - Provides a mechanism to detect which base class to use when calling an
      overloaded virtual function (each class defines the overloads they have,
      and so if it's not defined in this config, then it falls back to the
      parent configuration)
#}
{% if cls.x_inherits %}
template <{{ postcomma(cls.x_pybase_params) }}typename CfgBase>
using PyTrampolineCfgBase_{{ cls.x_qualname_ }} =
{% for base in cls.x_inherits %}
    PyTrampolineCfg_{{ base.x_qualname_ }}<{{ postcomma(base.x_params) }}
{% endfor %}
CfgBase
{% for base in cls.x_inherits %}>{% endfor %};
{% endif %}

template <{{ postcomma(cls.x_template_parameter_list) }}typename CfgBase = EmptyTrampolineCfg>
struct PyTrampolineCfg_{{ cls.x_qualname_ }} :
{% if cls.x_inherits %}
    PyTrampolineCfgBase_{{ cls.x_qualname_ }}<{{ postcomma(cls.x_pybase_args) }} CfgBase>
{% else %}
    CfgBase
{% endif %}
{
    using Base = {{ cls.x_qualname }};

    {# specify base class to use for each virtual function #}
    {% for fn in cls.methods.public + cls.methods.protected
           if not fn.data.ignore and (fn.virtual or fn.override) and not fn.final and not fn.data.buffers %}
    using override_base_{{ trampoline_signature(fn) }} = {{ cls.x_qualname }};
    {% endfor %}
};


{% if cls.x_inherits %}
{# 
    To avoid multiple inheritance here, we define a single base with bases that
    are all template bases.. 

    PyTrampolineBase is another trampoline or our base class
#}
template <typename PyTrampolineBase{{ precomma(cls.x_pybase_params) }}, typename PyTrampolineCfg>
using PyTrampolineBase_{{ cls.x_qualname_ }} =
{% for base in cls.x_inherits %}
    PyTrampoline_{{ base.x_qualname_ }}<
{% endfor %}
        PyTrampolineBase
{% for base in (cls.x_inherits | reverse) %}
        {{ precomma(base.x_params) }}
        , PyTrampolineCfg
    >
{% endfor %}
;

template <typename PyTrampolineBase{{ precomma(cls.x_template_parameter_list) }}, typename PyTrampolineCfg>
struct PyTrampoline_{{ cls.x_qualname_ }} : PyTrampolineBase_{{ cls.x_qualname_ }}<PyTrampolineBase{{ precomma(cls.x_pybase_args) }}, PyTrampolineCfg> {
    using PyTrampolineBase_{{ cls.x_qualname_ }}<PyTrampolineBase{{ precomma(cls.x_pybase_args) }}, PyTrampolineCfg>::PyTrampolineBase_{{ cls.x_qualname_ }};
{% else %}
template <typename PyTrampolineBase{{ precomma(cls.x_template_parameter_list) }}, typename PyTrampolineCfg>
struct PyTrampoline_{{ cls.x_qualname_ }} : PyTrampolineBase, virtual py::trampoline_self_life_support {
    using PyTrampolineBase::PyTrampolineBase;
{% endif %}

{% for typealias in cls.x_user_typealias %}
    {{ typealias }};
{% endfor %}
{% for typealias in cls.x_auto_typealias %}
    {{ typealias }};
{% endfor %}
{% for constant in cls.data.constants %}
    static constexpr auto {{ constant.split('::')[-1] }} = {{ constant }};
{% endfor %}

{# protected constructors -- only used by the direct child #}
{% for fn in cls.methods.protected if not fn.data.ignore and fn.constructor %}
#ifdef RPYGEN_ENABLE_{{ cls.x_qualname_ }}_PROTECTED_CONSTRUCTORS
    PyTrampoline_{{ cls.x_qualname_ }}({{ fn.parameters | join(', ', attribute='x_decl') }}) :
        {% if cls.x_inherits -%}
             PyTrampolineBase_{{ cls.x_qualname_ }}<PyTrampolineBase{{ precomma(cls.x_pybase_args) }}, PyTrampolineCfg>
        {%- else -%}
            PyTrampolineBase
        {%- endif -%}
        ({{ fn.parameters | join(', ', attribute='x_callname') }})
    {}
#endif
{% endfor %}

    {# virtual methods (disabled for buffer overrides for now) #}
    {% for fn in cls.methods.public + cls.methods.protected
           if not fn.data.ignore and (fn.virtual or fn.override) and not fn.final and not fn.data.buffers %}
#ifndef RPYGEN_DISABLE_{{ trampoline_signature(fn) }}
    {{ fn.rtnType }} {{ fn.name }}({{ fn.parameters | join(', ', attribute='x_decl') }}){%
        if fn.const %} const{% endif
    %}{{ fn.ref_qualifiers }} override {
    {% if fn.data.trampoline_cpp_code %}
    {{ fn.data.trampoline_cpp_code }}
    {% else %}
    {# TODO: probably will break for things like out parameters, etc #}
    {% if fn.data.ignore_pure %}
        throw std::runtime_error("not implemented");
    {% else %}
    {% if fn.data.virtual_xform %}
        auto custom_fn = {{ fn.data.virtual_xform }};
    {% endif %}
        using LookupBase = typename PyTrampolineCfg::Base;
    {#
        We define a "LookupBase" and "CallBase" here because to find the python
        override we need to use the actual class currently being overridden, but
        to make the actual call we might need to use a base class.

        .. lots of duplication here, but it's worse without it
    #}
    {% if fn.pure_virtual and fn.data.virtual_xform %}
        RPYBUILD_OVERRIDE_PURE_CUSTOM_NAME({{ cls.name }}, PYBIND11_TYPE({{ fn.rtnType }}), LookupBase,
            "{{ fn.x_name }}", {{ fn.name }}, {{ fn.parameters | join(', ', attribute='name') }});
    {% elif fn.pure_virtual %}
        RPYBUILD_OVERRIDE_PURE_NAME({{ cls.name }}, PYBIND11_TYPE({{ fn.rtnType }}), LookupBase,
            "{{ fn.x_name }}", {{ fn.name }}, {{ fn.parameters | join(', ', attribute='name') }});
    {% elif fn.data.virtual_xform %}
        using CxxCallBase = typename PyTrampolineCfg::override_base_{{ trampoline_signature(fn) }};
        RPYBUILD_OVERRIDE_CUSTOM_IMPL(PYBIND11_TYPE({{ fn.rtnType }}), LookupBase,
            "{{ fn.x_name }}", {{ fn.name }}, {{ fn.parameters | join(', ', attribute='name') }});
        return CxxCallBase::{{ fn.name }}({{ fn.parameters | join(', ', attribute='x_virtual_callname') }});
    {% else %}
        using CxxCallBase = typename PyTrampolineCfg::override_base_{{ trampoline_signature(fn) }};
        PYBIND11_OVERRIDE_IMPL(PYBIND11_TYPE({{ fn.rtnType }}), LookupBase,
            "{{ fn.x_name }}", {{ fn.parameters | join(', ', attribute='name') }});
        return CxxCallBase::{{ fn.name }}({{ fn.parameters | join(', ', attribute='x_virtual_callname') }});
    {% endif %}
    {% endif %}
    {% endif %}
    }
#endif

    {% endfor %}

    {# non-virtual protected methods/attributes #}
    {% for fn in cls.methods.protected
           if not fn.data.ignore and not (fn.virtual or fn.override or fn.constructor) %}
#ifndef RPYBLD_DISABLE_{{ trampoline_signature(fn) }}
{# hack to ensure we don't do 'using' twice' in the same class, while
   also ensuring that the overrides can be selectively disabled by
   child trampoline functions #}
  #ifndef RPYBLD_UDISABLE_{{ using_signature(fn) }}
    using {{ cls.x_qualname }}::{{ fn.name }};
    #define RPYBLD_UDISABLE_{{ using_signature(fn) }}
  #endif
#endif
    {% endfor %}

    {% for prop in cls.properties.protected if not prop.data.ignore %}
    using {{ cls.x_qualname }}::{{ prop.name }};
    {% endfor %}

    {% if cls.data.trampoline_inline_code %}
    {{ cls.data.trampoline_inline_code }}
    {% endif %}
};

}; // namespace rpygen
