#ifndef _STRVIEW_H
#define _STRVIEW_H

// NOMENCLATURE:
// - Chop: Modifies the original view and returns the chopped part.
// - Trim: Operations that chop whitespace from the left, right, or both sides of the view.
// - Slice: Returns a new view that is a slice of the original view, without modifying the original view.
//
// As a general rule of thumb, functions that take the view as a pointer will modify it
// while functions that take the view by value will not modify it.

#ifndef STRVIEW_DEF
#define STRVIEW_DEF static
#endif

#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>

// SAFETY: The user must ensure that the data pointer lives for the lifetime of the strview.
typedef struct {
  const char* data;
  size_t len;
} strview;

typedef bool (*sv_predicate)(char);

#define svlit(cstrlit) ((strview){ (cstrlit), sizeof((cstrlit)) - 1 })

#define svarg(sv) (int)(sv).len, (sv).data
#define svfmt "%.*s"

// SAFETY: The caller must ensure that the cstr is null-terminated.
STRVIEW_DEF strview sv_from_cstr(const char* cstr);
// SAFETY: The caller must ensure that the data pointer is valid for len bytes.
STRVIEW_DEF strview sv_from_data(const char* data, size_t len);
STRVIEW_DEF strview sv_chop_left(strview* sv, size_t n);
STRVIEW_DEF strview sv_chop_right(strview* sv, size_t n);
STRVIEW_DEF strview sv_slice(strview sv, size_t left, size_t right);
STRVIEW_DEF strview sv_chop_left_while(strview* sv, sv_predicate predicate);
STRVIEW_DEF strview sv_chop_right_while(strview* sv, sv_predicate predicate);
STRVIEW_DEF strview sv_trim_left(strview* sv);
STRVIEW_DEF strview sv_trim_right(strview* sv);
STRVIEW_DEF strview sv_trim(strview* sv);
// OWNERSHIP: The caller is responsible for freeing the returned string.
STRVIEW_DEF char*   sv_to_cstr(strview sv);
STRVIEW_DEF strview sv_chop_by_char(strview* sv, char delimiter);
STRVIEW_DEF strview sv_chop_by_predicate(strview* sv, sv_predicate predicate);
STRVIEW_DEF strview sv_chop_by_strview(strview* sv, strview delimiter);
STRVIEW_DEF bool    sv_starts_with(strview sv, strview prefix);
STRVIEW_DEF bool    sv_ends_with(strview sv, strview suffix);
STRVIEW_DEF size_t  sv_find(strview sv, strview substr, bool* found);
STRVIEW_DEF size_t  sv_rfind(strview sv, strview substr, bool* found);
STRVIEW_DEF bool    sv_contains(strview sv, strview substr);
STRVIEW_DEF size_t  sv_find_char(strview sv, char c, bool* found);
STRVIEW_DEF size_t  sv_rfind_char(strview sv, char c, bool* found);
STRVIEW_DEF bool    sv_contains_char(strview sv, char c);
STRVIEW_DEF int     sv_compare(strview sv1, strview sv2);
STRVIEW_DEF bool    sv_equal(strview sv1, strview sv2);
STRVIEW_DEF bool    sv_is_all_space(strview sv);
STRVIEW_DEF bool    sv_empty(strview sv);
STRVIEW_DEF char    sv_first(strview sv);
STRVIEW_DEF char    sv_last(strview sv);

STRVIEW_DEF bool sv_space_predicate(char c);
STRVIEW_DEF bool sv_alpha_predicate(char c);
STRVIEW_DEF bool sv_numeric_predicate(char c);
STRVIEW_DEF bool sv_alphanumeric_predicate(char c);
STRVIEW_DEF bool sv_hex_predicate(char c);
STRVIEW_DEF bool sv_identifier_predicate(char c);

// Aliases for common naming conventions
#define sv_split           sv_chop_by_char
#define sv_split_char      sv_chop_by_char
#define sv_split_predicate sv_chop_by_predicate
#define sv_split_strview   sv_chop_by_strview
#define sv_front           sv_first
#define sv_back            sv_last

#ifdef STRVIEW_IMPLEMENTATION

#include <string.h>

STRVIEW_DEF strview sv_from_data(const char* data, size_t len) {
  return (strview){ data, len };
}

STRVIEW_DEF strview sv_from_cstr(const char* cstr) {
  size_t len = 0;
  while (cstr[len] != '\0') {
    len++;
  }
  return sv_from_data(cstr, len);
}

// If the requested chop length is greater than the view length, the entire view is returned and the original view is emptied.
STRVIEW_DEF strview sv_chop_left(strview* sv, size_t n) {
  if (n > sv->len) {
    n = sv->len;
  }
  strview result = { sv->data, n };
  sv->data += n;
  sv->len -= n;
  return result;
}

// If the requested chop length is greater than the view length, the entire view is returned and the original view is emptied.
STRVIEW_DEF strview sv_chop_right(strview* sv, size_t n) {
  if (n > sv->len) {
    n = sv->len;
  }
  strview result = { sv->data + sv->len - n, n };
  sv->len -= n;
  return result;
}

// The caller must ensure that left + right <= sv.len.
// If the requested slice is out of bounds, an empty view is returned.
STRVIEW_DEF strview sv_slice(strview sv, size_t left, size_t right) {
  if (left + right > sv.len) {
    // If the requested slice is out of bounds, return an empty view.
    return sv_from_data(sv.data, 0);
  }
  return sv_from_data(sv.data + left, sv.len - left - right);
}

STRVIEW_DEF strview sv_chop_left_while(strview* sv, sv_predicate predicate) {
  size_t n = 0;
  while (n < sv->len && predicate(sv->data[n])) {
    n++;
  }
  return sv_chop_left(sv, n);
}

STRVIEW_DEF strview sv_chop_right_while(strview* sv, sv_predicate predicate) {
  size_t n = 0;
  while (n < sv->len && predicate(sv->data[sv->len - 1 - n])) {
    n++;
  }
  return sv_chop_right(sv, n);
}

STRVIEW_DEF bool sv_space_predicate(char c) {
  return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v';
}
STRVIEW_DEF bool sv_alpha_predicate(char c) {
  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
STRVIEW_DEF bool sv_numeric_predicate(char c) {
  return c >= '0' && c <= '9';
}
STRVIEW_DEF bool sv_alphanumeric_predicate(char c) {
  return sv_alpha_predicate(c) || sv_numeric_predicate(c);
}
STRVIEW_DEF bool sv_hex_predicate(char c) {
  return sv_numeric_predicate(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
STRVIEW_DEF bool sv_identifier_predicate(char c) {
  return sv_alphanumeric_predicate(c) || c == '_';
}

STRVIEW_DEF strview sv_trim_left(strview* sv) {
  return sv_chop_left_while(sv, sv_space_predicate);
}

STRVIEW_DEF strview sv_trim_right(strview* sv) {
  return sv_chop_right_while(sv, sv_space_predicate);
}

STRVIEW_DEF strview sv_trim(strview* sv) {
  strview left = sv_trim_left(sv);
  strview right = sv_trim_right(sv);
  return sv_from_data(left.data, left.len + right.len);
}

STRVIEW_DEF char* sv_to_cstr(strview sv) {
  char* cstr = (char*)malloc(sv.len + 1);
  if (cstr == NULL) {
    return NULL;
  }
  for (size_t i = 0; i < sv.len; i++) {
    cstr[i] = sv.data[i];
  }
  cstr[sv.len] = '\0';
  return cstr;
}

STRVIEW_DEF strview sv_chop_by_char(strview* sv, char delimiter) {
  size_t i = 0;
  while (i < sv->len && sv->data[i] != delimiter) {
    i++;
  }
  strview result = sv_from_data(sv->data, i);
  if (i < sv->len) {
    sv->data += i + 1; // Skip the delimiter
    sv->len -= i + 1;
  } else {
    sv->data += i;
    sv->len -= i;
  }
  return result;
}

STRVIEW_DEF strview sv_chop_by_predicate(strview* sv, sv_predicate predicate) {
  size_t i = 0;
  while (i < sv->len && !predicate(sv->data[i])) {
    i++;
  }
  strview result = sv_from_data(sv->data, i);
  if (i < sv->len) {
    sv->data += i + 1; // Skip the delimiter
    sv->len -= i + 1;
  } else {
    sv->data += i;
    sv->len -= i;
  }
  return result;
}

STRVIEW_DEF strview sv_chop_by_strview(strview* sv, strview delimiter) {
  size_t i = 0;
  while (i + delimiter.len <= sv->len) {
    if (sv->data[i] == delimiter.data[0] && strncmp(sv->data + i, delimiter.data, delimiter.len) == 0) {
      break;
    }
    i++;
  }
  strview result = sv_from_data(sv->data, i);
  if (i + delimiter.len <= sv->len) {
    sv->data += i + delimiter.len; // Skip the delimiter
    sv->len -= i + delimiter.len;
  } else {
    sv->data += i;
    sv->len -= i;
  }
  return result;
}

STRVIEW_DEF bool sv_starts_with(strview sv, strview prefix) {
  if (prefix.len > sv.len) {
    return false;
  }
  return strncmp(sv.data, prefix.data, prefix.len) == 0;
}

STRVIEW_DEF bool sv_ends_with(strview sv, strview suffix) {
  if (suffix.len > sv.len) {
    return false;
  }
  return strncmp(sv.data + sv.len - suffix.len, suffix.data, suffix.len) == 0;
}

STRVIEW_DEF size_t sv_find(strview sv, strview substr, bool* found) {
  if (substr.len > sv.len) {
    *found = false;
    return 0;
  }
  for (size_t i = 0; i <= sv.len - substr.len; i++) {
    if (strncmp(sv.data + i, substr.data, substr.len) == 0) {
      *found = true;
      return i;
    }
  }

  *found = false;
  return 0;
}

STRVIEW_DEF size_t sv_rfind(strview sv, strview substr, bool* found) {
  if (substr.len > sv.len) {
    *found = false;
    return 0;
  }
  for (size_t i = sv.len - substr.len + 1; i-- > 0;) {
    if (strncmp(sv.data + i, substr.data, substr.len) == 0) {
      *found = true;
      return i;
    }
  }

  *found = false;
  return 0;
}

STRVIEW_DEF bool sv_contains(strview sv, strview substr) {
  bool found;
  sv_find(sv, substr, &found);
  return found;
}

STRVIEW_DEF size_t sv_find_char(strview sv, char c, bool* found) {
  for (size_t i = 0; i < sv.len; i++) {
    if (sv.data[i] == c) {
      *found = true;
      return i;
    }
  }
  *found = false;
  return 0;
}

STRVIEW_DEF size_t sv_rfind_char(strview sv, char c, bool* found) {
  for (size_t i = sv.len; i-- > 0;) {
    if (sv.data[i] == c) {
      *found = true;
      return i;
    }
  }
  *found = false;
  return 0;
}

STRVIEW_DEF bool sv_contains_char(strview sv, char c) {
  bool found;
  sv_find_char(sv, c, &found);
  return found;
}

STRVIEW_DEF int sv_compare(strview sv1, strview sv2) {
  size_t min_len = sv1.len < sv2.len ? sv1.len : sv2.len;
  int cmp = strncmp(sv1.data, sv2.data, min_len);
  if (cmp != 0) {
    return cmp;
  }
  if (sv1.len < sv2.len) {
    return -1;
  } else if (sv1.len > sv2.len) {
    return 1;
  } else {
    return 0;
  }
}

STRVIEW_DEF bool sv_equal(strview sv1, strview sv2) {
  return sv_compare(sv1, sv2) == 0;
}

STRVIEW_DEF bool sv_is_all_space(strview sv) {
  for (size_t i = 0; i < sv.len; i++) {
    if (!sv_space_predicate(sv.data[i])) {
      return false;
    }
  }
  return true;
}

STRVIEW_DEF bool sv_empty(strview sv) {
  return sv.len == 0;
}

STRVIEW_DEF char sv_first(strview sv) {
  if (sv.len == 0) {
    return '\0'; // Return null character for empty view
  }
  return sv.data[0];
}

STRVIEW_DEF char sv_last(strview sv) {
  if (sv.len == 0) {
    return '\0'; // Return null character for empty view
  }
  return sv.data[sv.len - 1];
}

#endif // STRVIEW_IMPLEMENTATION

#endif // _STRVIEW_H