// This code is stripped copy-paste of https://libs.suckless.org/libgrapheme/
// grapheme_decode_utf8 function, licenced under ISC-License.
// Copyright 2019-2022 Laslo Hunhold <dev@frign.de>

#include <assert.h>
#include <stddef.h>
#include <stdint.h>

#include "utf8.h"

#define BETWEEN(c, l, u) (((c) >= (l)) && ((c) <= (u)))

const uint32_t KEKSUTF8InvalidCp = 0xFFFD;

static const struct {
    uint32_t mincp;
    uint32_t maxcp;
    uint8_t lower;
    uint8_t upper;
    char _pad[2];
} lut[] = {
    [0] =
        {
            /* 0xxxxxxx */
            .lower = 0x00, /* 00000000 */
            .upper = 0x7F, /* 01111111 */
            .mincp = (uint32_t)0,
            .maxcp = ((uint32_t)1 << (uint8_t)7) - 1, /* 7 bits capacity */
        },
    [1] =
        {
            /* 110xxxxx */
            .lower = 0xC0, /* 11000000 */
            .upper = 0xDF, /* 11011111 */
            .mincp = (uint32_t)1 << (uint8_t)7,
            .maxcp = ((uint32_t)1 << (uint8_t)11) - 1, /* 5+6=11 bits capacity */
        },
    [2] =
        {
            /* 1110xxxx */
            .lower = 0xE0, /* 11100000 */
            .upper = 0xEF, /* 11101111 */
            .mincp = (uint32_t)1 << (uint8_t)11,
            .maxcp = ((uint32_t)1 << (uint8_t)16) - 1, /* 4+6+6=16 bits capacity */
        },
    [3] =
        {
            /* 11110xxx */
            .lower = 0xF0, /* 11110000 */
            .upper = 0xF7, /* 11110111 */
            .mincp = (uint32_t)1 << (uint8_t)16,
            .maxcp = ((uint32_t)1 << (uint8_t)21) - 1, /* 3+6+6+6=21 bits capacity */
        },
};

size_t
KEKSUTF8CpDecode(uint32_t *cp, const unsigned char *str, const size_t len)
{
    assert(cp != NULL);
    if (str == NULL || len == 0) {
        (*cp) = KEKSUTF8InvalidCp;
        return 0;
    }
    if (str[0] == 0) {
        (*cp) = KEKSUTF8InvalidCp;
        return 1;
    }
    size_t off = 0;
    for (off = 0; off < 4; off++) {
        if (BETWEEN(str[0], lut[off].lower, lut[off].upper)) {
            (*cp) = str[0] - lut[off].lower;
            break;
        }
    }
    if (off == 4) {
        (*cp) = KEKSUTF8InvalidCp;
        return 1;
    }
    size_t i = 0;
    if ((1 + off) > len) {
        (*cp) = KEKSUTF8InvalidCp;
        for (i = 0; 1 + i < len; i++) {
            if (!BETWEEN(str[1 + i], 0x80, 0xBF)) {
                break;
            }
        }
        return ((1 + i) < len) ? (1 + i) : (1 + off);
    }
    for (i = 1; i <= off; i++) {
        if (!BETWEEN(str[i], 0x80, 0xBF)) {
            (*cp) = KEKSUTF8InvalidCp;
            return 1 + (i - 1);
        }
        (*cp) = (*cp << (uint8_t)6) | // NOLINT(hicpp-signed-bitwise)
                (str[i] & (uint8_t)0x3F);
    }
    if ((*cp < lut[off].mincp) || BETWEEN(*cp, 0xD800, 0xDFFF) || (*cp > 0x10FFFF)) {
        (*cp) = KEKSUTF8InvalidCp;
    }
    return 1 + off;
}
