#define UNICODE
#define PY_SSIZE_T_CLEAN
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif

#include <Python.h>
#include <datetime.h>
#include <errno.h>

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>

#define MIN(x, y) ((x < y) ? x : y)
#define MAX(x, y) ((x > y) ? x : y)
#define CLAMP(value, lower, upper) ((value > upper) ? upper : ((value < lower) ? lower : value))
#define STRIDE(width, r, c) ((width * (r)) + (c))

#ifdef _MSC_VER
#ifndef uint32_t
typedef unsigned __int32 uint32_t;
#endif

#ifndef uint8_t
typedef unsigned __int8 uint8_t;
#endif
#else
#include <stdint.h>
#endif
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

static PyObject *
barename(PyObject *self, PyObject *tag) {
    if (!PyUnicode_Check(tag)) {
        PyErr_Format(PyExc_TypeError, "%R is not a unicode string", tag);
        return NULL;
    }
    Py_ssize_t pos = PyUnicode_FindChar(tag, '}', 0, PyUnicode_GetLength(tag), -1);
    switch (pos) {
        case -2: return NULL;
        case -1: Py_INCREF(tag); return tag;
        default: return PyUnicode_Substring(tag, pos + 1, PyUnicode_GetLength(tag));
    }
}

static PyObject *
namespace(PyObject *self, PyObject *tag) {
    if (!PyUnicode_Check(tag)) {
        PyErr_Format(PyExc_TypeError, "%R is not a unicode string", tag);
        return NULL;
    }
    Py_ssize_t pos = PyUnicode_FindChar(tag, '}', 0, PyUnicode_GetLength(tag), -1);
    switch (pos) {
        case -2: return NULL;
        case -1: return PyUnicode_FromString("");
        default: return PyUnicode_Substring(tag, 1, pos);
    }
}


static PyObject *
speedup_parse_date(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    const char *raw, *orig, *tz;
    char *end;
    long year, month, day, hour, minute, second, tzh = 0, tzm = 0, sign = 0;
    size_t len;
    if (nargs != 1) {
        PyErr_SetString(PyExc_TypeError, "parse_date() takes exactly 1 argument");
        return NULL;
    }
    raw = PyUnicode_AsUTF8(args[0]);
    if (!raw) return NULL;
    while ((*raw == ' ' || *raw == '\t' || *raw == '\n' || *raw == '\r' || *raw == '\f' || *raw == '\v') && *raw != 0) raw++;
    len = strlen(raw);
    if (len < 19) Py_RETURN_NONE;

    orig = raw;

    year = strtol(raw, &end, 10);
    if ((end - raw) != 4) Py_RETURN_NONE;
    raw += 5;


    month = strtol(raw, &end, 10);
    if ((end - raw) != 2) Py_RETURN_NONE;
    raw += 3;

    day = strtol(raw, &end, 10);
    if ((end - raw) != 2) Py_RETURN_NONE;
    raw += 3;

    hour = strtol(raw, &end, 10);
    if ((end - raw) != 2) Py_RETURN_NONE;
    raw += 3;

    minute = strtol(raw, &end, 10);
    if ((end - raw) != 2) Py_RETURN_NONE;
    raw += 3;

    second = strtol(raw, &end, 10);
    if ((end - raw) != 2) Py_RETURN_NONE;

    tz = orig + len - 6;

    if (*tz == '+') sign = +1;
    if (*tz == '-') sign = -1;
    if (sign != 0) {
        // We have TZ info
        tz += 1;

        tzh = strtol(tz, &end, 10);
        if ((end - tz) != 2) Py_RETURN_NONE;
        tz += 3;

        tzm = strtol(tz, &end, 10);
        if ((end - tz) != 2) Py_RETURN_NONE;
    }

    return Py_BuildValue("lllllll", year, month, day, hour, minute, second, (tzh * 60 + tzm) * sign * 60);
}


static PyObject *
speedup_pdf_float(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    double f = 0.0, a = 0.0;
    char *buf = "0", *dot;
    void *free_buf = NULL;
    int precision = 6, l = 0;
    PyObject *ret;

    if (nargs != 1) {
        PyErr_SetString(PyExc_TypeError, "pdf_float() takes exactly 1 argument");
        return NULL;
    }
    f = PyFloat_AsDouble(args[0]);
    if (f == -1.0 && PyErr_Occurred()) return NULL;

    a = fabs(f);

    if (a > 1.0e-7) {
        if (a > 1) precision = MIN(MAX(0, 6 - (int)log10(a)), 6);
        buf = PyOS_double_to_string(f, 'f', precision, 0, NULL);
        if (buf != NULL) {
            free_buf = (void *)buf;
            if (precision > 0) {
                l = (int)(strlen(buf) - 1);
                while (l > 0 && buf[l] == '0') l--;
                if (buf[l] == ',' || buf[l] == '.') buf[l] = 0;
                else buf[l + 1] = 0;
                if ((dot = strchr(buf, ','))) *dot = '.';
            }
        } else if (!PyErr_Occurred()) PyErr_SetString(PyExc_TypeError, "Float->str failed.");
    }

    ret = PyUnicode_FromString(buf);
    if (free_buf != NULL) PyMem_Free(free_buf);
    return ret;
}

static PyObject *
speedup_detach(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    const char *devnull = NULL;
    if (nargs != 1) {
        PyErr_SetString(PyExc_TypeError, "detach() takes exactly 1 argument");
        return NULL;
    }
    devnull = PyUnicode_AsUTF8(args[0]);
    if (!devnull) return NULL;
    if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
    if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
    if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrnoWithFilename(PyExc_OSError, devnull);
    Py_RETURN_NONE;
}

static void
calculate_gaussian_kernel(Py_ssize_t size, double *kernel, double radius) {
    const double sqr = radius * radius;
    const double factor = 1.0 / (2 * M_PI * sqr);
    const double denom = 2 * sqr;
    double *t, sum = 0;
    Py_ssize_t r, c, center = size / 2;

    for (r = 0; r < size; r++) {
        t = kernel + (r * size);
        for (c = 0; c < size; c++) { t[c] = factor * pow(M_E, -(((r - center) * (r - center) + (c - center) * (c - center)) / denom)); }
    }

    // Normalize matrix
    for (r = 0; r < size * size; r++) sum += kernel[r];
    sum = 1 / sum;
    for (r = 0; r < size * size; r++) kernel[r] *= sum;
}

static PyObject *
speedup_create_texture(PyObject *self, PyObject *args, PyObject *kw) {
    PyObject *ret = NULL;
    Py_ssize_t width, height, weight = 3, i, j, r, c, half_weight;
    double pixel, *mask = NULL, radius = 1, *kernel = NULL, blend_alpha = 0.1;
    float density = 0.7f;
    unsigned char base_r, base_g, base_b, blend_r = 0, blend_g = 0, blend_b = 0, *ppm = NULL, *t = NULL;
    char header[100] = {0};
    static char *kwlist[] = {"blend_red", "blend_green", "blend_blue", "blend_alpha", "density", "weight", "radius", NULL};

    if (!PyArg_ParseTupleAndKeywords(
            args,
            kw,
            "nnbbb|bbbdfnd",
            kwlist,
            &width,
            &height,
            &base_r,
            &base_g,
            &base_b,
            &blend_r,
            &blend_g,
            &blend_b,
            &blend_alpha,
            &density,
            &weight,
            &radius))
        return NULL;
    if (weight % 2 != 1 || weight < 1) {
        PyErr_SetString(PyExc_ValueError, "The weight must be an odd positive number");
        return NULL;
    }
    if (radius <= 0) {
        PyErr_SetString(PyExc_ValueError, "The radius must be positive");
        return NULL;
    }
    if (width > 100000 || height > 10000) {
        PyErr_SetString(PyExc_ValueError, "The width or height is too large");
        return NULL;
    }
    if (width < 1 || height < 1) {
        PyErr_SetString(PyExc_ValueError, "The width or height is too small");
        return NULL;
    }
    snprintf(header, sizeof(header) - 1, "P6\n%d %d\n255\n", (int)width, (int)height); // NOLINT

    kernel = (double *)calloc(weight * weight, sizeof(double));
    if (kernel == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    mask = (double *)calloc(width * height, sizeof(double));
    if (mask == NULL) {
        free(kernel);
        PyErr_NoMemory();
        return NULL;
    }
    ppm = (unsigned char *)calloc(strlen(header) + (3 * width * height), sizeof(unsigned char));
    if (ppm == NULL) {
        free(kernel);
        free(mask);
        PyErr_NoMemory();
        return NULL;
    }

    calculate_gaussian_kernel(weight, kernel, radius);

    // Random noise, noisy pixels are blend_alpha, other pixels are 0
    for (i = 0; i < width * height; i++) {
        if (((float)(rand()) / (float)RAND_MAX) <= density) mask[i] = blend_alpha;
    }

    // Blur the noise using the gaussian kernel
    half_weight = weight / 2;
    for (r = 0; r < height; r++) {
        for (c = 0; c < width; c++) {
            pixel = 0;
            for (i = -half_weight; i <= half_weight; i++) {
                for (j = -half_weight; j <= half_weight; j++) {
                    pixel += (*(mask + STRIDE(width, CLAMP(r + i, 0, height - 1), CLAMP(c + j, 0, width - 1)))) *
                             (*(kernel + STRIDE(weight, half_weight + i, half_weight + j)));
                }
            }
            *(mask + STRIDE(width, r, c)) = CLAMP(pixel, 0, 1);
        }
    }

    // Create the texture in PPM (P6) format
    memcpy(ppm, header, strlen(header)); // NOLINT
    t = ppm + strlen(header);
    for (i = 0, j = 0; j < width * height; i += 3, j += 1) {
#define BLEND(src, dest) (((unsigned char)(src * mask[j])) + ((unsigned char)(dest * (1 - mask[j]))))
        t[i] = BLEND(blend_r, base_r);
        t[i + 1] = BLEND(blend_g, base_g);
        t[i + 2] = BLEND(blend_b, base_b);
    }

    ret = Py_BuildValue("s", ppm);
    free(mask);
    mask = NULL;
    free(kernel);
    kernel = NULL;
    free(ppm);
    ppm = NULL;
    return ret;
}

static PyObject *
speedup_websocket_mask(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    PyObject *data = NULL, *mask = NULL;
    Py_buffer data_buf = {0}, mask_buf = {0};
    Py_ssize_t offset = 0, i = 0;
    char *dbuf = NULL, *mbuf = NULL;
    int ok = 0;

    if (nargs < 2 || nargs > 3) {
        PyErr_SetString(PyExc_TypeError, "websocket_mask() takes 2 or 3 arguments");
        return NULL;
    }
    data = args[0];
    mask = args[1];
    if (nargs > 2) {
        offset = PyLong_AsSsize_t(args[2]);
        if (offset == -1 && PyErr_Occurred()) return NULL;
    }

    if (PyObject_GetBuffer(data, &data_buf, PyBUF_SIMPLE | PyBUF_WRITABLE) != 0) return NULL;
    if (PyObject_GetBuffer(mask, &mask_buf, PyBUF_SIMPLE) != 0) goto done;

    dbuf = (char *)data_buf.buf;
    mbuf = (char *)mask_buf.buf;
    for (i = 0; i < data_buf.len; i++) dbuf[i] ^= mbuf[(i + offset) & 3];
    ok = 1;

done:
    if (data_buf.obj) PyBuffer_Release(&data_buf);
    if (mask_buf.obj) PyBuffer_Release(&mask_buf);
    if (ok) { Py_RETURN_NONE; }
    return NULL;
}

#define UTF8_ACCEPT 0
#define UTF8_REJECT 1

static const uint8_t utf8d[] = {
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f
    1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f
    7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7,   7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf
    8,   8,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2,   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df
    0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3,                                                 // e0..ef
    0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,                                                 // f0..ff
    0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1,                                                 // s0..s0
    1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
    1,   2,   1,   1,   1,   1,   1,   2,   1,   2,   1,   1,   1,   1,   1,   1,   1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
    1,   2,   1,   1,   1,   1,   1,   1,   1,   2,   1,   1,   1,   1,   1,   1,   1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
    1,   3,   1,   1,   1,   1,   1,   3,   1,   3,   1,   1,   1,   1,   1,   1,   1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8
};

#ifdef _MSC_VER
static void __inline
#else
static void inline
#endif
    utf8_decode_(uint32_t *state, uint32_t *codep, uint8_t byte) {
    /* Comes from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
     * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
     * Used under license: https://opensource.org/licenses/MIT
     */
    uint32_t type = utf8d[byte];

    *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte);

    *state = utf8d[256 + *state * 16 + type];
}

static PyObject *
utf8_decode(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    uint8_t *dbuf = NULL;
    uint32_t state = UTF8_ACCEPT, codep = 0, *buf = NULL;
    PyObject *data_obj = NULL, *ans = NULL;
    Py_buffer pbuf;
    Py_ssize_t i = 0, pos = 0;

    if (nargs < 1 || nargs > 3) {
        PyErr_SetString(PyExc_TypeError, "utf8_decode() takes 1 to 3 arguments");
        return NULL;
    }
    data_obj = args[0];
    if (nargs > 1) {
        unsigned long val = PyLong_AsUnsignedLong(args[1]);
        if (val == (unsigned long)-1 && PyErr_Occurred()) return NULL;
        state = (uint32_t)val;
    }
    if (nargs > 2) {
        unsigned long val = PyLong_AsUnsignedLong(args[2]);
        if (val == (unsigned long)-1 && PyErr_Occurred()) return NULL;
        codep = (uint32_t)val;
    }
    if (PyObject_GetBuffer(data_obj, &pbuf, PyBUF_SIMPLE) != 0) return NULL;
    buf = (uint32_t *)PyMem_Malloc(sizeof(uint32_t) * pbuf.len);
    if (buf == NULL) goto error;
    dbuf = (uint8_t *)pbuf.buf;

    for (i = 0; i < pbuf.len; i++) {
        utf8_decode_(&state, &codep, dbuf[i]);
        if (state == UTF8_ACCEPT) buf[pos++] = codep;
        else if (state == UTF8_REJECT) {
            PyErr_SetString(PyExc_ValueError, "Invalid byte in UTF-8 string");
            goto error;
        }
    }
    ans = PyUnicode_DecodeUTF32((const char *)buf, pos * sizeof(uint32_t), "strict", NULL);
error:
    if (pbuf.obj) PyBuffer_Release(&pbuf);
    if (buf) {
        PyMem_Free(buf);
        buf = NULL;
    }
    if (ans == NULL) return ans;
    return Py_BuildValue("NII", ans, state, codep);
}

static PyObject *
clean_xml_chars(PyObject *self, PyObject *text) {
    PyObject *result = NULL;
    void *result_text = NULL;
    Py_ssize_t src_i, target_i;
    enum PyUnicode_Kind text_kind;
    Py_UCS4 ch;

    if (!PyUnicode_Check(text)) {
        PyErr_SetString(PyExc_TypeError, "A unicode string is required");
        return NULL;
    }
    if (PyUnicode_READY(text) != 0) {
        // just return null, an exception is already set by READY()
        return NULL;
    }
    if (PyUnicode_GET_LENGTH(text) == 0) {
        // make sure that malloc(0) will never happen
        return text;
    }

    text_kind = PyUnicode_KIND(text);
    // Once we've called READY(), our string is in canonical form, which means
    // it is encoded using UTF-{8,16,32}, such that each codepoint is one
    // element in the array. The value of the Kind enum is the size of each
    // character.
    result_text = malloc(PyUnicode_GET_LENGTH(text) * text_kind);
    if (result_text == NULL) return PyErr_NoMemory();

    target_i = 0;
    for (src_i = 0; src_i < PyUnicode_GET_LENGTH(text); src_i++) {
        ch = PyUnicode_READ(text_kind, PyUnicode_DATA(text), src_i);
        // based on https://en.wikipedia.org/wiki/Valid_characters_in_XML#Non-restricted_characters
        // python 3.3+ unicode strings never contain surrogate pairs, since if
        // they did, they would be represented as UTF-32
        if ((0x20 <= ch && ch <= 0x7e) || ch == 0x9 || ch == 0xa || ch == 0xd || ch == 0x85 || (0x00A0 <= ch && ch <= 0xD7FF) ||
            (0xE000 <= ch && ch <= 0xFDCF) || (0xFDF0 <= ch && ch <= 0xFFFD) || (0xffff < ch && ch <= 0x10ffff)) {
            PyUnicode_WRITE(text_kind, result_text, target_i, ch);
            target_i += 1;
        }
    }

    // using text_kind here is ok because we don't create any characters that
    // are larger than might already exist
    result = PyUnicode_FromKindAndData(text_kind, result_text, target_i);
    free(result_text);
    return result;
}

static PyObject *
speedup_iso_8601(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    const char *str = NULL, *c = NULL;
    int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, usecond = 0, i = 0, tzhour = 1000, tzminute = 0, tzsign = 0;

    if (nargs != 1) {
        PyErr_SetString(PyExc_TypeError, "parse_iso8601() takes exactly 1 argument");
        return NULL;
    }
    str = PyUnicode_AsUTF8(args[0]);
    if (!str) return NULL;
    c = str;

#define RAISE(msg) return PyErr_Format(PyExc_ValueError, "%s is not a valid ISO 8601 datestring: %s", str, msg);
#define CHAR_IS_DIGIT(c) (*c >= '0' && *c <= '9')
#define READ_DECIMAL_NUMBER(max_digits, x, abort)      \
    for (i = 0; i < max_digits; i++) {                 \
        if (CHAR_IS_DIGIT(c)) x = 10 * x + *c++ - '0'; \
        else { abort; }                                \
    }
#define OPTIONAL_SEPARATOR(x) \
    if (*c == x) c++;

    // Ignore leading whitespace
    while (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t' || *c == '\v' || *c == '\f') c++;

    // Year
    READ_DECIMAL_NUMBER(4, year, RAISE("No year specified"));
    OPTIONAL_SEPARATOR('-');
    // Month (optional)
    READ_DECIMAL_NUMBER(2, month, break);
    if (month == 0) month = 1; // YYYY format
    else {
        OPTIONAL_SEPARATOR('-');

        // Day (optional)
        READ_DECIMAL_NUMBER(2, day, break);
    }
    if (day == 0) day = 1; // YYYY-MM format
    if (month > 12) RAISE("month greater than 12");

    if (*c == 'T' || *c == ' ') // Time separator
    {
        c++;

        // Hour
        READ_DECIMAL_NUMBER(2, hour, RAISE("No hour specified"));
        OPTIONAL_SEPARATOR(':');
        // Minute (optional)
        READ_DECIMAL_NUMBER(2, minute, break);
        OPTIONAL_SEPARATOR(':');
        // Second (optional)
        READ_DECIMAL_NUMBER(2, second, break);

        if (*c == '.' || *c == ',') // separator for microseconds
        {
            c++;
            // Parse fraction of second up to 6 places
            READ_DECIMAL_NUMBER(6, usecond, break);
            // Omit excessive digits
            while (CHAR_IS_DIGIT(c)) c++;
            // If we break early, fully expand the usecond
            while (i++ < 6) usecond *= 10;
        }
    }

    switch (*c) {
        case 'Z':
            tzhour = 0;
            c++;
            break;
        case '+':
            tzsign = 1;
            c++;
            break;
        case '-':
            tzsign = -1;
            c++;
            break;
        default: break;
    }

    if (tzsign != 0) {
        tzhour = 0;
        READ_DECIMAL_NUMBER(2, tzhour, break);
        OPTIONAL_SEPARATOR(':');
        READ_DECIMAL_NUMBER(2, tzminute, break);
    }

    return Py_BuildValue(
        "NOi",
        PyDateTime_FromDateAndTime(year, month, day, hour, minute, second, usecond),
        (tzhour == 1000) ? Py_False : Py_True,
        tzsign * 60 * (tzhour * 60 + tzminute));
}

#ifndef _MSC_VER
#include <pthread.h>
#if defined(__FreeBSD__) || defined(__OpenBSD__)
#define FREEBSD_SET_NAME
#endif
#if defined(__APPLE__)
// I can't figure out how to get pthread.h to include this definition on macOS. MACOSX_DEPLOYMENT_TARGET does not work.
extern int pthread_setname_np(const char *name);
#elif defined(FREEBSD_SET_NAME)
// Function has a different name on FreeBSD
void pthread_set_name_np(pthread_t tid, const char *name);
#elif defined(__NetBSD__)
// pthread.h provides the symbol
#elif defined(__HAIKU__)
// Haiku doesn't support pthread_set_name_np yet
#else
// Need _GNU_SOURCE for pthread_setname_np on linux and that causes other issues on systems with old glibc
extern int pthread_setname_np(pthread_t, const char *name);
#endif
#endif


static PyObject *
set_thread_name(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    (void)(self);
    (void)(args);
    (void)(nargs);
#if defined(_MSC_VER) || defined(__HAIKU__)
    PyErr_SetString(PyExc_RuntimeError, "Setting thread names not supported on this platform");
    return NULL;
#else
    const char *name;
    int ret;
    if (nargs != 1) {
        PyErr_SetString(PyExc_TypeError, "set_thread_name() takes exactly 1 argument");
        return NULL;
    }
    name = PyUnicode_AsUTF8(args[0]);
    if (!name) return NULL;
    while (1) {
        errno = 0;
#if defined(__APPLE__)
        ret = pthread_setname_np(name);
#elif defined(FREEBSD_SET_NAME)
        pthread_set_name_np(pthread_self(), name);
        ret = 0;
#elif defined(__NetBSD__)
        ret = pthread_setname_np(pthread_self(), "%s", name);
#else
        ret = pthread_setname_np(pthread_self(), name);
#endif
        if (ret != 0 && (errno == EINTR || errno == EAGAIN)) continue;
        break;
    }
    if (ret != 0) {
        PyErr_SetFromErrno(PyExc_OSError);
        return NULL;
    }
    Py_RETURN_NONE;
#endif
}

#define char_is_ignored(ch) (ch <= 32)

typedef struct udata {
    void *data;
    int kind;
    Py_ssize_t len;
} udata;

static size_t
count_chars_in(udata *text) {
    size_t ans = text->len;
    for (Py_ssize_t i = 0; i < text->len; i++)
        if (char_is_ignored(PyUnicode_READ(text->kind, text->data, i))) ans--;
    return ans;
}

static size_t
count_chars(const char *tag_name, Py_ssize_t tag_len, udata *text, udata *tail) {
    size_t ans = 0;
    int is_ignored_tag = 0;
    char ltagname[16];
    if (tag_name) {
        const char *b = memchr(tag_name, '}', tag_len);
        if (b) {
            b++;
            tag_len -= b - tag_name;
            tag_name = b;
        }
        if (tag_len < sizeof(ltagname)) {
            memcpy(ltagname, tag_name, tag_len);
            for (size_t i = 0; i < tag_len; i++)
                if ('A' <= ltagname[i] && ltagname[i] <= 'Z') ltagname[i] += 32;
#define EQ(x) (tag_len + 1 == sizeof(#x) && memcmp(ltagname, #x, tag_len) == 0)
            switch (ltagname[0]) {
                case 's':
                    if (EQ(script) || EQ(style)) is_ignored_tag = 1;
                    else if (EQ(svg)) ans += 1000;
                    break;
                case 'n':
                    if (EQ(noscript)) is_ignored_tag = 1;
                    break;
                case 't':
                    if (EQ(title)) is_ignored_tag = 1;
                    break;
                case 'i':
                    if (EQ(img)) ans += 1000;
                    break;
                case 'v':
                    if (EQ(video)) ans += 2000;
                    break;
            }
        }
    }
#undef EQ
    ans += count_chars_in(tail);
    if (!is_ignored_tag) ans += count_chars_in(text);
    return ans;
}

static PyObject *
get_num_of_significant_chars(PyObject *self, PyObject *elem) {
    (void)(self);
    const char *tag_name = NULL;
    Py_ssize_t tag_len = 0;
    PyObject *ptn = PyObject_GetAttrString(elem, "tag"), *text = NULL;
    if (!ptn) PyErr_Clear();
    else if (PyUnicode_Check(ptn)) tag_name = PyUnicode_AsUTF8AndSize(ptn, &tag_len);
    udata xdata = {0}, tdata = {0};
    if (tag_name) {
        text = PyObject_GetAttrString(elem, "text");
        if (!text) PyErr_Clear();
        else if (PyUnicode_Check(text)) {
            xdata.len = PyUnicode_GET_LENGTH(text);
            xdata.kind = PyUnicode_KIND(text);
            xdata.data = PyUnicode_DATA(text);
        }
    }
    PyObject *tail = PyObject_GetAttrString(elem, "tail");
    if (!tail) PyErr_Clear();
    else if (PyUnicode_Check(tail)) {
        tdata.len = PyUnicode_GET_LENGTH(tail);
        tdata.kind = PyUnicode_KIND(tail);
        tdata.data = PyUnicode_DATA(tail);
    }
    size_t ans;
    Py_BEGIN_ALLOW_THREADS ans = count_chars(tag_name, tag_len, &xdata, &tdata);
    Py_END_ALLOW_THREADS;
    Py_XDECREF(ptn);
    Py_XDECREF(text);
    Py_XDECREF(tail);
    return PyLong_FromSize_t(ans);
}


typedef struct deepcopy_data {
    PyObject *python_deepcopy, *memo;
} deepcopy_data;

static PyObject *deepcopy_object(PyObject *, deepcopy_data *);

static PyObject *
insert_into_cache(PyObject *o, deepcopy_data *d) {
    if (!o) return NULL;
    PyObject *key = PyLong_FromVoidPtr(o);
    if (!key) {
        Py_DECREF(o);
        return NULL;
    }
    if (PyDict_SetItem(d->memo, key, o) != 0) {
        Py_DECREF(key);
        Py_DECREF(o);
        return NULL;
    }
    return o;
}

static PyObject *
deepcopy_list(PyObject *o, deepcopy_data *d) {
    PyObject *ans = insert_into_cache(PyList_New(PyList_GET_SIZE(o)), d);
    if (!ans) return NULL;
    for (Py_ssize_t i = 0; i < PyList_GET_SIZE(o); i++) {
        PyObject *t = deepcopy_object(PyList_GET_ITEM(o, i), d);
        if (!t) {
            Py_CLEAR(t);
            return NULL;
        }
        PyList_SET_ITEM(ans, i, t);
    }
    return ans;
}

static PyObject *
deepcopy_tuple(PyObject *o, deepcopy_data *d) {
    PyObject *ans = insert_into_cache(PyTuple_New(PyTuple_GET_SIZE(o)), d);
    if (!ans) return NULL;
    for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(o); i++) {
        PyObject *t = deepcopy_object(PyTuple_GET_ITEM(o, i), d);
        if (!t) {
            Py_CLEAR(ans);
            return NULL;
        }
        PyTuple_SET_ITEM(ans, i, t);
    }
    return ans;
}


static PyObject *
deepcopy_dict(PyObject *o, deepcopy_data *d) {
    PyObject *key, *value;
    Py_ssize_t pos = 0;
    PyObject *ans = insert_into_cache(PyDict_New(), d);
    if (!ans) return NULL;

    while (PyDict_Next(o, &pos, &key, &value)) {
        PyObject *tk = deepcopy_object(key, d);
        if (!tk) break;
        PyObject *tv = deepcopy_object(value, d);
        if (!tv) {
            Py_DECREF(tk);
            break;
        }
        int ok = PyDict_SetItem(ans, tk, tv) == 0;
        Py_DECREF(tk);
        Py_DECREF(tv);
        if (!ok) break;
    }
    if (PyErr_Occurred()) {
        Py_DECREF(ans);
        return NULL;
    }
    return ans;
}

static PyObject *
deepcopy_set(PyObject *o, deepcopy_data *d, int is_frozen) {
    PyObject *ans = insert_into_cache(is_frozen ? PyFrozenSet_New(NULL) : PySet_New(NULL), d);
    if (!ans) return NULL;
    PyObject *iter = PyObject_GetIter(o);
    if (!iter) {
        Py_DECREF(ans);
        return NULL;
    }
    PyObject *key;
    while ((key = PyIter_Next(iter)) != NULL) {
        PyObject *t = deepcopy_object(key, d);
        if (!t) break;
        if (PySet_Add(ans, t) != 0) {
            Py_DECREF(t);
            break;
        }
    }
    Py_DECREF(iter);
    if (PyErr_Occurred()) {
        Py_DECREF(ans);
        return NULL;
    }
    return ans;
}

static PyObject *
deepcopy_object(PyObject *o, deepcopy_data *d) {
    PyObject *key = PyLong_FromVoidPtr(o);
    if (!key) return NULL;
    PyObject *cached = PyDict_GetItem(d->memo, key);
    Py_DECREF(key);
    if (cached) return Py_NewRef(cached);
    PyTypeObject *type;
    // Fast case for builtin immutable types
    if (o == Py_None || o == Py_True || o == Py_False || (type = Py_TYPE(o)) == &PyUnicode_Type || type == &PyBytes_Type || type == &PyLong_Type ||
        type == &PyFloat_Type || type == &PyMemoryView_Type || type == &PyComplex_Type || type == &PyType_Type || type == &PyFunction_Type ||
        type == &PyCode_Type || o == Py_Ellipsis || o == Py_NotImplemented)
        return Py_NewRef(o);
    if (type == &PyList_Type) return deepcopy_list(o, d);
    if (type == &PyTuple_Type) return deepcopy_tuple(o, d);
    if (type == &PyDict_Type) return deepcopy_dict(o, d);
    if (type == &PySet_Type) return deepcopy_set(o, d, 0);
    if (type == &PyFrozenSet_Type) return deepcopy_set(o, d, 1);
    if (PyWeakref_Check(o)) return Py_NewRef(o);
    return PyObject_CallFunctionObjArgs(d->python_deepcopy, o, d->memo);
}

static PyObject *
deepcopy(PyObject *self, PyObject *o) {
    PyObject *memo = PyDict_New();
    if (!memo) return NULL;
    PyObject *copy_module = PyImport_ImportModule("copy");
    if (!copy_module) {
        Py_DECREF(memo);
        return NULL;
    }
    PyObject *python_deepcopy = PyObject_GetAttrString(copy_module, "deepcopy");
    Py_DECREF(copy_module);
    if (!python_deepcopy) {
        Py_DECREF(memo);
        return NULL;
    }
    deepcopy_data d = {.python_deepcopy = python_deepcopy, .memo = memo};
    PyObject *ans = deepcopy_object(o, &d);
    Py_DECREF(python_deepcopy);
    Py_DECREF(memo);
    return ans;
}


static PyObject *
pread_all(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
    int fd;
    unsigned long long n, offset = 0;
    if (nargs < 2 || nargs > 3) {
        PyErr_SetString(PyExc_TypeError, "pread_all() takes 2 or 3 arguments");
        return NULL;
    }
    fd = (int)PyLong_AsLong(args[0]);
    if (fd == -1 && PyErr_Occurred()) return NULL;
    n = PyLong_AsUnsignedLongLong(args[1]);
    if (n == (unsigned long long)-1 && PyErr_Occurred()) return NULL;
    if (nargs > 2) {
        offset = PyLong_AsUnsignedLongLong(args[2]);
        if (offset == (unsigned long long)-1 && PyErr_Occurred()) return NULL;
    }
#ifdef _WIN32
    PyObject *msvcrt = PyImport_ImportModule("msvcrt");
    if (!msvcrt) return NULL;
    PyObject *get_osfhandle = PyObject_GetAttrString(msvcrt, "get_osfhandle");
    Py_CLEAR(msvcrt);
    if (!get_osfhandle) return NULL;
    PyObject *ret = PyObject_CallFunctionObjArgs(get_osfhandle, args[0], NULL);
    Py_CLEAR(get_osfhandle);
    if (!ret) return NULL;
    HANDLE file = (HANDLE)PyLong_AsUnsignedLongLong(ret);
    Py_CLEAR(ret);
#endif
    PyObject *output = PyBytes_FromStringAndSize(NULL, n);
    if (!output || !n) return output;
    size_t pos = 0;
    char *buf = PyBytes_AS_STRING(output);
    int saved_errno = 0;
    Py_BEGIN_ALLOW_THREADS;
    while (pos < n) {
#ifdef _WIN32
        DWORD nr = 0;
        OVERLAPPED overlapped;
        memset(&overlapped, 0, sizeof(OVERLAPPED));
        overlapped.OffsetHigh = (uint32_t)((offset & 0xFFFFFFFF00000000LL) >> 32);
        overlapped.Offset = (uint32_t)(offset & 0xFFFFFFFFLL);
        SetLastError(0);
        BOOL ok = ReadFile(file, buf + pos, n - pos, &nr, &overlapped);
        if (!ok) {
            DWORD err = GetLastError();
            if (err != ERROR_HANDLE_EOF) saved_errno = err;
            break;
        }
#else
        ssize_t nr = pread(fd, buf + pos, n - pos, offset);
        if (nr < 0) {
            if (errno == EINTR || errno == EAGAIN) continue;
            saved_errno = errno;
            break;
        } else if (nr == 0) break;
#endif
        pos += nr;
        offset += nr;
    }
    Py_END_ALLOW_THREADS if (saved_errno != 0) {
        Py_CLEAR(output);
#ifdef _WIN32
        PyErr_SetFromWindowsErr(saved_errno);
#else
        errno = saved_errno;
        PyErr_SetFromErrno(PyExc_OSError);
#endif
        return NULL;
    }
    if (pos < n && _PyBytes_Resize(&output, pos) != 0) return NULL;
    return output;
}

static PyMethodDef speedup_methods[] = {
    {"deepcopy", deepcopy, METH_O, "deepcopy(object)\n\nFast implementation of deepcopy()"},

    {"parse_date",
     (PyCFunction)(void (*)(void))speedup_parse_date,
     METH_FASTCALL,
     "parse_date()\n\nParse ISO dates faster (specialized for dates stored in the calibre db)."},

    {"parse_iso8601",
     (PyCFunction)(void (*)(void))speedup_iso_8601,
     METH_FASTCALL,
     "parse_iso8601(datestring)\n\nParse ISO 8601 dates faster. More spec compliant than parse_date()"},

    {"pdf_float", (PyCFunction)(void (*)(void))speedup_pdf_float, METH_FASTCALL, "pdf_float()\n\nConvert float to a string representation suitable for PDF"},

    {"detach",
     (PyCFunction)(void (*)(void))speedup_detach,
     METH_FASTCALL,
     "detach()\n\nRedirect the standard I/O stream to the specified file (usually os.devnull)"},

    {"create_texture",
     (PyCFunction)speedup_create_texture,
     METH_VARARGS | METH_KEYWORDS,
     "create_texture(width, height, red, green, blue, blend_red=0, blend_green=0, blend_blue=0, blend_alpha=0.1, density=0.7, weight=3, radius=1)\n\n"
     "Create a texture of the specified width and height from the specified color."
     " The texture is created by blending in random noise of the specified blend color into a flat image."
     " All colors are numbers between 0 and 255. 0 <= blend_alpha <= 1 with 0 being fully transparent."
     " 0 <= density <= 1 is used to control the amount of noise in the texture."
     " weight and radius control the Gaussian convolution used for blurring of the noise. weight must be an odd positive integer. Increasing the weight will "
     "tend to blur out the noise. Decreasing it will make it sharper."
     " This function returns an image (bytestring) in the PPM format as the texture."},

    {"websocket_mask",
     (PyCFunction)(void (*)(void))speedup_websocket_mask,
     METH_FASTCALL,
     "websocket_mask(data, mask [, offset=0)\n\nXOR the data (bytestring) with the specified (must be 4-byte bytestring) mask"},

    {"utf8_decode",
     (PyCFunction)(void (*)(void))utf8_decode,
     METH_FASTCALL,
     "utf8_decode(data, [, state=0, codep=0)\n\nDecode an UTF-8 bytestring, using a strict UTF-8 decoder, that unlike python does not allow orphaned "
     "surrogates. Returns a unicode object and the state."},

    {"clean_xml_chars", clean_xml_chars, METH_O, "clean_xml_chars(unicode_object)\n\nRemove codepoints in unicode_object that are not allowed in XML"},

    {"set_thread_name", (PyCFunction)(void (*)(void))set_thread_name, METH_FASTCALL, "set_thread_name(name)\n\nWrapper for pthread_setname_np"},

    {"pread_all",
     (PyCFunction)(void (*)(void))pread_all,
     METH_FASTCALL,
     "pread_all(fd, n, offset)\n\nRead upto n bytes from the specified fd at offset in a thread safe manner."
     " If less than n bytes are returned it means there were less than n bytes in the file at offset."
     " Only works with seekable regular files, not sockets/ttys/etc. Note that on Windows it moves the file pointer"
     " so cannot be mixed with calls to tell() or ordinary reads."},

    {"get_num_of_significant_chars", get_num_of_significant_chars, METH_O, "get_num_of_significant_chars(elem)\n\nGet the number of chars in specified tag"},

    {"barename", barename, METH_O, "barename(tag)\n\nGet bare tag name without namespace"},

    {"namespace", namespace, METH_O, "namespace(tag)\n\nGet namespace of the tag"},

    {NULL, NULL, 0, NULL}};

static int
exec_module(PyObject *module) {
    PyDateTime_IMPORT;
#ifndef _WIN32
    PyModule_AddIntConstant(module, "O_CLOEXEC", O_CLOEXEC);
#endif
    return 0;
}

static PyModuleDef_Slot slots[] = {{Py_mod_exec, exec_module}, {0, NULL}};

static struct PyModuleDef module_def = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "speedup",
    .m_doc = "Implementation of methods in C for speed.",
    .m_methods = speedup_methods,
    .m_slots = slots,
};

CALIBRE_MODINIT_FUNC
PyInit_speedup(void) { return PyModuleDef_Init(&module_def); }
