/*
 * 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 buf.h
#pragma once

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>

#ifndef BUF_CAP_BASE
#define BUF_CAP_BASE 64
#endif

#define BUF_NULL (buf_t) { NULL, 0, 0, 0 }

typedef struct {
  uint8_t *bytes;
  size_t len;
  size_t cap;
  size_t pos;
} buf_t;

void buf_init(buf_t *self, void *bytes, size_t len);
void buf_free(buf_t *self);
bool buf_write_len(buf_t *self, const void *bytes, size_t len);
bool buf_write(buf_t *self, const void *bytes);
void buf_writef(buf_t *self, const char *fmt, ...);
bool buf_read(buf_t *self, void *bytes, size_t len);
void buf_concat(buf_t *self, buf_t buf);
void buf_repall_len(buf_t *self, const char *pat, const char *rep, size_t len);
void buf_repall(buf_t *self, const char *pat, const char *rep);

#ifdef BUF_IMPL

#include <string.h>

void buf_init(buf_t *self, void *bytes, size_t len) {
  self->bytes = (uint8_t *)bytes;
  self->len = len;
  self->cap = len;
  self->pos = 0;
}

void buf_free(buf_t *self) {
  if (!self || !self->bytes) return;
  free(self->bytes);
  *self = BUF_NULL;
}

bool buf_write_len(buf_t *self, const void *bytes, size_t len) {
  if (!self || !bytes) return false;
  size_t needed = self->len + len;
  if (needed > self->cap) {
    size_t new_cap = self->cap ? self->cap * 2 : BUF_CAP_BASE;
    while (needed > new_cap) new_cap *= 2;
    uint8_t *tmp = (uint8_t *) realloc(self->bytes, new_cap);
    if (!tmp) return false;
    self->bytes = tmp;
    self->cap = new_cap;
  }
  memcpy(self->bytes + self->len, bytes, len);
  self->len += len;
  return true;
}

bool buf_write(buf_t *self, const void *bytes) {
  if (!bytes) return false;
  return buf_write_len(self, bytes, strlen(bytes));
}

void buf_writef(buf_t *self, const char *fmt, ...) {
  if (!self || !fmt) return;
  va_list args;
  va_start(args, fmt);
  ssize_t data_len = vsnprintf(NULL, 0, fmt, args);
  va_end(args);
  if (data_len < 0) return;
  char *data = malloc(data_len + 1);
  if (!data) return;
  va_start(args, fmt);
  vsnprintf(data, data_len + 1, fmt, args);
  va_end(args);
  buf_write(self, data);
  free(data);
}

bool buf_read(buf_t *self, void *bytes, size_t len) {
  if (!self || !bytes || self->pos + len > self->len) return false;
  memcpy(bytes, self->bytes + self->pos, len);
  self->pos += len;
  return true;
}

void buf_concat(buf_t *self, buf_t buf) {
  buf_write_len(self, buf.bytes, buf.len);
}

void buf_repall_len(buf_t *self, const char *pat, const char *rep, size_t len) {
  if (!self || !self->bytes || !pat || !rep) return;
  const char *bytes = (const char *) self->bytes;
  size_t pat_len = strlen(pat);
  size_t rep_len = strlen(rep);
  if (pat_len == 0) return;
  buf_t out = BUF_NULL;
  size_t idx = 0;
  while (idx < len && *bytes) {
    size_t rem = len - idx;
    if (rem >= pat_len && strncmp(bytes, pat, pat_len) == 0) {
      buf_write_len(&out, rep, rep_len);
      bytes += pat_len;
      idx += pat_len;
    }
    else {
      buf_write_len(&out, bytes, 1);
      bytes++;
      idx++;
    }
  }
  buf_free(self);
  buf_write_len(self, out.bytes, out.len);
  buf_free(&out);
}

void buf_repall(buf_t *self, const char *pat, const char *rep) {
  buf_repall_len(self, pat, rep, self->len);
}

#endif /* BUF_IMPL */
