#ifndef _OBFUSCATION_H_
#define _OBFUSCATION_H_

#include <stdint.h>
#include <string.h>
#include <time.h>

#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)
#include <immintrin.h>
#include <cpuid.h>
#define ARCH_X86
#endif

#if defined(__aarch64__) || defined(__arm__) || defined(_M_ARM64) || defined(_M_ARM)
#if defined(__ARM_NEON) || defined(__aarch64__)
#include <arm_neon.h>
#define ARCH_ARM_NEON
#endif
#endif

#define OBFUSCATION_VERSION     1

#define WG_TYPE_HANDSHAKE       0x01
#define WG_TYPE_HANDSHAKE_RESP  0x02
#define WG_TYPE_COOKIE          0x03
#define WG_TYPE_DATA            0x04

#define WG_TYPE(data) ((uint32_t)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)))
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

static uint8_t crc8_table[256];
static volatile int crc8_table_initialized = 0;

#if defined(__x86_64__) || defined(__aarch64__)
#define XOR_CACHE_ENTRIES 32
#else
#define XOR_CACHE_ENTRIES 8
#endif
#define XOR_CACHE_MAX_LEN 1500

typedef struct {
    int length;
    int key_length;
    uint8_t mask[XOR_CACHE_MAX_LEN];
} xor_cache_entry_t;

static _Thread_local xor_cache_entry_t xor_cache[XOR_CACHE_ENTRIES];
static _Thread_local int xor_cache_count = 0;
static int xor_cache_cap = XOR_CACHE_ENTRIES;

static inline void xor_set_cache_cap(int n) {
    if (n < 1) n = 1;
    if (n > XOR_CACHE_ENTRIES) n = XOR_CACHE_ENTRIES;
    xor_cache_cap = n;
}

static inline int xor_get_cache_cap(void) {
    return xor_cache_cap;
}

#ifdef ARCH_X86
static volatile int cpu_features_detected = 0;
static volatile int cpu_has_avx2 = 0;
static volatile int cpu_has_avx512f = 0;

static inline void detect_cpu_features(void) {
    if (cpu_features_detected) return;
    unsigned int eax, ebx, ecx, edx;
    if (__get_cpuid(7, &eax, &ebx, &ecx, &edx)) {
        cpu_has_avx2 = (ebx & (1 << 5)) != 0;
        cpu_has_avx512f = (ebx & (1 << 16)) != 0;
    }
    cpu_features_detected = 1;
}
#endif

static _Thread_local uint32_t rng_state = 0;

static inline void fast_rng_init(void) {
    if (rng_state) return;
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    rng_state = (uint32_t)(ts.tv_nsec ^ ts.tv_sec ^ (uintptr_t)&rng_state);
    if (rng_state == 0) rng_state = 1;
}

static inline uint32_t fast_rand(void) {
    uint32_t x = rng_state;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    rng_state = x;
    return x;
}

static inline void fast_rand_bytes(uint8_t *p, size_t n) {
    while (n >= 4) {
        uint32_t r = fast_rand();
        memcpy(p, &r, 4);
        p += 4; n -= 4;
    }
    if (n > 0) {
        uint32_t r = fast_rand();
        for (size_t i = 0; i < n; i++) p[i] = (r >> (i * 8)) & 0xFF;
    }
}

static inline void init_crc8_table(void) {
    if (crc8_table_initialized) return;
    for (int i = 0; i < 256; i++) {
        uint8_t crc = 0;
        uint8_t inbyte = i;
        for (int j = 0; j < 8; j++) {
            uint8_t mix = (crc ^ inbyte) & 0x01;
            crc >>= 1;
            if (mix) {
                crc ^= 0x8C;
            }
            inbyte >>= 1;
        }
        crc8_table[i] = crc;
    }
    crc8_table_initialized = 1;
    fast_rng_init();
#ifdef ARCH_X86
    detect_cpu_features();
#endif
}

static inline uint8_t is_obfuscated(uint8_t *data) {
    return data[0] < 1 || data[0] > 4 || data[1] | data[2] | data[3];
}

#ifdef ARCH_X86

__attribute__((target("avx512f")))
static void xor_apply_mask_avx512(uint8_t *buffer, const uint8_t *mask, int length) {
    int i = 0;
    for (; i + 64 <= length; i += 64) {
        __m512i b = _mm512_loadu_si512((const void *)(buffer + i));
        __m512i m = _mm512_loadu_si512((const void *)(mask + i));
        _mm512_storeu_si512((void *)(buffer + i), _mm512_xor_si512(b, m));
    }
    for (; i + 16 <= length; i += 16) {
        __m128i b = _mm_loadu_si128((const __m128i *)(buffer + i));
        __m128i m = _mm_loadu_si128((const __m128i *)(mask + i));
        _mm_storeu_si128((__m128i *)(buffer + i), _mm_xor_si128(b, m));
    }
    for (; i < length; i++) buffer[i] ^= mask[i];
}

__attribute__((target("avx2")))
static void xor_apply_mask_avx2(uint8_t *buffer, const uint8_t *mask, int length) {
    int i = 0;
    for (; i + 32 <= length; i += 32) {
        __m256i b = _mm256_loadu_si256((const __m256i *)(buffer + i));
        __m256i m = _mm256_loadu_si256((const __m256i *)(mask + i));
        _mm256_storeu_si256((__m256i *)(buffer + i), _mm256_xor_si256(b, m));
    }
    for (; i + 16 <= length; i += 16) {
        __m128i b = _mm_loadu_si128((const __m128i *)(buffer + i));
        __m128i m = _mm_loadu_si128((const __m128i *)(mask + i));
        _mm_storeu_si128((__m128i *)(buffer + i), _mm_xor_si128(b, m));
    }
    for (; i < length; i++) buffer[i] ^= mask[i];
}

static void xor_apply_mask_sse2(uint8_t *buffer, const uint8_t *mask, int length) {
    int i = 0;
    for (; i + 16 <= length; i += 16) {
        __m128i b = _mm_loadu_si128((const __m128i *)(buffer + i));
        __m128i m = _mm_loadu_si128((const __m128i *)(mask + i));
        _mm_storeu_si128((__m128i *)(buffer + i), _mm_xor_si128(b, m));
    }
    for (; i < length; i++) buffer[i] ^= mask[i];
}

#endif

static inline void xor_apply_mask(uint8_t *buffer, const uint8_t *mask, int length) {
#if defined(ARCH_X86)
    if (cpu_has_avx512f && length >= 64) {
        xor_apply_mask_avx512(buffer, mask, length);
    } else if (cpu_has_avx2 && length >= 32) {
        xor_apply_mask_avx2(buffer, mask, length);
    } else {
        xor_apply_mask_sse2(buffer, mask, length);
    }
#elif defined(ARCH_ARM_NEON)
    int i = 0;
    for (; i + 16 <= length; i += 16) {
        vst1q_u8(buffer + i, veorq_u8(vld1q_u8(buffer + i), vld1q_u8(mask + i)));
    }
    for (; i < length; i++) buffer[i] ^= mask[i];
#else
    int i = 0;
    const int step = (int)sizeof(size_t);
    for (; i + step <= length; i += step) {
        size_t b, m;
        memcpy(&b, buffer + i, sizeof(size_t));
        memcpy(&m, mask + i, sizeof(size_t));
        b ^= m;
        memcpy(buffer + i, &b, sizeof(size_t));
    }
    for (; i < length; i++) buffer[i] ^= mask[i];
#endif
}

static inline xor_cache_entry_t *xor_cache_find(int length, int key_length) {
    for (int i = 0; i < xor_cache_count; i++) {
        if (xor_cache[i].length == length && xor_cache[i].key_length == key_length) {
            return &xor_cache[i];
        }
    }
    return NULL;
}

static inline xor_cache_entry_t *xor_cache_alloc(int length, int key_length) {
    xor_cache_entry_t *entry = (xor_cache_count < xor_cache_cap)
        ? &xor_cache[xor_cache_count++]
        : &xor_cache[fast_rand() % xor_cache_cap];
    entry->length = length;
    entry->key_length = key_length;
    return entry;
}

static inline void xor_gen_apply(uint8_t *buffer, uint8_t *mask, int length, char *key, int key_length) {
    uint8_t crc = 0;
    uint8_t key_adj[256];
    const uint8_t base = (uint8_t)(length + key_length);
    for (int k = 0; k < key_length; k++) key_adj[k] = key[k] + base;
    int ki = 0;
    for (int i = 0; i < length; i++) {
        crc = crc8_table[crc ^ key_adj[ki]];
        mask[i] = crc;
        buffer[i] ^= crc;
        if (++ki >= key_length) ki = 0;
    }
}

static inline void xor_data_stream(uint8_t *buffer, int length, char *key, int key_length) {
    uint8_t key_adj[256];
    const uint8_t base = (uint8_t)(length + key_length);
    for (int k = 0; k < key_length; k++) key_adj[k] = key[k] + base;
    uint8_t crc = 0;
    int ki = 0, i = 0;
    uint8_t chunk[64];
    while (i < length) {
        int n = length - i;
        if (n > (int)sizeof(chunk)) n = (int)sizeof(chunk);
        for (int j = 0; j < n; j++) {
            crc = crc8_table[crc ^ key_adj[ki]];
            chunk[j] = crc;
            if (++ki >= key_length) ki = 0;
        }
        xor_apply_mask(buffer + i, chunk, n);
        i += n;
    }
}

static inline void xor_data(uint8_t *buffer, int length, char *key, int key_length) {
    if (!crc8_table_initialized) init_crc8_table();

    if (length <= XOR_CACHE_MAX_LEN) {
        xor_cache_entry_t *entry = xor_cache_find(length, key_length);
        if (entry) {
            xor_apply_mask(buffer, entry->mask, length);
        } else {
            entry = xor_cache_alloc(length, key_length);
            xor_gen_apply(buffer, entry->mask, length, key, key_length);
        }
    } else {
        xor_data_stream(buffer, length, key, key_length);
    }
}

typedef struct {
    uint8_t crc;
    int ki;
    int key_length;
    uint8_t key_adj[256];
} stream_cipher_t;

static inline void stream_cipher_init(stream_cipher_t *s, const char *key, int key_length) {
    if (!crc8_table_initialized) init_crc8_table();
    if (key_length > 256) key_length = 256;
    s->crc = 0;
    s->ki = 0;
    s->key_length = key_length;
    const uint8_t base = (uint8_t)key_length;
    for (int k = 0; k < key_length; k++) s->key_adj[k] = (uint8_t)key[k] + base;
}

static inline void stream_cipher_apply(stream_cipher_t *s, uint8_t *buffer, int length) {
    uint8_t crc = s->crc;
    int ki = s->ki;
    const uint8_t *key_adj = s->key_adj;
    const int key_length = s->key_length;
    uint8_t chunk[64];
    int i = 0;
    while (i < length) {
        int n = length - i;
        if (n > (int)sizeof(chunk)) n = (int)sizeof(chunk);
        for (int j = 0; j < n; j++) {
            crc = crc8_table[crc ^ key_adj[ki]];
            chunk[j] = crc;
            if (++ki >= key_length) ki = 0;
        }
        xor_apply_mask(buffer + i, chunk, n);
        i += n;
    }
    s->crc = crc;
    s->ki = ki;
}

static inline int encode(uint8_t *buffer, int length, char *key, int key_length, uint8_t version, int max_dummy_length_data, int obfuscate_bytes) {
    int partial = obfuscate_bytes > 0 && obfuscate_bytes < length;

    if (version >= 1) {
        uint32_t packet_type = WG_TYPE(buffer);
        uint8_t rnd = 1 + (fast_rand() % 255);
        buffer[0] ^= rnd;
        buffer[1] = rnd;
        uint16_t dummy_length = 0;
        if (!partial && length < MAX_DUMMY_LENGTH_TOTAL) {
            uint16_t max_dummy_length = MAX_DUMMY_LENGTH_TOTAL - length;
            switch (packet_type) {
                case WG_TYPE_HANDSHAKE:
                case WG_TYPE_HANDSHAKE_RESP:
                    dummy_length = fast_rand() % MIN(max_dummy_length, MAX_DUMMY_LENGTH_HANDSHAKE);
                    break;
                case WG_TYPE_COOKIE:
                case WG_TYPE_DATA:
                    if (max_dummy_length_data) {
                        dummy_length = fast_rand() % MIN(max_dummy_length, max_dummy_length_data);
                    }
                    break;
                default:
                    break;
            }
        }
        buffer[2] = dummy_length & 0xFF;
        buffer[3] = dummy_length >> 8;
        if (dummy_length > 0) {
            memset(buffer + length, 0xFF, dummy_length);
            length += dummy_length;
        }
    }

    xor_data(buffer, partial ? obfuscate_bytes : length, key, key_length);

    return length;
}

static inline int decode(uint8_t *buffer, int length, char *key, int key_length, uint8_t *version_out, int obfuscate_bytes) {
    int partial = obfuscate_bytes > 0 && obfuscate_bytes < length;

    xor_data(buffer, partial ? obfuscate_bytes : length, key, key_length);

    if (!is_obfuscated(buffer)) {
        *version_out = 0;
        return length;
    }

    buffer[0] ^= buffer[1];
    length -= (uint16_t)(buffer[2] | (buffer[3] << 8));
    buffer[1] = buffer[2] = buffer[3] = 0;
    return length;
}

#endif