package Hypersonic::Future::Pool;

use strict;
use warnings;
use 5.010;

our $VERSION = '0.06';

use XS::JIT::Builder;
use Hypersonic::Future qw(MAX_FUTURES);

# Thread pool configuration
use constant {
    POOL_SIZE           => 8,
    QUEUE_SIZE          => 4096,
    OP_TYPE_CODE        => 1,
    OP_TYPE_DB_QUERY    => 2,
    OP_TYPE_DB_EXECUTE  => 3,
};

use Exporter 'import';
our @EXPORT_OK = qw(POOL_SIZE QUEUE_SIZE OP_TYPE_CODE OP_TYPE_DB_QUERY OP_TYPE_DB_EXECUTE);

# JIT state - our so Future.pm can set it
our $COMPILED = 0;
my $POOL_INITIALIZED = 0;

sub get_xs_functions {
    return {
        'Hypersonic::Future::Pool::_init'           => { source => 'xs_pool_init', is_xs_native => 1 },
        'Hypersonic::Future::Pool::_shutdown'       => { source => 'xs_pool_shutdown', is_xs_native => 1 },
        'Hypersonic::Future::Pool::_submit'         => { source => 'xs_pool_submit', is_xs_native => 1 },
        'Hypersonic::Future::Pool::_process_ready'  => { source => 'xs_pool_process_ready', is_xs_native => 1 },
        'Hypersonic::Future::Pool::get_notify_fd'   => { source => 'xs_pool_get_notify_fd', is_xs_native => 1 },
        'Hypersonic::Future::Pool::pending_count'   => { source => 'xs_pool_pending_count', is_xs_native => 1 },
        'Hypersonic::Future::Pool::is_initialized'  => { source => 'xs_pool_is_initialized', is_xs_native => 1 },
    };
}

sub generate_c_code {
    my ($class, $builder, $opts) = @_;

    $opts //= {};
    my $pool_size = $opts->{pool_size} // POOL_SIZE;
    my $queue_size = $opts->{queue_size} // QUEUE_SIZE;

    # System includes
    $builder->line('#include <stdlib.h>')
            ->line('#include <string.h>')
            ->line('#include <pthread.h>')
            ->line('#include <unistd.h>')
            ->line('#include <fcntl.h>')
            ->line('#include <errno.h>')
            ->blank;

    # Platform-specific notification
    $builder->line('#ifdef __linux__')
            ->line('#include <sys/eventfd.h>')
            ->line('#define USE_EVENTFD 1')
            ->line('#else')
            ->line('#define USE_EVENTFD 0')
            ->line('#endif')
            ->blank;

    # Defines
    $builder->line("#define POOL_SIZE $pool_size")
            ->line("#define QUEUE_SIZE $queue_size")
            ->line('#define OP_TYPE_CODE 1')
            ->line('#define OP_TYPE_DB_QUERY 2')
            ->line('#define OP_TYPE_DB_EXECUTE 3')
            ->blank;

    # ThreadPoolOp - a queued operation
    $builder->line('typedef struct ThreadPoolOp {')
            ->line('    int op_type;')
            ->line('    int future_slot;')
            ->line('    SV *code;              /* For CODE type: coderef to execute */')
            ->line('    SV *args;              /* Arrayref of arguments */')
            ->line('    SV *result;            /* Result after execution */')
            ->line('    char *error;           /* Error message if failed */')
            ->line('    int completed;         /* 1 when done */')
            ->line('    struct ThreadPoolOp *next;')
            ->line('} ThreadPoolOp;')
            ->blank;

    # ThreadPool
    $builder->line('typedef struct ThreadPool {')
            ->line('    pthread_t threads[POOL_SIZE];')
            ->line('    pthread_mutex_t mutex;')
            ->line('    pthread_cond_t cond;')
            ->line('    pthread_cond_t done_cond;')
            ->line('    ThreadPoolOp *queue_head;')
            ->line('    ThreadPoolOp *queue_tail;')
            ->line('    ThreadPoolOp *completed_head;')
            ->line('    ThreadPoolOp *completed_tail;')
            ->line('    int queue_count;')
            ->line('    int completed_count;')
            ->line('    int shutdown;')
            ->line('    int initialized;')
            ->line('    int notify_fd;')
            ->line('#if !USE_EVENTFD')
            ->line('    int notify_pipe[2];')
            ->line('#endif')
            ->line('} ThreadPool;')
            ->blank;

    # Global pool
    $builder->line('static ThreadPool pool = {0};')
            ->blank;

    # Helper: signal completion to main thread
    $builder->line('static void pool_signal_completion(void) {')
            ->line('#if USE_EVENTFD')
            ->line('    uint64_t val = 1;')
            ->line('    write(pool.notify_fd, &val, sizeof(val));')
            ->line('#else')
            ->line('    char c = 1;')
            ->line('    write(pool.notify_pipe[1], &c, 1);')
            ->line('#endif')
            ->line('}')
            ->blank;

    # Helper: clear notification
    $builder->line('static void pool_clear_notification(void) {')
            ->line('#if USE_EVENTFD')
            ->line('    uint64_t val;')
            ->line('    read(pool.notify_fd, &val, sizeof(val));')
            ->line('#else')
            ->line('    char buf[64];')
            ->line('    while (read(pool.notify_pipe[0], buf, sizeof(buf)) > 0) {}')
            ->line('#endif')
            ->line('}')
            ->blank;

    # Worker thread function
    # Workers just move ops from queue to completed and signal main thread.
    # Actual Perl code execution must happen in the main interpreter thread.
    $builder->line('static void* pool_worker(void *arg) {')
            ->line('    (void)arg;')
            ->line('    while (1) {')
            ->line('        ThreadPoolOp *op = NULL;')
            ->line('        pthread_mutex_lock(&pool.mutex);')
            ->line('        while (!pool.shutdown && !pool.queue_head) {')
            ->line('            pthread_cond_wait(&pool.cond, &pool.mutex);')
            ->line('        }')
            ->line('        if (pool.shutdown && !pool.queue_head) {')
            ->line('            pthread_mutex_unlock(&pool.mutex);')
            ->line('            break;')
            ->line('        }')
            ->line('        /* Dequeue */')
            ->line('        op = pool.queue_head;')
            ->line('        pool.queue_head = op->next;')
            ->line('        if (!pool.queue_head) pool.queue_tail = NULL;')
            ->line('        pool.queue_count--;')
            ->line('        op->next = NULL;')
            ->line('        /* Move to completed queue immediately */')
            ->line('        if (pool.completed_tail) {')
            ->line('            pool.completed_tail->next = op;')
            ->line('        } else {')
            ->line('            pool.completed_head = op;')
            ->line('        }')
            ->line('        pool.completed_tail = op;')
            ->line('        pool.completed_count++;')
            ->line('        pthread_mutex_unlock(&pool.mutex);')
            ->line('        /* Notify main thread that work is ready to execute */')
            ->line('        pool_signal_completion();')
            ->line('    }')
            ->line('    return NULL;')
            ->line('}')
            ->blank;

    # XS: init
    $builder->xs_function('xs_pool_init')
            ->xs_preamble
            ->line('if (pool.initialized) {')
            ->line('    ST(0) = sv_2mortal(newSViv(1));')
            ->line('    XSRETURN(1);')
            ->line('}')
            ->line('pthread_mutex_init(&pool.mutex, NULL);')
            ->line('pthread_cond_init(&pool.cond, NULL);')
            ->line('pthread_cond_init(&pool.done_cond, NULL);')
            ->line('#if USE_EVENTFD')
            ->line('pool.notify_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);')
            ->line('if (pool.notify_fd < 0) croak("eventfd() failed");')
            ->line('#else')
            ->line('if (pipe(pool.notify_pipe) < 0) croak("pipe() failed");')
            ->line('fcntl(pool.notify_pipe[0], F_SETFL, O_NONBLOCK);')
            ->line('fcntl(pool.notify_pipe[1], F_SETFL, O_NONBLOCK);')
            ->line('pool.notify_fd = pool.notify_pipe[0];')
            ->line('#endif')
            ->line('for (int i = 0; i < POOL_SIZE; i++) {')
            ->line('    if (pthread_create(&pool.threads[i], NULL, pool_worker, NULL) != 0) {')
            ->line('        croak("pthread_create failed");')
            ->line('    }')
            ->line('}')
            ->line('pool.initialized = 1;')
            ->line('ST(0) = sv_2mortal(newSViv(1));')
            ->xs_return('1')
            ->xs_end;

    # XS: shutdown
    $builder->xs_function('xs_pool_shutdown')
            ->xs_preamble
            ->line('if (!pool.initialized) {')
            ->line('    ST(0) = sv_2mortal(newSViv(0));')
            ->line('    XSRETURN(1);')
            ->line('}')
            ->line('pthread_mutex_lock(&pool.mutex);')
            ->line('pool.shutdown = 1;')
            ->line('pthread_cond_broadcast(&pool.cond);')
            ->line('pthread_mutex_unlock(&pool.mutex);')
            ->line('for (int i = 0; i < POOL_SIZE; i++) {')
            ->line('    pthread_join(pool.threads[i], NULL);')
            ->line('}')
            ->line('#if USE_EVENTFD')
            ->line('close(pool.notify_fd);')
            ->line('#else')
            ->line('close(pool.notify_pipe[0]);')
            ->line('close(pool.notify_pipe[1]);')
            ->line('#endif')
            ->line('pthread_mutex_destroy(&pool.mutex);')
            ->line('pthread_cond_destroy(&pool.cond);')
            ->line('pthread_cond_destroy(&pool.done_cond);')
            ->line('/* Reset pool struct for potential reinit */')
            ->line('memset(&pool, 0, sizeof(pool));')
            ->line('ST(0) = sv_2mortal(newSViv(1));')
            ->xs_return('1')
            ->xs_end;

    # XS: submit - queue an operation
    $builder->xs_function('xs_pool_submit')
            ->xs_preamble
            ->line('if (items < 3) croak("Usage: Pool->_submit($future_slot, $code, $args_aref)");')
            ->line('if (!pool.initialized) croak("Thread pool not initialized");')
            ->line('int future_slot = SvIV(ST(1));')
            ->line('SV *code = ST(2);')
            ->line('SV *args = (items > 3) ? ST(3) : NULL;')
            ->line('ThreadPoolOp *op = (ThreadPoolOp*)calloc(1, sizeof(ThreadPoolOp));')
            ->line('if (!op) croak("malloc failed");')
            ->line('op->op_type = OP_TYPE_CODE;')
            ->line('op->future_slot = future_slot;')
            ->line('op->code = SvREFCNT_inc(code);')
            ->line('if (args) op->args = SvREFCNT_inc(args);')
            ->line('pthread_mutex_lock(&pool.mutex);')
            ->line('if (pool.queue_tail) {')
            ->line('    pool.queue_tail->next = op;')
            ->line('} else {')
            ->line('    pool.queue_head = op;')
            ->line('}')
            ->line('pool.queue_tail = op;')
            ->line('pool.queue_count++;')
            ->line('pthread_cond_signal(&pool.cond);')
            ->line('pthread_mutex_unlock(&pool.mutex);')
            ->line('ST(0) = sv_2mortal(newSViv(1));')
            ->xs_return('1')
            ->xs_end;

    # XS: process_ready - process completed operations in main thread
    # Directly resolves futures via the shared registry
    $builder->xs_function('xs_pool_process_ready')
            ->xs_preamble
            ->line('if (!pool.initialized) { ST(0) = sv_2mortal(newSViv(0)); XSRETURN(1); }')
            ->line('pool_clear_notification();')
            ->line('int processed = 0;')
            ->line('while (1) {')
            ->line('    ThreadPoolOp *op = NULL;')
            ->line('    pthread_mutex_lock(&pool.mutex);')
            ->line('    if (pool.completed_head) {')
            ->line('        op = pool.completed_head;')
            ->line('        pool.completed_head = op->next;')
            ->line('        if (!pool.completed_head) pool.completed_tail = NULL;')
            ->line('        pool.completed_count--;')
            ->line('    }')
            ->line('    pthread_mutex_unlock(&pool.mutex);')
            ->line('    if (!op) break;')
            ->line('    /* Execute the code in main Perl thread */')
            ->line('    dSP;')
            ->line('    ENTER; SAVETMPS;')
            ->line('    PUSHMARK(SP);')
            ->line('    if (op->args && SvROK(op->args)) {')
            ->line('        AV *args_av = (AV*)SvRV(op->args);')
            ->line('        int len = av_len(args_av) + 1;')
            ->line('        for (int i = 0; i < len; i++) {')
            ->line('            SV **elem = av_fetch(args_av, i, 0);')
            ->line('            if (elem) XPUSHs(*elem);')
            ->line('        }')
            ->line('    }')
            ->line('    PUTBACK;')
            ->line('    int count = call_sv(op->code, G_EVAL | G_ARRAY);')
            ->line('    SPAGAIN;')
            ->line('    SV *errsv = ERRSV;')
            ->line('    /* Access future registry directly */')
            ->line('    extern FutureContext future_registry[];')
            ->line('    extern void future_invoke_callbacks(int, int);')
            ->line('    FutureContext *ctx = &future_registry[op->future_slot];')
            ->line('    if (SvTRUE(errsv)) {')
            ->line('        /* Failed - store error */')
            ->line('        STRLEN len;')
            ->line('        const char *msg = SvPV(errsv, len);')
            ->line('        ctx->fail_message = (char*)malloc(len + 1);')
            ->line('        memcpy(ctx->fail_message, msg, len);')
            ->line('        ctx->fail_message[len] = 0;')
            ->line('        ctx->state = FUTURE_STATE_FAILED;')
            ->line('        /* Pop any partial results */')
            ->line('        while (count-- > 0) POPs;')
            ->line('        PUTBACK;')
            ->line('        FREETMPS; LEAVE;')
            ->line('        future_invoke_callbacks(op->future_slot, FUTURE_CB_FAIL);')
            ->line('    } else if (count > 0) {')
            ->line('        /* Success with results */')
            ->line('        ctx->result_values = (SV**)malloc(count * sizeof(SV*));')
            ->line('        for (int i = count - 1; i >= 0; i--) {')
            ->line('            ctx->result_values[i] = SvREFCNT_inc(POPs);')
            ->line('        }')
            ->line('        ctx->result_count = count;')
            ->line('        ctx->state = FUTURE_STATE_DONE;')
            ->line('        PUTBACK;')
            ->line('        FREETMPS; LEAVE;')
            ->line('        future_invoke_callbacks(op->future_slot, FUTURE_CB_DONE);')
            ->line('    } else {')
            ->line('        /* Success with no results */')
            ->line('        ctx->state = FUTURE_STATE_DONE;')
            ->line('        PUTBACK;')
            ->line('        FREETMPS; LEAVE;')
            ->line('        future_invoke_callbacks(op->future_slot, FUTURE_CB_DONE);')
            ->line('    }')
            ->line('    /* Cleanup op */')
            ->line('    if (op->code) SvREFCNT_dec(op->code);')
            ->line('    if (op->args) SvREFCNT_dec(op->args);')
            ->line('    free(op);')
            ->line('    processed++;')
            ->line('}')
            ->line('ST(0) = sv_2mortal(newSViv(processed));')
            ->xs_return('1')
            ->xs_end;

    # XS: get_notify_fd
    $builder->xs_function('xs_pool_get_notify_fd')
            ->xs_preamble
            ->line('if (!pool.initialized) { ST(0) = sv_2mortal(newSViv(-1)); XSRETURN(1); }')
            ->line('ST(0) = sv_2mortal(newSViv(pool.notify_fd));')
            ->xs_return('1')
            ->xs_end;

    # XS: pending_count
    $builder->xs_function('xs_pool_pending_count')
            ->xs_preamble
            ->line('if (!pool.initialized) { ST(0) = sv_2mortal(newSViv(0)); XSRETURN(1); }')
            ->line('pthread_mutex_lock(&pool.mutex);')
            ->line('int count = pool.queue_count + pool.completed_count;')
            ->line('pthread_mutex_unlock(&pool.mutex);')
            ->line('ST(0) = sv_2mortal(newSViv(count));')
            ->xs_return('1')
            ->xs_end;

    # XS: is_initialized
    $builder->xs_function('xs_pool_is_initialized')
            ->xs_preamble
            ->line('ST(0) = boolSV(pool.initialized);')
            ->xs_return('1')
            ->xs_end;
}

sub compile {
    my ($class, %opts) = @_;

    return 1 if $COMPILED;

    # Pool is compiled together with Future - just call Future's compile
    Hypersonic::Future->compile(%opts);

    return 1;
}


sub init {
    my $class = shift;
    $class->compile unless $COMPILED;
    return $class->_init;
}

sub shutdown {
    my $class = shift;
    return 0 unless $COMPILED;
    return $class->_shutdown;
}

sub submit {
    my ($class, $future, $code, $args) = @_;

    $class->compile unless $COMPILED;

    # Get future slot
    my $slot;
    if (ref($future) eq 'Hypersonic::Future') {
        # Extract slot from future object (blessed scalar)
        $slot = $$future;
    } else {
        $slot = $future;
    }

    return $class->_submit($slot, $code, $args);
}

sub process_ready {
    my $class = shift;
    return 0 unless $COMPILED;
    return $class->_process_ready;
}

1;

__END__

=head1 NAME

Hypersonic::Future::Pool - Thread pool for async operations

=head1 SYNOPSIS

    use Hypersonic::Future::Pool;
    use Hypersonic::Future;

    # Initialize pool
    Hypersonic::Future::Pool->init;

    # Submit work
    my $f = Hypersonic::Future->new;
    Hypersonic::Future::Pool->submit($f, sub {
        # This runs in the main thread when worker signals ready
        return expensive_computation(@_);
    }, [$arg1, $arg2]);

    # Process completed work (in event loop)
    my $fd = Hypersonic::Future::Pool->get_notify_fd;
    # When fd is readable:
    Hypersonic::Future::Pool->_process_ready;

    # Cleanup
    Hypersonic::Future::Pool->shutdown;

=head1 DESCRIPTION

C<Hypersonic::Future::Pool> provides a thread pool for offloading blocking
operations. Worker threads signal completion via eventfd (Linux) or pipe
(macOS/BSD), and the actual Perl code execution happens in the main thread
to avoid Perl interpreter threading issues.

=cut
