/* MIT License
 *
 * Copyright (c) 2025 Tyge Løvset
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// zsview is a zero-terminated string view.

#ifndef STC_ZSVIEW_H_INCLUDED
#define STC_ZSVIEW_H_INCLUDED

#include "common.h"
#include "types.h"
#include "priv/utf8_prv.h"

#define             zsview_init() c_zv("")
#define             zsview_clone(zs) c_default_clone(zs)
#define             zsview_drop(self) c_default_drop(self)
#define             zsview_toraw(self) (self)->str

STC_INLINE zsview   zsview_from(const char* str)
                        { return c_literal(zsview){str, c_strlen(str)}; }
STC_INLINE void     zsview_clear(zsview* self) { *self = c_zv(""); }
STC_INLINE csview   zsview_sv(zsview zs) { return c_sv_2(zs.str, zs.size); }

STC_INLINE isize    zsview_size(zsview zs) { return zs.size; }
STC_INLINE bool     zsview_is_empty(zsview zs) { return zs.size == 0; }

STC_INLINE bool zsview_equals(zsview zs, const char* str) {
    isize n = c_strlen(str);
    return zs.size == n && !c_memcmp(zs.str, str, n);
}

STC_INLINE isize zsview_find(zsview zs, const char* search) {
    const char* res = strstr(zs.str, search);
    return res ? (res - zs.str) : c_NPOS;
}

STC_INLINE bool zsview_contains(zsview zs, const char* str)
    { return zsview_find(zs, str) != c_NPOS; }

STC_INLINE bool zsview_starts_with(zsview zs, const char* str) {
    isize n = c_strlen(str);
    return n <= zs.size && !c_memcmp(zs.str, str, n);
}

STC_INLINE bool zsview_ends_with(zsview zs, const char* str) {
    isize n = c_strlen(str);
    return n <= zs.size && !c_memcmp(zs.str + zs.size - n, str, n);
}

STC_INLINE zsview zsview_from_pos(zsview zs, isize pos) {
    if (pos > zs.size) pos = zs.size;
    zs.str += pos; zs.size -= pos; return zs;
}

STC_INLINE csview zsview_subview(zsview zs, isize pos, isize len) {
    c_assert(((size_t)pos <= (size_t)zs.size) & (len >= 0));
    if (pos + len > zs.size) len = zs.size - pos;
    return c_literal(csview){zs.str + pos, len};
}

STC_INLINE zsview zsview_tail(zsview zs, isize len) {
    c_assert(len >= 0);
    if (len > zs.size) len = zs.size;
    zs.str += zs.size - len; zs.size = len;
    return zs;
}

/* utf8 */

STC_INLINE zsview zsview_u8_from_pos(zsview zs, isize u8pos)
    { return zsview_from_pos(zs, utf8_to_index(zs.str, u8pos)); }

STC_INLINE zsview zsview_u8_tail(zsview zs, isize u8len) {
    const char* p = &zs.str[zs.size];
    while (u8len && p != zs.str)
        u8len -= (*--p & 0xC0) != 0x80;
    zs.size -= p - zs.str, zs.str = p;
    return zs;
}

STC_INLINE csview zsview_u8_subview(zsview zs, isize u8pos, isize u8len)
    { return utf8_subview(zs.str, u8pos, u8len); }

STC_INLINE zsview_iter zsview_u8_at(zsview zs, isize u8pos) {
    csview sv;
    sv.buf = utf8_at(zs.str, u8pos);
    sv.size = utf8_chr_size(sv.buf);
    return c_literal(zsview_iter){.chr = sv};
}

STC_INLINE isize zsview_u8_size(zsview zs)
    { return utf8_count(zs.str); }

STC_INLINE bool zsview_u8_valid(zsview zs) // requires linking with utf8 symbols
    { return utf8_valid_n(zs.str, zs.size); }

/* utf8 iterator */

STC_INLINE zsview_iter zsview_begin(const zsview* self) {
    zsview_iter it = {.chr = {self->str, utf8_chr_size(self->str)}}; return it;
}

STC_INLINE zsview_iter zsview_end(const zsview* self) {
    (void)self; zsview_iter it = {0}; return it;
}

STC_INLINE void zsview_next(zsview_iter* it) {
    it->ref += it->chr.size;
    it->chr.size = utf8_chr_size(it->ref);
    if (*it->ref == '\0') it->ref = NULL;
}

STC_INLINE zsview_iter zsview_advance(zsview_iter it, isize u8pos) {
    it.ref = utf8_offset(it.ref, u8pos);
    it.chr.size = utf8_chr_size(it.ref);
    if (*it.ref == '\0') it.ref = NULL;
    return it;
}

/* ---- Container helper functions ---- */

STC_INLINE size_t zsview_hash(const zsview *self)
    { return c_basehash_n(self->str, self->size); }

STC_INLINE int zsview_cmp(const zsview* x, const zsview* y)
    { return strcmp(x->str, y->str); }

STC_INLINE bool zsview_eq(const zsview* x, const zsview* y)
    { return x->size == y->size && !c_memcmp(x->str, y->str, x->size); }

STC_INLINE int zsview_icmp(const zsview* x, const zsview* y)
    { return utf8_icmp(x->str, y->str); }

STC_INLINE bool zsview_ieq(const zsview* x, const zsview* y)
    { return x->size == y->size && !utf8_icmp(x->str, y->str); }

/* ---- case insensitive ---- */

STC_INLINE bool zsview_iequals(zsview zs, const char* str)
    { return c_strlen(str) == zs.size && !utf8_icmp(zs.str, str); }

STC_INLINE bool zsview_istarts_with(zsview zs, const char* str)
    { return c_strlen(str) <= zs.size && !utf8_icmp(zs.str, str); }

STC_INLINE bool zsview_iends_with(zsview zs, const char* str) {
    isize n = c_strlen(str);
    return n <= zs.size && !utf8_icmp(zs.str + zs.size - n, str);
}

#endif // STC_ZSVIEW_H_INCLUDED

#if defined i_import
  #include "priv/utf8_prv.c"
#endif
