
//Musk
#include "header.h"

#define CACHE_SIZE 4096
#define CACHE_MASK 4095

#ifndef KUMMER_ENABLE_CACHE
#define KUMMER_ENABLE_CACHE 1
#endif

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
#define KUMMER_TLS _Thread_local
#else
#define KUMMER_TLS
#endif

typedef enum {
    KUMMER_FAST = 0,
    KUMMER_BALANCED = 1,
    KUMMER_ACCURATE = 2
} KummerMode;

typedef struct {
    KummerMode mode;
    double tol;
    double epsabs;
    double epsrel;
    int max_iter_series;
    int max_iter_regular;
    int max_iter_asympt;
    int quad_limit;
    int min_digits_required;
} KummerConfig;

static inline KummerConfig kummer_make_config(KummerMode mode) {
    switch (mode) {
        case KUMMER_FAST:
            return (KummerConfig){KUMMER_FAST, 1e-8, 1e-8, 1e-8, 80, 80, 16, 60, 4};
        case KUMMER_ACCURATE:
            return (KummerConfig){KUMMER_ACCURATE, 1e-14, 1e-13, 1e-13, 500, 150, 40, 150, 10};
        case KUMMER_BALANCED:
        default:
            return (KummerConfig){KUMMER_BALANCED, 1e-11, 1e-10, 1e-10, 150, 120, 24, 100, 7};
    }
}

static KUMMER_TLS KummerConfig current_kummer_config = {KUMMER_BALANCED, 1e-11, 1e-10, 1e-10, 150, 120, 24, 100, 7};
static inline const KummerConfig *kummer_cfg(void) { return &current_kummer_config; }

typedef struct {
    double a, b, c, result;
    uint64_t hash;
    uint32_t timestamp;
    uint16_t valid;
    uint16_t hits;
    uint16_t mode;
} KummerCacheEntry;
static KUMMER_TLS KummerCacheEntry cache[CACHE_SIZE];
static KUMMER_TLS int cache_initialized = 0;
static KUMMER_TLS uint32_t global_timestamp = 0;
static KUMMER_TLS uint32_t cache_hits = 0, cache_misses = 0;
// Hash function
static inline uint64_t fast_hash_v2(double a, double b, double c) {
    uint64_t ia, ib, ic;
    memcpy(&ia, &a, sizeof(uint64_t));
    memcpy(&ib, &b, sizeof(uint64_t));
    memcpy(&ic, &c, sizeof(uint64_t));
   
    ia &= 0x7FFFFFFFFFFFFFFFULL;
    ic &= 0x7FFFFFFFFFFFFFFFULL;
   
    uint64_t hash = ia * 0x9E3779B97F4A7C15ULL;
    hash ^= ib + 0x9E3779B97F4A7C15ULL + (hash << 6) + (hash >> 2);
    hash ^= ic + 0x9E3779B97F4A7C15ULL + (hash << 6) + (hash >> 2);
   
    hash ^= ((uint64_t)kummer_cfg()->mode + 1ULL) * 0xBF58476D1CE4E5B9ULL;
    return hash;
}
// init cache
static inline void init_cache_optimized(void) {
    if (!cache_initialized) {
        memset(cache, 0, sizeof(cache));
        cache_initialized = 1;
        global_timestamp = 1;
        cache_hits = cache_misses = 0;
    }
}
// Cache lookup
static inline int cache_lookup_fast(double a, double b, double c, double *result) {
#if !KUMMER_ENABLE_CACHE
    (void)a; (void)b; (void)c; (void)result;
    return 0;
#else
    const KummerConfig *cfg = kummer_cfg();
    init_cache_optimized();
    uint64_t hash = fast_hash_v2(a, b, c);
    int idx = hash & CACHE_MASK;
   
    KummerCacheEntry *entry = &cache[idx];
   
    if (entry->valid && entry->hash == hash && entry->mode == (uint16_t)cfg->mode) {
        if (entry->a == a && entry->b == b && entry->c == c) {
            *result = entry->result;
            entry->timestamp = ++global_timestamp;
            entry->hits++;
            cache_hits++;
            return 1;
        }
    }
   
    cache_misses++;
    return 0;
#endif
}
// Cache store
static inline void cache_store_smart(double a, double b, double c, double result) {
#if !KUMMER_ENABLE_CACHE
    (void)a; (void)b; (void)c; (void)result;
    return;
#else
    const KummerConfig *cfg = kummer_cfg();
    uint64_t hash = fast_hash_v2(a, b, c);
    int idx = hash & CACHE_MASK;
   
    KummerCacheEntry *entry = &cache[idx];
   
    if (entry->valid) {
        uint32_t age = global_timestamp - entry->timestamp;
        if (entry->hits > 3 && age < 1000) {
            for (int i = 1; i < 8; i++) {
                int alt_idx = (idx + i) & CACHE_MASK;
                KummerCacheEntry *alt = &cache[alt_idx];
                if (!alt->valid || (global_timestamp - alt->timestamp) > age) {
                    entry = alt;
                    break;
                }
            }
        }
    }
   
    entry->a = a;
    entry->b = b;
    entry->c = c;
    entry->result = result;
    entry->hash = hash;
    entry->timestamp = ++global_timestamp;
    entry->valid = 1;
    entry->hits = 0;
    entry->mode = (uint16_t)cfg->mode;
#endif
}
// Parametri
typedef struct {
    double a, b, x, c;
    double a_minus_1, b_minus_a_minus_1, neg_x;
    double inv_gamma_a;
} OptimizedParams;
static inline void setup_params(OptimizedParams *p, double a, double b, double x, double c) {
    p->a = a;
    p->b = b;
    p->x = x;
    p->c = c;
    p->a_minus_1 = a - 1.0;
    p->b_minus_a_minus_1 = b - a - 1.0;
    p->neg_x = -x;
    p->inv_gamma_a = exp(-lgammafn(a));
}
// Convergence
static inline int fast_converged(double current, double previous, double threshold) {
    double diff = fabs(current - previous);
    if (diff <= 1e-300) return 1;
    if (current == 0.0) return 0;
    return (diff <= threshold * fabs(current));
}
// MIGLIORAMENTO 1: Potenza con controllo overflow/underflow
static inline double optimized_pow(double base, double exp) {
    if (exp == 0.0) return 1.0;
    if (exp == 1.0) return base;
    if (exp == 2.0) return base * base;
    if (exp == 0.5) return sqrt(base);
    if (exp == -1.0) return 1.0 / base;
    if (exp == -0.5) return 1.0 / sqrt(base);
   
    if (base > 0.0) {
        double log_result = exp * log(base);
        if (log_result > 700.0) return INFINITY;
        if (log_result < -700.0) return 0.0;
    }
   
    return R_pow(base, exp);
}
//
static inline double kahan_sum(double *values, int n) {
    if (n == 0) return 0.0;
    if (n == 1) return values[0];
   
    double sum = 0.0;
    double c = 0.0;
   
    for (int i = 0; i < n; i++) {
        double y = values[i] - c;
        double t = sum + y;
        c = (t - sum) - y;
        sum = t;
    }
   
    return sum;
}
// Integrand 1
void integrand1_opt(double *t, int n, void *params) {
    OptimizedParams *p = (OptimizedParams*)params;
    const double a_m1 = p->a_minus_1;
    const double b_ma_m1 = p->b_minus_a_minus_1;
    const double neg_x = p->neg_x;
   
    for(int i = 0; i < n; i++) {
        const double ti = t[i];
        const double exp_term = exp(neg_x * ti);
        const double pow_term1 = optimized_pow(ti, a_m1);
        const double pow_term2 = optimized_pow(1.0 + ti, b_ma_m1);
        t[i] = exp_term * pow_term1 * pow_term2;
    }
}
// Integrand 2
void integrand2_opt(double *u, int n, void *params) {
    OptimizedParams *p = (OptimizedParams*)params;
    const double a_m1 = p->a_minus_1;
    const double b_ma_m1 = p->b_minus_a_minus_1;
    const double neg_x = p->neg_x;
    const double c = p->c;
   
    for(int i = 0; i < n; i++) {
        const double ui = u[i];
        const double one_minus_u = 1.0 - ui;
        const double t = c / one_minus_u;
        const double jacobian = c / (one_minus_u * one_minus_u);
        const double exp_term = exp(neg_x * t);
        const double pow_term1 = optimized_pow(t, a_m1);
        const double pow_term2 = optimized_pow(1.0 + t, b_ma_m1);
        u[i] = jacobian * exp_term * pow_term1 * pow_term2;
    }
}
//
void chguit_opt(double a, double b, double x, double *hu, int *id) {
    *id = 9;
    *hu = 0.0;
   
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(x) || x <= 0.0) {
        *id = -1;
        return;
    }
   
    double c = fmax(15.0 / sqrt(x), 8.0);
    OptimizedParams params;
    setup_params(&params, a, b, x, c);
   
    if (!R_FINITE(params.inv_gamma_a)) {
        *id = -1;
        return;
    }
   
    double zero = 0.0, one = 1.0;
    const KummerConfig *cfg = kummer_cfg();
    double epsabs = cfg->epsabs;
    double epsrel = cfg->epsrel;
    int last = 0;
    int limit = cfg->quad_limit;
    int lenw = 4 * limit;
   
    /*
     * Temporary QUADPACK workspaces. chguit_opt() can be called many times
     * within one native entry point, so bracket R_alloc() with a local
     * allocation-stack watermark to avoid accumulating temporary memory.
     */
    void *vmax = vmaxget();
    int *iwork = (int *) R_alloc((size_t) limit, sizeof(int));
    double *work = (double *) R_alloc((size_t) lenw, sizeof(double));

    /* Preserve the previous zero-initialization semantics. */
    memset(iwork, 0, (size_t) limit * sizeof(int));
    memset(work, 0, (size_t) lenw * sizeof(double));
   
    double result1, abserr1;
    int neval1, ier1;
   
    Rdqags(integrand1_opt, &params, &zero, &c, &epsabs, &epsrel, &result1, &abserr1,
           &neval1, &ier1, &limit, &lenw, &last, iwork, work);
   
    if (ier1 > 1) {
        vmaxset(vmax);
        *id = -1;
        return;
    }
   
    result1 *= params.inv_gamma_a;
   
    double result2, abserr2;
    int neval2, ier2;
   
    Rdqags(integrand2_opt, &params, &zero, &one, &epsabs, &epsrel, &result2, &abserr2,
           &neval2, &ier2, &limit, &lenw, &last, iwork, work);
   
    if (ier2 > 1) {
        vmaxset(vmax);
        *id = -1;
        return;
    }
   
    result2 *= params.inv_gamma_a;
   
    // NUOVO: Somma con Kahan
    double results[2] = {result1, result2};
    *hu = kahan_sum(results, 2);
   
    double total_error = abserr1 + abserr2;
    if (*hu != 0.0 && total_error > 0.0) {
        *id = (int)fmax(1.0, -log10(total_error / fabs(*hu)));
    }
   
    vmaxset(vmax);
}

// CHGUS
void chgus_opt(double a, double b, double x, double *hu, int *id) {
    *id = -100;
    *hu = 0.0;
   
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(x) || x <= 0.0) {
        *id = -1;
        return;
    }
   
    const double pi_val = M_PI;
   
    double ga = gammafn(a);
    double gb = gammafn(b);
    double gab = gammafn(1.0 + a - b);
    double gb2 = gammafn(2.0 - b);
   
    if (!R_FINITE(ga) || !R_FINITE(gb) || !R_FINITE(gab) || !R_FINITE(gb2)) {
        *id = -1;
        return;
    }
   
    double sin_pi_b = sin(pi_val * b);
    if (fabs(sin_pi_b) < 1e-15) {
        *id = -1;
        return;
    }
   
    double hu0 = pi_val / sin_pi_b;
    double r1 = hu0 / (gab * gb);
    double r2 = hu0 * optimized_pow(x, 1.0 - b) / (ga * gb2);
    *hu = r1 - r2;
   
    double hmax = fabs(*hu), hmin = fabs(*hu);
   
    double a_k = a;
    double b_k = b;
    double ab_k = a - b + 1.0;
    double b1_k = 2.0 - b;
   
    const KummerConfig *cfg = kummer_cfg();
    for (int j = 1; j <= cfg->max_iter_regular; j++) {
        r1 *= (a_k / ((double)j * b_k)) * x;
        r2 *= (ab_k / ((double)j * b1_k)) * x;
       
        double prev_hu = *hu;
        *hu += r1 - r2;
       
        double hua = fabs(*hu);
        if (hua > hmax) hmax = hua;
        if (hua < hmin && hua > 0.0) hmin = hua;
       
        if (fast_converged(*hu, prev_hu, cfg->tol)) break;
       
        a_k += 1.0;
        b_k += 1.0;
        ab_k += 1.0;
        b1_k += 1.0;
    }
   
    if (hmin > 0.0 && hmax > 0.0) {
        double d1 = log10(hmax);
        double d2 = log10(hmin);
        *id = (int)(15 - fabs(d1 - d2));
    } else {
        *id = 10;
    }
}
// CHGUL
void chgul_opt(double a, double b, double x, double *hu, int *id) {
    *id = -100;
    *hu = 0.0;
   
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(x) || x <= 0.0) {
        *id = -1;
        return;
    }
   
    const KummerConfig *cfg = kummer_cfg();
    double aa = a - b + 1.0;
    int il1 = 0; // a > 0
    int il2 = (aa == floor(aa) && aa <= 0.0);
   
    int nm = 0;
    if (il1) nm = (int)fabs(a);
    if (il2) nm = (int)fabs(aa);
   
    if (il1 || il2) {
        *hu = 1.0;
        double r = 1.0;
        const double inv_x = 1.0 / x;
        double a_k = a;
        double ab_k = a - b + 1.0;
       
        for (int k = 1; k <= nm; k++) {
            r *= -a_k * ab_k * inv_x / (double)k;
            *hu += r;
            a_k += 1.0;
            ab_k += 1.0;
        }
        *hu = optimized_pow(x, -a) * (*hu);
        *id = 10;
    } else {
        *hu = 1.0;
        double r = 1.0, r0 = 0.0;
        const double inv_x = 1.0 / x;
        double a_k = a;
        double ab_k = a - b + 1.0;
       
        for (int k = 1; k <= cfg->max_iter_asympt; k++) {
            r *= -a_k * ab_k * inv_x / (double)k;
            double ra = fabs(r);
           
            if (k > 5 && (ra >= r0 || ra < cfg->tol * fabs(*hu))) break;
           
            r0 = ra;
            *hu += r;
            a_k += 1.0;
            ab_k += 1.0;
        }
        *id = (r0 > 0.0) ? (int)fmax(1.0, -log10(r0)) : 15;
        *hu = optimized_pow(x, -a) * (*hu);
    }
}
// CHGUBI
void chgubi_opt(double a, double b, double x, double *hu, int *id) {
    *id = -100;
    *hu = 0.0;
   
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(x) || x <= 0.0) {
        *id = -1;
        return;
    }
   
    const double EL = 0.5772156649015329;
    const double gamma_E = EL;
    const KummerConfig *cfg = kummer_cfg();
   
    int n = (int)fabs(b - 1.0);
    double rn = 1.0, rn1 = 1.0;
    for (int j = 1; j <= n; j++) {
        rn *= (double)j;
        if (j == n - 1) rn1 = rn;
    }
   
    double ps = digamma(a);
    double ga = gammafn(a);
   
    if (!R_FINITE(ps) || !R_FINITE(ga) || ga == 0.0) {
        *id = -1;
        return;
    }
   
    double a0, a1, a2, ua, ub, ga1;
   
    if (b > 0.0) {
        a0 = a;
        a1 = a - n;
        a2 = a1;
        ga1 = gammafn(a1);
        if (!R_FINITE(ga1) || ga1 == 0.0) {
            *id = -1;
            return;
        }
        ua = R_pow(-1.0, (double)(n - 1)) / (rn * ga1);
        ub = rn1 / ga * optimized_pow(x, -n);
    } else {
        a0 = a + n;
        a1 = a0;
        a2 = a;
        ga1 = gammafn(a1);
        if (!R_FINITE(ga1) || ga1 == 0.0) {
            *id = -1;
            return;
        }
        ua = R_pow(-1.0, (double)(n - 1)) / (rn * ga) * optimized_pow(x, n);
        ub = rn1 / ga1;
    }
   
    double hm1 = 1.0;
    double r = 1.0;
    double hmax = 1.0, hmin = 1.0;
   
    for (int k = 1; k <= cfg->max_iter_regular; k++) {
        r *= (a0 + k - 1.0) * x / ((n + k) * (double)k);
        double prev_hm1 = hm1;
        hm1 += r;
       
        double hu1 = fabs(hm1);
        if (hu1 > hmax) hmax = hu1;
        if (hu1 < hmin && hu1 > 0.0) hmin = hu1;
       
        if (fast_converged(hm1, prev_hm1, cfg->tol)) break;
    }
   
    *id = (hmin > 0.0 && hmax > 0.0) ? (int)(15 - fabs(log10(hmax) - log10(hmin))) : 10;
   
    hm1 *= log(x);
   
    // Ottimizzato s0 con digamma
    double s0 = 0.0;
    double H_a_m1 = digamma(a) + gamma_E;
    if (b >= 0.0) {
        double H_n = digamma((double)(n + 1)) + gamma_E;
        s0 = -H_n;
    } else {
        double H_n = digamma((double)(n + 1)) + gamma_E;
        double H_n_a_m1 = digamma((double)(n + a)) + gamma_E;
        s0 = -H_n + H_n_a_m1 - H_a_m1;
    }
   
    double hm2 = ps + 2.0 * EL + s0;
    r = 1.0;
   
    for (int k = 1; k <= cfg->max_iter_regular; k++) {
        double s1 = 0.0, s2 = 0.0;
       
        double H_k = digamma((double)(k + 1)) + gamma_E;
        if (b > 0.0) {
            double H_k_a_m1 = digamma((double)(k + a)) + gamma_E;
            s1 = - (2.0 * H_k - H_k_a_m1 + H_a_m1);
            double H_k_n = digamma((double)(k + n + 1)) + gamma_E;
            s2 = H_k_n - H_k;
        } else {
            double H_k_n = digamma((double)(k + n + 1)) + gamma_E;
            double H_k_n_a_m1 = digamma((double)(k + n + a)) + gamma_E;
            s1 = -H_k_n + H_k_n_a_m1 - H_a_m1;
            s2 = H_k;
        }
       
        double hw = 2.0 * EL + ps + s1 - s2;
        r *= (a0 + k - 1.0) * x / ((n + k) * (double)k);
        double prev_hm2 = hm2;
        hm2 += r * hw;
       
        if (fast_converged(hm2, prev_hm2, cfg->tol)) break;
    }
   
    double hm3 = (n == 0) ? 0.0 : 1.0;
    r = 1.0;
    for (int k = 1; k <= n - 1; k++) {
        r *= (a2 + k - 1.0) * x / ((k - n) * (double)k);
        hm3 += r;
    }
   
    double sa = ua * (hm1 + hm2);
    double sb = ub * hm3;
    *hu = sa + sb;
   
    if (sa * sb < 0.0 && sa != 0.0 && *hu != 0.0) {
        int id1 = (int)log10(fabs(sa));
        int id2 = (int)log10(fabs(*hu));
        *id = fmax(1, *id - abs(id1 - id2));
    }
}
// CHGU
void chgu_opt(double a, double b, double x, double *hu, int *md, int *isfer) {
    *isfer = 0;
    *hu = 0.0;
    *md = 0;
   
    double cached_result;
    if (cache_lookup_fast(a, b, x, &cached_result)) {
        *hu = cached_result;
        *md = 0;
        return;
    }
   
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(x) || x <= 0.0) {
        *isfer = -1;
        *hu = R_NaN;
        return;
    }
   
    const KummerConfig *cfg = kummer_cfg();
    double aa = a - b + 1.0;
   
    //int il1 = 0; // a > 0
    int il2 = (aa == floor(aa) && aa <= 0.0);
    int il3 = (fabs(a * aa) / x <= 2.0);
    int bn = (b == floor(b) && b != 0.0);
   
    double hu_best = 0.0;
    int id_best = -100;
    int md_best = 0;
   
    if (b != floor(b) && x <= 50.0) {
        int id;
        double hu_temp;
        chgus_opt(a, b, x, &hu_temp, &id);
        if (id > id_best) {
            id_best = id;
            hu_best = hu_temp;
            md_best = 1;
        }
    }
   
    if (il2 || il3) { // il1 = 0
        int id;
        double hu_temp;
        chgul_opt(a, b, x, &hu_temp, &id);
        if (id > id_best) {
            id_best = id;
            hu_best = hu_temp;
            md_best = 2;
        }
    }
   
    if (a >= 1.0 && bn && x <= 30.0) {
        int id;
        double hu_temp;
        chgubi_opt(a, b, x, &hu_temp, &id);
        if (id > id_best) {
            id_best = id;
            hu_best = hu_temp;
            md_best = 3;
        }
    }
   
    if (a >= 1.0 && id_best < cfg->min_digits_required) {
        int id;
        double hu_temp;
        chguit_opt(a, b, x, &hu_temp, &id);
        if (id > id_best) {
            id_best = id;
            hu_best = hu_temp;
            md_best = 4;
        }
    }
   
    if (a < 1.0 && id_best < cfg->min_digits_required) {
        if (b <= a) {
            double a00 = a, b00 = b;
            a = a - b + 1.0;
            b = 2.0 - b;
            int id;
            double hu_temp;
            chguit_opt(a, b, x, &hu_temp, &id);
            hu_temp = optimized_pow(x, 1.0 - b00) * hu_temp;
            if (id > id_best) {
                id_best = id;
                hu_best = hu_temp;
                md_best = 4;
            }
            a = a00;
            b = b00;
        } else if (bn) { // !il1 always true
            int id;
            double hu_temp;
            chgubi_opt(a, b, x, &hu_temp, &id);
            if (id > id_best) {
                id_best = id;
                hu_best = hu_temp;
                md_best = 3;
            }
        }
    }
   
    *hu = hu_best;
    *md = md_best;
   
    if (id_best <= 0 || !R_FINITE(hu_best)) {
        *isfer = 1;
        if (!R_FINITE(hu_best)) *hu = R_NaN;
    } else {
        cache_store_smart(a, b, x, *hu);
    }
}
// main function with selectable speed/accuracy tradeoff
double kummer_mode(double a, double b, double c, KummerMode mode) {
    current_kummer_config = kummer_make_config(mode);
    // Casi immediati
    if (a == 0.0) return 1.0;
    if (c == 0.0) return R_NaN;
    if (a == 1.0 && b == 1.0) return exp(c);
    if (!R_FINITE(a) || !R_FINITE(b) || !R_FINITE(c) || c <= 0.0) return R_NaN;
   
    // Check cache
    double cached_result;
    if (cache_lookup_fast(a, b, c, &cached_result)) {
        return cached_result;
    }
   
    //
    if (c > 1e50) {
        double result = optimized_pow(c, -a);
        if (current_kummer_config.mode != KUMMER_FAST) {
            double corr = 1.0 - a * (a - b + 1.0) / c;
            result *= corr;
        }
        cache_store_smart(a, b, c, result);
        return result;
    }
   
    // main case
    double result;
    int method, status;
    chgu_opt(a, b, c, &result, &method, &status);
   
    if (status == -1 || !R_FINITE(result)) {
        return R_NaN;
    }

    // Store in cache
    cache_store_smart(a, b, c, result);
    return result;
}

// Default: compromesso precisione/velocita.
double kummer(double a, double b, double c) {
    return kummer_mode(a, b, c, KUMMER_BALANCED);
}
