#ifndef _SO_CTL_H
#define _SO_CTL_H

#include <stdio.h>
#include <stdlib.h>

typedef int (*ctl_render_fn_t)(void *, void *, const char *);

typedef struct {
  ctl_render_fn_t fn;
  void *data;
  const char *dl_path;
} ctl_templref_t;

#define __TEMPLDEF_MACRO($name, $params) \
  typedef struct { \
    $params \
  } $name##_t; \
  \
  typedef void (*$name##_fn_t)(void *, $name##_t *); \
  \
  int $name##_render(void *out, void *ctx, const char *dl_path) { \
    void *handle = dlopen(dl_path, RTLD_NOW); \
    if (!handle) { \
      fprintf(stderr, "%s\n", dlerror()); \
      return 1; \
    } \
    $name##_fn_t fn = ($name##_fn_t) dlsym(handle, "ctl_" #$name); \
    char *err = dlerror(); \
    if (err) { \
      fprintf(stderr, "%s\n", err); \
      dlclose(handle); \
      return 1; \
    } \
    fn(out, ($name##_t *) ctx); \
    dlclose(handle); \
    return 0; \
  }

#define __TEMPLDEF_HEADER($name, $params) \
  __TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(void *out, $name##_t *ctx);

#define TEMPLDEF($name, $params) \
  __TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(void *out, $name##_t *ctx) {

#define ENDTEMPL }

#define TEMPLBIND($name, $data, $dl_path) \
  (ctl_templref_t) { \
    .fn = $name##_render, \
    .data = $data, \
    .dl_path = $dl_path, \
  },

#define INCLUDE($name) ctx->$name.fn(ctl, ctx->$name.data, ctx->$name.dl_path);

#endif // _SO_CTL_H
