// Copyright (C) 2026 Sophie Sitzler
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

// @file ductape.h

#ifndef _SO_DUCTAPE_H
#define _SO_DUCTAPE_H

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

// @type duc_render_fn_t
typedef int (*duc_render_fn_t)(void *, void *);

// @struct duc_templref_t
typedef struct {
  duc_render_fn_t fn;
  void *data;
  const char *dl_path;
} duc_templref_t;

// @macro __DUC_TEMPLDEF_MACRO
// @internal
#define __DUC_TEMPLDEF_MACRO($name, $params) \
  typedef struct { \
    $params \
  } $name##_t; \
  \
  typedef void (*$name##_fn_t)(void *, $name##_t *); \
  \
  int $name##_render(void *out, void *ctx) { \
    char *path = getenv("DUC_BUILDPATH"); \
    if (path == NULL) { \
      path = "./"; \
    } \
    char dl_path[strlen(path) + strlen(__DUC_UID_##$name) + 4]; \
    snprintf(dl_path, sizeof(dl_path), "%s%s.so", path, __DUC_UID_##$name); \
    void *handle = dlopen(dl_path, RTLD_NOW); \
    if (!handle) { \
      fprintf(stderr, "%s\n", dlerror()); \
      return 1; \
    } \
    const size_t *hash = (size_t *) dlsym(handle, "__DUC_SIG_HASH_" #$name); \
    if (__DUC_SIG_HASH_##$name != *hash) { \
      fprintf(stderr, "Invalid signature hash! (%llx != %llx)\n", __DUC_SIG_HASH_##$name, *hash); \
      return 1; \
    } \
    $name##_fn_t fn = ($name##_fn_t) dlsym(handle, "duc_" #$name); \
    char *err = dlerror(); \
    if (err) { \
      fprintf(stderr, "%s\n", err); \
      dlclose(handle); \
      return 1; \
    } \
    fn(out, ($name##_t *) ctx); \
    dlclose(handle); \
    return 0; \
  }

// @macro __DUC_TEMPLDEF_HEADER
// @internal
#define __DUC_TEMPLDEF_HEADER($name, $params) \
  __DUC_TEMPLDEF_MACRO($name, $params) \
  \
  void duc_##$name(void *out, $name##_t *ctx);

// @macro DUC_TEMPLDEF
#define DUC_TEMPLDEF($name, $params) \
  __DUC_TEMPLDEF_MACRO($name, $params) \
  \
  void duc_##$name(void *out, $name##_t *ctx) {

// @macro DUC_ENDTEMPL
#define DUC_ENDTEMPL }

// @macro DUC_TEMPLBIND
#define DUC_TEMPLBIND($name, $data, $dl_path) \
  (duc_templref_t) { \
    .fn = $name##_render, \
    .data = $data, \
    .dl_path = $dl_path, \
  },

// @macro DUC_INCLUDE
#define DUC_INCLUDE($name) ctx->$name.fn(duc, ctx->$name.data, ctx->$name.dl_path);

#endif // _SO_DUC_H
