#ifndef _SO_CTL_H
#define _SO_CTL_H

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

typedef struct ctl_t ctl_t;

struct ctl_t {
  void *out;
};

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 __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 __TEMPLDEF_HEADER($name, $params) \
  __TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(ctl_t *ctl, $name##_t *ctx);

#define TEMPLDEF($name, $params) \
  __TEMPLDEF_MACRO($name, $params) \
  \
  void ctl_##$name(ctl_t *ctl, $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);

ctl_t *ctl_init(void *out);
void ctl_cleanup(ctl_t *self);

#ifdef SO_CTL_IMPL

ctl_t *ctl_init(void *out) {
  ctl_t *self = (ctl_t *) malloc(sizeof(ctl_t));
  *self = (ctl_t) {
    .out = out,
  };
  return self;
}

void ctl_cleanup(ctl_t *self) {
  if (!self) return;
  free(self);
}

#endif // SO_CTL_IMPL
#endif // _SO_CTL_H
