#ifndef SO_CTL_H
#define SO_CTL_H

#include <stdio.h>

typedef void (*ctl_write_fn_t)(const char *);

typedef struct {
  ctl_write_fn_t write;
} ctl_t;

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

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

#define __CTL_TEMPLDEF_MACRO($name, $params) \
  typedef struct { \
    $params \
  } $name##_t; \
  \
  typedef void (*$name##_fn_t)(ctl_t *, $name##_t *); \
  \
  int $name##_render(ctl_t *ctl, 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(ctl, ($name##_t *) ctx); \
    dlclose(handle); \
    return 0; \
  }

#define __CTL_TEMPLDEF_HEADER($name, $params) \
  __CTL_TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(ctl_t *ctl, $name##_t *ctx);

#define CTL_TEMPLDEF($name, $params) \
  __CTL_TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(ctl_t *ctl, $name##_t *ctx) {

#define CTL_ENDTEMPL }

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

#define CTL_WRITE($str) ctl->write($str);

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

#endif /* SO_CTL_H */
