/// Fixed-size strings with value semantics.
#pragma once

#include <assert.h>
#include <bsd/string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

/// A fixed-size string.
/// The string is null-terminated so that it can be used with the usual C APIs.
//
// TODO: The asserts on length should be hard asserts, not just asserts in debug
// builds.
#define DEF_STRING(STRING, SIZE)                                          \
  typedef struct STRING {                                                 \
    size_t length;                                                        \
    char   str[SIZE];                                                     \
  } STRING;                                                               \
                                                                          \
  static const size_t STRING##_size = SIZE;                               \
                                                                          \
  static inline const char* STRING##_cstr(const STRING* str) {            \
    return str->str;                                                      \
  }                                                                       \
                                                                          \
  static inline size_t STRING##_length(STRING str) { return str.length; } \
                                                                          \
  static inline STRING STRING##_make(const char* cstr) {                  \
    if (!cstr) {                                                          \
      return (STRING){0};                                                 \
    } else {                                                              \
      STRING str = (STRING){0};                                           \
      str.length = strlcpy(str.str, cstr, SIZE);                          \
      return str;                                                         \
    }                                                                     \
  }                                                                       \
                                                                          \
  static inline STRING STRING##_dirname(STRING path) {                    \
    STRING str = path;                                                    \
    for (int i = str.length - 1; i >= 0; --i) {                           \
      if (str.str[i] == '/' || str.str[i] == '\\') {                      \
        str.str[i] = 0;                                                   \
        str.length = i;                                                   \
        return str;                                                       \
      } else {                                                            \
        str.str[i] = 0;                                                   \
      }                                                                   \
    }                                                                     \
    str        = (STRING){0};                                             \
    str.str[0] = '.';                                                     \
    str.length = 1;                                                       \
    return str;                                                           \
  }                                                                       \
                                                                          \
  static inline void STRING##_append_cstr(STRING* a, const char* b) {     \
    size_t b_length = strlen(b);                                          \
    assert(a->length + b_length + 1 < SIZE);                              \
    strlcpy(a->str + a->length, b, SIZE);                                 \
    a->length = a->length + b_length;                                     \
  }                                                                       \
                                                                          \
  static inline void STRING##_append(STRING* a, STRING b) {               \
    assert(a->length + b.length + 1 < SIZE);                              \
    strlcpy(a->str + a->length, b.str, SIZE);                             \
    a->length = a->length + b.length;                                     \
  }                                                                       \
                                                                          \
  static inline STRING STRING##_concat(STRING a, STRING b) {              \
    assert(a.length + b.length + 1 < SIZE);                               \
    STRING str = {0};                                                     \
    strlcpy(str.str, a.str, SIZE);                                        \
    strlcpy(str.str + a.length, b.str, SIZE);                             \
    str.length = a.length + b.length;                                     \
    return str;                                                           \
  }                                                                       \
                                                                          \
  static inline STRING STRING##_concat_cstr(STRING a, const char* b) {    \
    return STRING##_concat(a, STRING##_make(b));                          \
  }                                                                       \
                                                                          \
  static inline STRING STRING##_concat_path(STRING a, STRING b) {         \
    return STRING##_concat(STRING##_concat(a, STRING##_make("/")), b);    \
  }                                                                       \
                                                                          \
  static inline bool STRING##_eq(STRING a, STRING b) {                    \
    if (a.length != b.length) {                                           \
      return false;                                                       \
    }                                                                     \
    return strncmp(a.str, b.str, a.length) == 0;                          \
  }                                                                       \
                                                                          \
  static inline bool STRING##_eq_cstr(STRING a, const char* b) {          \
    return (a.length == strlen(b)) && strncmp(a.str, b, a.length) == 0;   \
  }                                                                       \
                                                                          \
  static inline STRING STRING##_itoa(int n) {                             \
    STRING    str     = (STRING){0};                                      \
    const int written = snprintf(str.str, SIZE, "%d", n);                 \
    assert(written >= 0);                                                 \
    str.length = (size_t)written;                                         \
    return str;                                                           \
  }                                                                       \
                                                                          \
  static inline uint64_t STRING##_hash(STRING str) {                      \
    return cstring_hash(str.str);                                         \
  }

/// Return a hash of the given string.
static inline uint64_t cstring_hash(const char* str) {
  uint64_t hash = 0;
  for (size_t i = 0; i < strlen(str); ++i) {
    hash = (uint64_t)str[i] + (hash << 6) + (hash << 16) - hash;
  }
  return hash;
}

DEF_STRING(sstring, 32)    // Small.
DEF_STRING(mstring, 256)   // Medium.
DEF_STRING(lstring, 1024)  // Large.
DEF_STRING(xlstring, 4096) // Extra large.