/*
 * This file is part of Beastie <https://purl.org/nxg/dist/beastie>
 * SPDX-FileCopyrightText: 2024 Norman Gray <https://nxg.me.uk>
 * SPDX-License-Identifier: BSD-2-Clause
 */

#ifndef UNICODE_H
#define UNICODE_H 1

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

#include "config.h"
#if HAVE_ICU
#include <unicode/ustdio.h>
#include <unicode/utext.h>
#include <unicode/utf.h>

#else
// unicode codepoints
typedef uint16_t UChar;         // imitating ICU
#endif

#include "unidefs.h"            // codepoint_t

codepoint_t decode_utf8(const unsigned char* bc,
                        size_t len,
                        unsigned char* nused,
                        const char** errmsg);
// Returned by decode_utf8 when it can't decode a UTF-8 sequence
// (this bit-pattern is not a valid codepoint).
extern const codepoint_t UNICODE_BAD_DECODE;

// used to signal EOF
extern const codepoint_t UNICODE_EOF;

// The unicode 'replacement character'
extern const codepoint_t UNICODE_REPLACEMENT_CHARACTER;

const unsigned char* encode_utf8(const codepoint_t codepoint, int* len);

// The ustring_s type represents strings as an array of codepoints.
// The encoding is equivalent to UTF-16.
// The `len` field is the number of codepoints in the string,
// while `idx` is the first unused slot in `s[]`,
// ie, taking account of the fact that a codepoint outside the BMP
// will occupy two code-units (uint16_t);
// these will be the same if there are no surrogates in the string.
//
// The cache_store_ pointer must in fact be an s7_pointer, cast to
// void.  This supports a simple cache mechanism, managed exclusively
// by unicode-scm.c:ustring_cache_object_{set,get}_proc (qv).
struct ustring_s {
    UChar* s_;
    size_t alloc_;              // number of uint16_t allocated to s
    size_t len_;                // number of characters/codepoints in the string
    size_t idx_;                // the number of code-units allocated
    const void* cache_store_;   // cache information for higher layers
};
typedef struct ustring_s* ustring_t;

typedef struct unicode_reader_s {
#if HAVE_ICU
    UFILE* in;             // input file, if we're reading from a file
#else
    FILE* in;              // input file; NULL if we're reading from a string,
                           // and set to NULL at EOF
    unsigned char* buf;    // bytes; non-NULL when reading from file
    size_t buflen;         // size of buf[]
    size_t len;            // amount of valid data in buf
#endif

    // This could be implemented using a UText, but in fact that provides
    // very little real benefit, and when I did implement it this way,
    // in most of the cases the semantics of UText meant that I had to
    // fall back to looking at the chunkContents within it.
    ustring_t us;          // non-NULL when we are reading from a ustring
    char our_ustring_p;    // true if this ustring belongs to this reader

    size_t idx;            // next point to be read
    size_t count;          // total number of codepoints returned so far
    char* filename;        // NULL if we are reading from a string
    size_t line_count;     // current line number (first line is line 1)
    char ascii_p;          // if true, return codepoints below 0x80 as characters
    codepoint_t pushed;    // one character of pushback
    char at_eof_p;         // true at EOF/EOS
} unicode_reader;

unicode_reader* make_unicode_reader_file(const char* filename,
                                         size_t buflen,
                                         const char** errmsg);
unicode_reader* make_unicode_reader_string(const unsigned char* bstr,
                                           const char** errmsg);
unicode_reader* make_unicode_reader_ustring(ustring_t us,
                                            const char** errmsg);
void unicode_reader_free(unicode_reader* p);
const size_t unicode_reader_get_source(unicode_reader* ur,
                                       char* buf,
                                       size_t buflen,
                                       char* is_file_p);
const codepoint_t unicode_reader_next_cp(unicode_reader* p,
                                         const char** errmsg);
void push_codepoint(unicode_reader* p, codepoint_t cp);

codepoint_t from_surrogate(const uint16_t* const p, const char** errmsg);
int is_surrogate(const codepoint_t cp);

ustring_t make_ustring(const char** errmsg);
void ustring_free(ustring_t);
void ustring_reset(ustring_t);
int ustring_length(ustring_t);
int ustring_lt(ustring_t us1, ustring_t us2);

const char* unicode_get_locale(void);
int unicode_set_locale(const char* locale, char** errmsg);

ustring_t ustring_map_func(ustring_t us, codepoint_t (*f)(codepoint_t));

#define USTRING_EQUAL_COLLAPSE_REPLACEMENTS 1
int ustring_equal(ustring_t s1, ustring_t s2, byte_t flags);

ustring_t ustring_append_cp(ustring_t, codepoint_t, const char**);
ustring_t ustring_append_utf8(ustring_t us, const byte_t* s, const char** errmsg);
ustring_t ustring_append_utf8_with_length(ustring_t us, const byte_t* s, const size_t len, const char** errmsg);
ustring_t ustring_append_ustring(ustring_t us1, ustring_t us2, const char** errmsg);
ustring_t ustring_append_uchars(ustring_t us1, UChar* uchars,
                                size_t n_cp, size_t n_uchars, const char** errmsg);
codepoint_t ustring_ref(ustring_t, size_t);
ustring_t ustring_substring(ustring_t us, int start, int end, const char** errmsg);
const byte_t* ustring_to_utf8(ustring_t, const char** errmsg);

const void* ustring_cache_store_get(ustring_t);
void ustring_cache_store_set(ustring_t, const void*);

int unicode_verbosity(int);
const char* unicode_version();

#if !HAVE_ICU
// These are copied from unicode/utf.h, which says:
//
// Code points that are not characters include:
//   * single surrogate code points (U+d800..U+dfff, 2048 code points)
//   * the last two code points on each plane
//     (U+__fffe and U+__ffff, 34 code points)
//   * U+fdd0..U+fdef (new with Unicode 3.1, 32 code points)
//   * the highest Unicode code point value is U+10ffff
//
// This means that all code points below U+d800 are character code points,
// and that boundary is tested first for performance.
#define U_IS_UNICODE_CHAR(c)                                    \
    ((uint32_t)(c)<0xd800 ||                                    \
     (0xdfff<(c) && (c)<=0x10ffff && !U_IS_UNICODE_NONCHAR(c)))
// Note: the 'noncharacters' are as discussed in the Standard,
// Sect.3.4, D14.  This set is _not_ the complement of U_IS_UNICODE_CHAR.
#define U_IS_UNICODE_NONCHAR(c)                                 \
    ((c)>=0xfdd0 &&                                             \
     ((c)<=0xfdef || ((c)&0xfffe)==0xfffe) && (c)<=0x10ffff)
#endif

int initialise_unicode_module(char** errmsg);

#endif /*  UNICODE_H */
