// 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 ctl.h

#ifndef _SO_CTL_H
#define _SO_CTL_H

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

// @type ctl_render_fn_t
typedef int (*ctl_render_fn_t)(void *, void *, const char *);

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

// @macro __TEMPLDEF_MACRO
// @internal
#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) { \
    char *path = getenv("CTL_BUILDPATH"); \
    if (path == NULL) { \
      path = "./"; \
    } \
    char dl_path[strlen(path) + strlen(__CTL_UID_##$name) + 4]; \
    snprintf(dl_path, sizeof(dl_path), "%s%s.so", path, __CTL_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, "__CTL_SIG_HASH_" #$name); \
    if (__CTL_SIG_HASH_##$name != *hash) { \
      fprintf(stderr, "Invalid signature hash! (%llx != %llx)\n", __CTL_SIG_HASH_##$name, *hash); \
      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; \
  }

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

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

// @macro ENDTEMPL
#define ENDTEMPL }

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

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

#endif // _SO_CTL_H
