#pragma once

#include <stddef.h>
#include <stdbool.h>

#define ORB_RC_INIT ((orb_rc_t) { .code = 0, .is_fatal = false })
#define ORB_OK ORB_RC_INIT

#define ORB_MODULE(module) { \
  orb_module_run(module, DUC_OUT); \
}

#define ORB_MODULE_DEF(type, param_list) { \
  orb_module_t module = orb_module_create(&type, param_list); \
  ORB_MODULE(&module) \
}

typedef struct {
  int code;
  bool is_fatal;
} orb_rc_t;

typedef struct orb_module orb_module_t;

typedef struct {
  size_t size;
  orb_rc_t (*init)(orb_module_t *self);
  orb_rc_t (*fetch)(orb_module_t *self);
  orb_rc_t (*render)(orb_module_t *self, void *out);
  void (*cleanup)(orb_module_t *self);
} orb_module_type_t;

struct orb_module {
  const orb_module_type_t *type;
  void *param_list;
  void *data;
};

typedef struct {
  void *out;
  orb_module_t *module;
  orb_rc_t rc;
} orb_ctx_t;

orb_rc_t ORB_ERR(int code);
orb_rc_t ORB_FATAL(int code);
orb_ctx_t orb_ctx_create(void *out);
orb_module_t orb_module_create(const orb_module_type_t *type, void *param_list);
void orb_module_run(orb_module_t *self, orb_ctx_t *ctx);

#ifdef ORB_IMPL

#include <stdlib.h>

orb_rc_t ORB_ERR(int code) {
  return (orb_rc_t) { .code = code, .is_fatal = false };
}

orb_rc_t ORB_FATAL(int code) {
  return (orb_rc_t) { .code = code, .is_fatal = true };
}

orb_ctx_t orb_ctx_create(void *out) {
  return (orb_ctx_t) {
    .out = out,
    .module = NULL,
    .rc = ORB_RC_INIT,
  };
}

orb_module_t orb_module_create(const orb_module_type_t *type, void *param_list) {
  return (orb_module_t) {
    .type = type,
    .param_list = param_list,
    .data = NULL,
  };
}

static void _orb_module_init(orb_module_t *self) {
  if (self->type->size == 0) return;
  self->data = calloc(1, self->type->size);
}

static void _orb_module_cleanup(orb_module_t *self) {
  if (self->data) {
    free(self->data);
    self->data = NULL;
  }
}

void orb_module_run(orb_module_t *self, orb_ctx_t *ctx) {
  orb_ctx_t sub_ctx = {
    .out = ctx->out,
    .module = self,
    .rc = ORB_RC_INIT,
  };
  _orb_module_init(self);
  if (self->type->init) {
    sub_ctx.rc = self->type->init(self);
    if (sub_ctx.rc.is_fatal) goto cleanup;
  }
  if (self->type->fetch) {
    sub_ctx.rc = self->type->fetch(self);
    if (sub_ctx.rc.is_fatal) goto cleanup;
  }
  if (self->type->render) {
    orb_rc_t rc = self->type->render(self, &sub_ctx);
    if (rc.code != 0) sub_ctx.rc = rc;
    if (sub_ctx.rc.is_fatal) goto cleanup;
  }
  cleanup:
  if (self->type->cleanup) {
    self->type->cleanup(self);
  }
  _orb_module_cleanup(self);
  ctx->rc = sub_ctx.rc;
}

#endif // ORB_IMPL
