{#
    Macros for binding things with pybind11
#}

{%- macro fndef(cls_qualname, fn) %}
{% if cls_qualname and fn.static %}def_static{% else %}def{% endif %}
{% endmacro -%}

{%- macro fnptr(cls_qualname, fn, trampoline_qualname) -%}
  {%- if fn.data.cpp_code -%}
    {{ fn.data.cpp_code }}
  {%- elif not fn.x_genlambda -%}
    &
    {%- if trampoline_qualname -%}
      {{ trampoline_qualname }}::
    {%- elif cls_qualname -%}
      {{ cls_qualname }}::
    {%- else -%}
      {{ fn.namespace }}
    {%- endif -%}
    {{ fn.name }}
  {%- else -%}
    [](
      {%- if cls_qualname -%}
        {{ cls_qualname }} * __that
        {%- if fn.x_in_params %},{% endif -%}
      {% endif -%}
      {{ fn.x_in_params | join(', ', attribute='x_decl') -}}
      ) {
          {% for stmt in fn.x_lambda_pre %}
          {{ stmt }};
          {% endfor %}
          {{ fn.x_callstart }}
          {%- if trampoline_qualname -%}
            (({{ trampoline_qualname }}*)__that)->
          {%- elif cls_qualname -%}
            __that->
          {%- else -%}
            {{ fn.namespace }}
          {%- endif -%}
          {{ fn.name }}({{ fn.parameters | join(', ', attribute='x_callname') }} {{ fn.x_callend }});
          {% for stmt in fn.x_lambda_post %}
          {{ stmt }};
          {% endfor %}
          {{ fn.x_wrap_return }}
        }
  {%- 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.rtnType }} (
  {%- if cls_qualname and not fn.static -%}
    {{ cls_qualname }}::
  {%- endif -%}
  *)(
      {{- fn.x_in_params | join(', ', attribute='x_type_full') -}}
  )
  {%- if fn.const %} const{% endif -%}
{%- endmacro -%}

{%- macro genmethod(cls_qualname, fn, trampoline_qualname) -%}
  {%- set ns = namespace(qualname=cls_qualname, arg_params=fn.x_in_params) -%}
  {%+ if fn.data.ifdef %}

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

  #ifndef {{ fn.data.ifndef }}
  {% endif %}
  {%- if fn.operator -%}
    {%- if fn.data.cpp_code -%}
      {{ fn.data.cpp_code }}
    {%- else -%}
      {%- if ns.arg_params -%}
        .def(py::self {{ fn.operator }} {{ ns.arg_params[0].raw_type }}()
      {%- else -%}
        .def({{ fn.operator}} py::self
      {%- endif -%}
    {%- endif -%}
    {%- set ns.arg_params = [] -%}
  {%- elif fn.constructor -%}
    {%- set ns.arg_params = fn.x_all_params -%}
    {%- if fn.data.cpp_code -%}
      {{ fn.data.cpp_code }}
    {%- elif trampoline_qualname -%}
      .def(py::init([]({{ ns.arg_params | join(', ', attribute='x_decl') }}) {
        return std::make_unique<{{ trampoline_qualname }}>(
          {{- ns.arg_params | join(', ', attribute='x_callname') -}}
        );
      })
    {%- else -%}
      .def(py::init<{{ ns.arg_params | join(', ', attribute='x_type_full') }}>()
    {%- endif -%}
  {%- else -%}
    .{{ fndef(cls_qualname, fn) }}("{{ fn.x_name }}",{{ ' ' }}
      {%- if not fn.data.cpp_code and not fn.x_genlambda -%}
        
        {%- if trampoline_qualname -%}
          {%- set ns.qualname = trampoline_qualname -%}
          static_cast<{{ gensig(cls_qualname, fn) }}>(
        {%- endif -%}
        {%- if fn.x_overloaded -%}
          static_cast<{{ gensig(ns.qualname, fn) }}>(
        {% endif -%}
      {%- endif -%}
	      {{- fnptr(cls_qualname, fn, trampoline_qualname) -}}
      {%- if not fn.data.cpp_code and not fn.x_genlambda -%}
        {%- if fn.x_overloaded -%}){%- endif -%}
        {%- if trampoline_qualname -%}){%- endif -%}
      {%- endif -%}
  {%- endif -%}

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

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

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

  {{- fn.x_return_value_policy -}}

  {%- if fn.x_doc %},
    {% for dq in fn.x_doc_quoted %}
      {{ dq }}{% if loop.nextitem is defined %}{{ '\n' }}{% endif %}
    {%- endfor -%}
  {%- endif -%}
  )
  {%+ if fn.data.ifdef %}
  #endif // {{ fn.data.ifdef }}
  {% endif %}
  {%+ if fn.data.ifndef %}
  #endif // {{ fn.data.ifndef }}
  {% endif %}
{%- endmacro -%}

{%- macro genprop(qualname, prop) -%}
  {%- if prop.array_size is defined -%}
    .def_property_readonly("{{ prop.x_name }}", []({{ qualname }}& inst) {
        return py::memoryview(py::buffer_info(
          &inst.{{ prop.name }}, sizeof({{ prop.type }}),
          py::format_descriptor<{{ prop.type }}>::format(),
          1, {{ "{" }}{{ prop.array_size }}{{ "}" }}, {sizeof({{ prop.type }})}
        ));
    })
  {%- elif prop.array -%}
    .def_property("{{ prop.x_name }}",
      [](const {{ qualname }}& inst) { return inst.{{ prop.name}}; },
      []({{ qualname }}& inst, const {{ prop.type }} v) {inst.{{ prop.name}} = v; })
  {%- elif prop.reference -%}
    .def_property
    {%- if prop.constant or prop.constexpr or prop.x_readonly -%}
        _readonly
    {%- endif -%}
    ("{{ prop.x_name }}", {{ ' ' }}
        [](const {{ qualname }}& inst) -> {{ prop.type }} { return inst.{{ prop.name}}; }
        {%- if not (prop.constant or prop.constexpr or prop.x_readonly) %},
        []({{ qualname }}& inst, {{ prop.type }} v) {inst.{{ prop.name}} = v; }
        {%- endif -%}
    )

  {%- else -%}
    .def_
      {%- if prop.constant or prop.constexpr or prop.x_readonly -%}
        readonly
      {%- else -%}
        readwrite
      {%- endif -%}
      {%- if prop.static %}_static{% endif -%}
      ("{{ prop.x_name }}", &{{ qualname }}::{{ prop.name}})
  {%- endif -%}
{%- endmacro -%}

{%- macro genenum(scope, enum) %}
{%- if 'name' in enum -%}
py::enum_<{{ enum.x_namespace }}{{ enum.name }}>({{ scope }}, "{{ enum.x_name }}")
  {% for val in enum['values'] if not val.data.ignore %}
    .value("{{ val.x_name }}", {{ enum.x_namespace }}{{ enum.name }}::{{ val.name }})
  {% endfor -%}
  ;
{%- endif -%}
{% endmacro -%}

{%- macro unnamed_enum(x, enums) %}
{% for enum in enums if 'name' not in enum -%}
  {% for val in enum['values'] -%}
    {{ x }}.attr("{{ val.name }}") = (int){{ enum.x_namespace }}{{ val.name }};
  {% endfor %}
{% endfor %}
{% endmacro -%}

{%- macro cls_using(cls) -%}
  {% if cls.namespace and not cls.parent %}
    using namespace {{ cls.namespace }};
  {% endif %}
  {% if cls.parent %}
  using {{ cls.name }} = {{ cls.parent.name }}::{{ cls.name }};
  {% endif %}
  {% for typealias in cls.data.typealias -%}
    using {% if '=' in typealias %}{{ typealias }}{% else %}{{ typealias.split('::')[-1] }} = {{ typealias }}{% endif %};
  {% endfor %}
  {% for constant in cls.data.constants -%}
    static constexpr auto {{ constant.split('::')[-1] }} = {{ constant }};
  {% endfor %}
{% endmacro -%}

{%- macro cls_decl(cls, varname, name) -%}
  py::class_<{{ cls.x_qualname }}
    {%- if cls.data.nodelete -%}
      , std::unique_ptr<{{ cls.x_qualname }}, py::nodelete>
    {%- elif cls.data.shared_ptr -%}
      , std::shared_ptr<{{ cls.x_qualname }}>
    {%- endif -%}
    {%- if cls.x_has_trampoline -%}
      , {{ cls.x_trampoline_name }}
    {%- endif -%}

    {%- if cls.x_inherits -%}
      , {{ cls.x_inherits | join(', ', attribute='class') }}
    {%- endif -%}
    
    >
      {{ varname }}(

    {%- if cls.parent -%}
      {{ cls.parent.x_varname }}
    {%- else -%}
      {{ cls.x_module_var }}
    {%- endif -%}
    , {{ name }});
{%- endmacro -%}

{%- macro cls_methods(cls, varname) -%}
  {{ varname }}
  {# default constructor if not defined #}
  {% if not cls.x_has_constructor and not cls.data.nodelete and not cls.data.force_no_default_constructor %}
    .def(py::init<>(), release_gil())
  {% endif -%}
  {%- for fn in cls.methods.public if not fn.data.ignore %}
    {{ genmethod(cls.x_qualname, fn, None) }}
  {% endfor -%}

  {%- if cls.x_has_trampoline -%}
  {%- for fn in cls.methods.protected if not fn.data.ignore %}
    {{ genmethod(cls.x_qualname, fn, cls.x_trampoline_name) }}
  {% endfor -%}
  {%- endif -%}

  {%- for prop in cls.properties.public if not prop.data.ignore %}
    {{ genprop(cls.x_qualname, prop) }}
  {% endfor -%}
  {%- for prop in cls.properties.protected if not prop.data.ignore %}
    {{ genprop(cls.x_trampoline_name, prop) }}
  {%- endfor %};

  {{ unnamed_enum(varname, cls.enums.public) }}
{%- endmacro -%}
