{% include "cls_prologue.cpp.j2" %}

{% import "pybind11.cpp.j2" as pybind11 %}

{% if templates %}
#include "{{ mod_fn }}_tmpl.hpp"
{% endif %}

{% for inc in data.extra_includes %}
#include <{{ inc }}>
{% endfor %}

{%- for typealias in user_typealias %}
{{ typealias }};
{% endfor %}

{#
  Ordering of the initialization function

  - namespace/typealiases
  - global enums
  - templates (because CRTP)
  - class declarations
  - class enums
  - class methods
  - global methods

  Additionally, we use two-part initialization to ensure that documentation
  strings are generated properly. First part is to register the class with
  pybind11, second part is to generate all the methods/etc for it.

#}

{% for cls in header.classes
   if cls.namespace and not cls.parent and not cls.data.ignore and cls.template is not defined %}
  using namespace {{ cls.namespace }};
{% endfor %}

{% for cls in header.classes if cls.data.nodelete %}
PYBIND11_TYPE_CASTER_BASE_HOLDER(typename {{ cls.x_qualname }}, std::unique_ptr<typename {{ cls.x_qualname }}, py::nodelete>);
{% endfor %}


struct rpybuild_{{ mod_fn }}_initializer {

{% for cls in header.classes if not cls.data.ignore and cls.template is not defined %}
  {{ pybind11.cls_user_using(cls) }}
  {{ pybind11.cls_consts(cls) }}
{% endfor %}

{% for pkg, vname in subpackages.items() %}
  py::module {{ vname }};
{% endfor %}

{# enums #}
{% for enum in header.enums if "name" in enum and not enum.data.ignore %}
  {{ pybind11.enum_decl(enum.x_module_var, enum) }} enum{{ loop.index }};
{% endfor %}

{# template decls #}
{% for name, tmpl_data in templates.items() %}
  rpygen::bind_{{ tmpl_data.x_qualname_ }}_{{ loop.index }} tmplCls{{ loop.index }};
{% endfor %}

{# class decls #}
{%- for cls in header.classes
     if not cls.parent and not cls.data.ignore and cls.template is not defined %}
  {{ pybind11.cls_decl(cls) }}
{% endfor %}

  py::module &m;

  {# register classes with pybind11 #}
  rpybuild_{{ mod_fn }}_initializer(py::module &m) :

  {% for pkg, vname in subpackages.items() %}
    {{ vname }}(m.def_submodule("{{ pkg }}")),
  {% endfor %}

  {% for enum in header.enums if "name" in enum and not enum.data.ignore %}
    enum{{ loop.index }}{{ pybind11.enum_init(enum.x_module_var, enum) }},
  {% endfor %}

  {% for name, tmpl_data in templates.items() %}
    tmplCls{{ loop.index }}({{ tmpl_data.x_module_var }}, "{{ name }}"),
  {% endfor %}

  {% for cls in header.classes
     if not cls.parent and not cls.data.ignore and cls.template is not defined %}
    {{ pybind11.cls_init(cls, '"' + cls.x_name + '"') }}
  {% endfor %}

    m(m)
  {
    {#
      enums can go in the initializer because they cant have dependencies,
      and then we dont need to figure out class dependencies for enum arguments
    #}
    {% for enum in header.enums if "name" in enum and not enum.data.ignore %}
      enum{{ loop.index }}{{ pybind11.enum_def(enum.x_module_var, enum) }}
    {% endfor %}

    {% for cls in header.classes if not cls.data.ignore %}
    {{ pybind11.cls_def_enum(cls, cls.x_varname) }}
    {% endfor %}
  }

void finish() {

{# templates #}
{% for tdata in templates.values() %}
  tmplCls{{ loop.index }}.finish(
    {% if tdata.x_doc_set %}{{ pybind11.docv(tdata.x_doc_set) }}{% else %}NULL{% endif %},
    {% if tdata.x_doc_add %}{{ pybind11.docv(tdata.x_doc_add) }}{% else %}NULL{% endif %}
  );
{% endfor %}

{# class methods #}
{%- for cls in header.classes
   if not cls.parent and not cls.data.ignore and cls.template is not defined %}
  {
  {{ pybind11.cls_auto_using(cls) }}

  {{ pybind11.cls_def(cls, cls.x_varname) }}
  }
{% endfor %}

{# global methods #}
{%- if header.functions %}
  {% for fn in header.functions if not fn.data.ignore and not fn.data.ignore_py -%}
    {{ fn.x_module_var }}{{ pybind11.genmethod(None, fn, None) }};
  {% endfor %};
{% endif %}

{% if data.inline_code %}

  {{ data.inline_code }}
{% endif %}
}

}; // struct rpybuild_{{ mod_fn }}_initializer

static std::unique_ptr<rpybuild_{{ mod_fn }}_initializer> cls;

void begin_init_{{ mod_fn }}(py::module &m) {
  cls = std::make_unique<rpybuild_{{ mod_fn }}_initializer>(m);
}

void finish_init_{{ mod_fn }}() {
  cls->finish();
  cls.reset();
}