#ifndef SLIBS_H
#define SLIBS_H

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Miscellaneous

#define sl_auto(name, x) typeof(x) name = x

#define sl_new(type, ...)             \
  ({                                  \
    type *ptr = malloc(sizeof(type)); \
    *ptr = (type){__VA_ARGS__};       \
    ptr;                              \
  })

#define sl_init(type, ...) \
  (type) { __VA_ARGS__ }

#define sl_array_len(arr) sizeof(arr) / sizeof(arr[0])

#define sl_fmt_spec(arg) \
  _Generic((arg),        \
      int8_t: "%d",      \
      int16_t: "%d",     \
      int32_t: "%d",     \
      int64_t: "%lld",   \
      uint8_t: "%u",     \
      uint16_t: "%u",    \
      uint32_t: "%lu",   \
      uint64_t: "%llu",  \
      double: "%lf",     \
      float: "%f",       \
      char: "%c",        \
      char *: "%s",      \
      void *: "%p",      \
      default: "Unknown")

#define sl_stringify(x) #x

// Vector

#define sl_vec(type) \
  struct             \
  {                  \
    type *data;      \
    size_t size;     \
    size_t capacity; \
  }

#define sl_vec_grow(vec)                                                   \
  {                                                                        \
    (vec).capacity = (vec).capacity * 2 + 1;                               \
    void *ptr = realloc((vec).data, (vec).capacity * sizeof(*(vec).data)); \
    if (ptr)                                                               \
      (vec).data = ptr;                                                    \
  }

#define sl_vec_push(vec, element)         \
  {                                       \
    if ((vec).size >= (vec).capacity)     \
      sl_vec_grow(vec);                   \
    (vec).data[(vec).size++] = (element); \
  }

#define sl_vec_shift(vec, element)                                         \
  {                                                                        \
    if ((vec).size >= (vec).capacity)                                      \
      sl_vec_grow(vec);                                                    \
    memmove((vec).data + 1, (vec).data, (vec).size * sizeof(*(vec).data)); \
    (vec).data[0] = (element);                                             \
    (vec).size++;                                                          \
  }

#define sl_vec_pop(vec) \
  {                     \
    if ((vec).size > 0) \
    {                   \
      (vec).size--;     \
    }                   \
  }

#define sl_vec_at(vec, index) ((vec).data[index])

#define sl_vec_size(vec) ((vec).size)

#define sl_vec_capacity(vec) ((vec).capacity)

#define sl_vec_free(vec) free((vec).data)

#define sl_vec_begin(vec) (vec).data

#define sl_vec_end(vec) ((vec).data + (vec).size)

#define sl_vec_it(name, vec)          \
  sl_auto((name), sl_vec_begin(vec)); \
  (name) != sl_vec_end(vec);          \
  ++(name)

// String

typedef sl_vec(char) sl_string;

#define sl_string(c_str)                         \
  ({                                             \
    sl_string str = {0};                         \
    for (size_t i = 0; i < strlen((c_str)); i++) \
      sl_vec_push(str, (c_str)[i]);              \
    str;                                         \
  })

#define sl_tostring(val)                                        \
  ({                                                            \
    sl_auto(len, snprintf(NULL, 0, sl_fmt_spec(val), val) + 1); \
    sl_auto(buf, (char *)malloc(len));                          \
    snprintf(buf, len, sl_fmt_spec(val), val);                  \
    sl_auto(str, sl_string(buf));                               \
    free(buf);                                                  \
    str;                                                        \
  })

#define sl_str_free(str) sl_vec_free(str)

#define sl_c_str(str)         \
  ({                          \
    sl_vec_push((str), '\0'); \
    (str).size--;             \
    (str).data;               \
  })

void sl_append_c_str(sl_string* sl_str, const char* c_str);

#ifdef SL_IMPLEMENTATION
void sl_append_c_str(sl_string* sl_str, const char* c_str) {
    for(int i = 0; i < strlen(c_str); i++) {
        sl_vec_push(*sl_str, c_str[i]);
    }
}
#endif

// Pointers

#define sl_ptr(type) \
  struct             \
  {                  \
    type *ptr;       \
    int ref_count;   \
  }

#define sl_ptr_make(raw_ptr) \
  {                          \
    raw_ptr, 1               \
  }

#define sl_ptr_release(smart_ptr) \
  ({                              \
    smart_ptr.ref_count--;        \
    if (smart_ptr.ref_count <= 0) \
    {                             \
      free(smart_ptr.ptr);        \
    }                             \
  })

#define sl_ptr_get(smart_ptr, raw_ptr_name, scope)                        \
  ({                                                                      \
    assert(smart_ptr.ref_count > 0 && "Smart pointer already released!"); \
    sl_auto(raw_ptr_name, smart_ptr.ptr);                                 \
    smart_ptr.ref_count++;                                                \
    scope;                                                                \
    sl_ptr_release(smart_ptr);                                            \
  });

#define sl_ptr_scope(smart_ptr, scope) \
  ({                                   \
    scope;                             \
    sl_ptr_release(smart_ptr);         \
  });

void sl_read_file(const char *filename, sl_string *buffer);

#ifdef SL_IMPLEMENTATION
void sl_read_file(const char *filename, sl_string *buffer)
{
  FILE *file = fopen(filename, "r");
  if (!file)
  {
    fprintf(stderr, "Error: could not open file %s\n", filename);
    exit(1);
  }

  fseek(file, 0, SEEK_END);
  size_t file_size = ftell(file);
  fseek(file, 0, SEEK_SET);

  for (size_t i = 0; i < file_size; i++)
  {
    sl_vec_push(*buffer, fgetc(file));
  }

  fclose(file);
}
#endif

#endif // SLIBS_H