{#
    Macros for binding things with pybind11
#}

{%- macro docv(value) -%}
  {%- for dq in value -%}
    {{ dq }}{% if loop.nextitem is defined %}{{ '\n' }}{% endif %}
  {%- endfor -%}
{%- endmacro -%}

{%- macro doc(o, pre, post) -%}
  {%- if o.doc %}{{ pre }}
    {{ docv(o.doc) }}{{ post }}
  {%- endif -%}
{%- endmacro -%}

{%- macro fndef(fn) %}
{% if fn.is_static_method %}def_static{% else %}def{% endif %}
{% endmacro -%}

{%- macro fnptr(cls_qualname, fn, trampoline_qualname, tmpl) -%}
  {%- if fn.cpp_code -%}
    {{ fn.cpp_code }}
  {%- elif not fn.genlambda -%}
    &
    {%- if trampoline_qualname -%}
      {{ trampoline_qualname }}::
    {%- elif cls_qualname -%}
      {{ cls_qualname }}::
    {%- else -%}
      {{ fn.namespace }}::
    {%- endif -%}
    {% if tmpl %}template {% endif %}{{ fn.cpp_name }}{{ tmpl }}
  {%- else -%}
    {%- set genlambda = fn.genlambda -%}
    [](
      {%- if cls_qualname -%}
        {{ cls_qualname }} * __that
        {%- if genlambda.in_params %},{% endif -%}
      {% endif -%}
      {{ genlambda.in_params | join(', ', attribute='decl') -}}
      ) {
          {{ genlambda.pre }}
          {{ genlambda.call_start }}
          {%- if trampoline_qualname -%}
            (({{ trampoline_qualname }}*)__that)->
          {%- elif cls_qualname -%}
            __that->
          {%- else -%}
            {{ fn.namespace }}::
          {%- endif -%}
          {{ fn.cpp_name }}{{ tmpl }}({{ fn.all_params | join(', ', attribute='call_name') }});
          {{ genlambda.ret }}
        }
  {%- endif -%}
{%- endmacro -%}

{%- macro gensig(cls_qualname, fn) -%}
  {#-
    py::overload_cast fails in some obscure cases, so we don't use it here
    https://github.com/pybind/pybind11/issues/1153
  -#}
  {{ fn.cpp_return_type }}(
  {%- if cls_qualname and not fn.is_static_method -%}
    {{ cls_qualname }}::
  {%- endif -%}
  *)(
      {{- fn.all_params | join(', ', attribute='full_cpp_type') -}}
  )
  {%- if fn.const %} const{% endif -%}
  {%- if fn.ref_qualifiers %} {{ fn.ref_qualifiers }}{% endif -%}
{%- endmacro -%}

{%- macro _genmethod(cls_qualname, fn, trampoline_qualname, tmpl) -%}
  {%- set ns = namespace(qualname=cls_qualname, arg_params=fn.filtered_params) -%}
  {%+ if fn.ifdef %}

  #ifdef {{ fn.ifdef }}
  {% endif %}
  {%+ if fn.ifndef %}

  #ifndef {{ fn.ifndef }}
  {% endif %}
  {%- if fn.operator -%}
        .def({{ fn.cpp_code }}
    {%- set ns.arg_params = [] -%}
  {%- elif fn.is_constructor -%}
    {%- if fn.cpp_code -%}
      .def(py::init({{ fn.cpp_code }})
    {%- elif trampoline_qualname -%}
      .def(py::init_alias<{{ ns.arg_params | join(', ', attribute='full_cpp_type') }}>()
    {%- else -%}
      .def(py::init<{{ ns.arg_params | join(', ', attribute='full_cpp_type') }}>()
    {%- endif -%}
  {%- else -%}
    .{{ fndef(fn) }}("{{ fn.py_name }}",{{ ' ' }}
      {%- if not fn.cpp_code and not fn.genlambda -%}
        {%- if trampoline_qualname -%}
          {%- set ns.qualname = trampoline_qualname -%}
          static_cast<{{ gensig(cls_qualname, fn) }}>(
        {%- endif -%}
        {%- if fn.is_overloaded -%}
          static_cast<{{ gensig(ns.qualname, fn) }}>(
        {% endif -%}
      {%- endif -%}
      {%- if fn.genlambda -%}{%- set ns.arg_params = fn.genlambda.in_params -%}{%- endif -%}
	      {{- fnptr(cls_qualname, fn, trampoline_qualname, tmpl) -}}
      {%- if not fn.cpp_code and not fn.genlambda -%}
        {%- if fn.is_overloaded -%}){%- endif -%}
        {%- if trampoline_qualname -%}){%- endif -%}
      {%- endif -%}
  {%- endif -%}

  {%- if ns.arg_params -%},
      {{ ns.arg_params | join(', ', attribute='py_arg') }}
  {%- endif -%}

  {%- if fn.release_gil -%}
    , release_gil()
  {%- endif -%}

  {%- for nurse, patient in fn.keepalives %}
    , py::keep_alive<{{ nurse }}, {{ patient }}>()
  {%- endfor -%}

  {{- fn.return_value_policy -}}

  {{ doc(fn, ', py::doc(', ')') }}
  )
  {%+ if fn.ifdef %}
  #endif // {{ fn.ifdef }}
  {% endif %}
  {%+ if fn.ifndef %}
  #endif // {{ fn.ifndef }}
  {% endif %}
{%- endmacro -%}

{%- macro genmethod(cls_qualname, fn, trampoline_qualname) -%}
  {%- if not fn.template_impls -%}
    {{ _genmethod(cls_qualname, fn, trampoline_qualname, "") }}
  {%- else -%}
    {%- for tmpl in fn.template_impls -%}
    {{ _genmethod(cls_qualname, fn, trampoline_qualname, "<" + (tmpl | join(", ")) + ">") }}
    {% endfor -%}
  {%- endif -%}
{%- endmacro %}

{%- macro genprop(qualname, prop) -%}
  {%- if prop.array_size -%}
    .def_property_readonly("{{ prop.py_name }}", []({{ qualname }}& inst) {
        return py::memoryview::from_buffer(
          &inst.{{ prop.cpp_name }}, sizeof({{ prop.cpp_type }}),
          py::format_descriptor<{{ prop.cpp_type }}>::value,
          {{ "{" }}{{ prop.array_size }}{{ "}" }}, {sizeof({{ prop.cpp_type }})},
          {% if prop.readonly %}true{% else %}false{% endif %}
        );
    }
    {{- doc(prop, ', py::doc(', ')') }})
  {%- elif prop.array -%}
    {# cannot sensibly autowrap an array of incomplete size #}
  {%- elif prop.reference or prop.bitfield -%}
    .def_property
    {%- if prop.readonly -%}
        _readonly
    {%- endif -%}
    ("{{ prop.py_name }}", {{ ' ' }}
        [](const {{ qualname }}& inst) -> {{ prop.cpp_type }} { return inst.{{ prop.cpp_name}}; }
        {%- if not prop.readonly %},
        []({{ qualname }}& inst, {{ prop.cpp_type }} v) {inst.{{ prop.cpp_name}} = v; }
        {%- endif -%}
        {{- doc(prop, ', py::doc(', ')') }}
    )

  {%- else -%}
    .def_
      {%- if prop.readonly -%}
        readonly
      {%- else -%}
        readwrite
      {%- endif -%}
      {%- if prop.static %}_static{% endif -%}
      ("{{ prop.py_name }}", &{{ qualname }}::{{ prop.cpp_name}}
      {{- doc(prop, ', py::doc(', ')') }})
  {%- endif -%}
{%- endmacro -%}

{%- macro enum_decl(enum) %}
  py::enum_<{{ enum.full_cpp_name }}>
{%- endmacro -%}

{%- macro enum_init(scope, enum) %}
  ({{ scope }}, "{{ enum.py_name }}"
  {{ doc(enum, ',', '') }}
  {%- if enum.arithmetic %}, py::arithmetic(){% endif %})
{%- endmacro -%}

{%- macro enum_def(scope, enum) %}
  {% for val in enum.values %}
    .value("{{ val.py_name }}", {{ val.full_cpp_name }}
    {%- if val.doc %},
      {% for dq in val.doc -%}
      {{ dq }}{% if loop.nextitem is defined %}{{ '\n' }}{% endif %}
      {%- endfor -%}
    {%- endif -%})
  {% endfor -%}
  {{ enum.inline_code or "" }};
{% endmacro -%}

{%- macro unnamed_enums(x, unnamed_enums) %}
{% for enum in unnamed_enums -%}
  {% for val in enum.values -%}
    {{ x }}.attr("{{ val.py_name }}") = (int){{ val.full_cpp_name }};
  {% endfor %}
{% endfor %}
{% endmacro -%}

{%- macro cls_user_using(cls) -%}
  {% for typealias in cls.user_typealias %}
    {{ typealias }};
  {% endfor %}
{% endmacro -%}

{%- macro cls_auto_using(cls) -%}
  {% for ccls in cls.child_classes if not ccls.template %}
  using {{ ccls.cpp_name }} [[maybe_unused]] = typename {{ ccls.full_cpp_name }};
  {% endfor %}
  {% for enum in cls.enums if enum.cpp_name %}
  using {{ enum.cpp_name }} [[maybe_unused]] = typename {{ enum.full_cpp_name }};
  {% endfor %}
  {% for typealias in cls.auto_typealias %}
    {{ typealias }};
  {% endfor %}
{% endmacro -%}

{%- macro cls_consts(cls) -%}
  {% for constant in cls.constants -%}
    static constexpr auto {{ constant[0] }} = {{ constant[1] }};
  {% endfor %}
{% endmacro -%}


{%- macro cls_decl(cls) -%}
  {%- if cls.trampoline %}{% set tctx = cls.trampoline %}
  using {{ tctx.var }} = {{ tctx.full_cpp_name }};
    static_assert(std::is_abstract<{{ tctx.var }}>::value == false, "{{ cls.full_cpp_name }} " RPYBUILD_BAD_TRAMPOLINE);
  {% endif -%}
  py::class_<typename {{ cls.full_cpp_name }}
    {%- if cls.nodelete -%}
      , std::unique_ptr<typename {{ cls.full_cpp_name }}, py::nodelete>
    {%- endif -%}
    {%- if cls.trampoline -%}
      , {{ cls.trampoline.var }}
    {%- endif -%}

    {%- if cls.bases -%}
      , {{ cls.bases | join(', ', attribute='full_cpp_name_w_templates') }}
    {%- endif -%}
    > {{ cls.var_name }};

    {% for enum in cls.enums %}
    {{ enum_decl(enum) }} {{ cls.var_name }}_enum{{ loop.index }};
    {% endfor %}

    {# recurse #}
    {% for ccls in cls.child_classes if not ccls.template %}
    {{ cls_decl(ccls) }}
    {% endfor -%}

{%- endmacro -%}

{%- macro cls_init(cls, name) -%}
    {{ cls.var_name }}({{ cls.scope_var }}, {{ name }}
    {%- if cls.final -%}
      , py::is_final()
    {%- endif -%}
    {%- if cls.force_multiple_inheritance -%}
      , py::multiple_inheritance()
    {%- endif -%}

    ),

  {% for enum in cls.enums %}
    {{ cls.var_name }}_enum{{ loop.index }}{{ enum_init(cls.var_name, enum) }},
  {% endfor %}

  {# recurse #}
  {% for ccls in cls.child_classes if not ccls.template %}
    {{ cls_init(ccls, '"' + ccls.py_name + '"') }}
  {% endfor -%}

{%- endmacro -%}

{%- macro cls_def_enum(cls, varname) %}
  {% for enum in cls.enums %}
    {{ cls.var_name }}_enum{{ loop.index }}{{ enum_def(cls.var_name, enum) }}
  {% endfor %}
{% endmacro -%}

{%- macro cls_def(cls, varname) -%}

  {% if cls.vcheck_fns -%}
  {%- for fn in cls.vcheck_fns %}
  {
    auto vcheck = {{ fn.cpp_code.strip() }};
    static_assert(std::is_convertible<decltype(vcheck), std::function<
    {{- fn.cpp_return_type }}({{ cls.full_cpp_name }}*
    {%- if fn.all_params %}, {{ fn.all_params | join(', ', attribute='full_cpp_type') }}{% endif -%}
      ) >>::value,
      "{{ cls.full_cpp_name }}::{{ fn.cpp_name }} must have virtual_xform if cpp_code signature doesn't match original function");
  }
  {% endfor -%}
  {%- endif %}

  {{ doc(cls, varname + '.doc() =', ';') }}

  {{ varname }}
  {% if cls.add_default_constructor %}
    .def(py::init<>(), release_gil())
  {% endif -%}
  {%- for fn in cls.wrapped_public_methods %}
    {{ genmethod(cls.full_cpp_name, fn, None) }}
  {% endfor -%}

  {%- if cls.trampoline -%}
  {%- for fn in cls.wrapped_protected_methods %}
    {{ genmethod(cls.full_cpp_name, fn, cls.trampoline.var ) }}
  {% endfor -%}
  {%- endif -%}

  {%- for prop in cls.public_properties %}
    {{ genprop(cls.full_cpp_name, prop) }}
  {% endfor -%}
  {%- for prop in cls.protected_properties %}
    {{ genprop(cls.trampoline.full_cpp_name, prop) }}
  {%- endfor %}{{ cls.inline_code }};

  {{ unnamed_enums(varname, cls.unnamed_enums) }}

  {#- recurse -#}
  {%- for ccls in cls.child_classes if not ccls.template %}
  {{ cls_def(ccls, ccls.var_name) }}
  {% endfor -%}

{%- endmacro -%}
