#include <stdlib.h>
#include <stdio.h>

#include "universe.h"
#include "thread.h"

// --- OS DETECTION ---
#ifdef _WIN32
  #include <windows.h>
  typedef HANDLE ThreadType;
#else
  #include <pthread.h>
  #include <unistd.h>
  typedef pthread_t ThreadType;
#endif

// --- GET CORES (if env var) ---
static int get_cores() {
    // A. Override to Environment Variable
    const char* env = getenv("THREADS");
    if (env) {
        int n = atoi(env);
        if (n > 0) return n;
    }

    // B. Fallback to Hardware Detection
    #ifdef _WIN32
        SYSTEM_INFO s; 
        GetSystemInfo(&s); 
        return s.dwNumberOfProcessors;
    #else
        long n = sysconf(_SC_NPROCESSORS_ONLN); 
        return (n > 0) ? (int)n : 1;
    #endif
}

// --- INTERNAL STRUCTURES ---
typedef struct { // ThreadJob ("briefcase" for each thread)
    Universe *u;
    int target;
    int control;
    double param;
    long long start;
    long long end;
    KernelFunc kernel; // Pointer to the specific math to run
} ThreadJob;

// --- THE GENERIC WORKER ---
// This is the only function the OS threads actually call. It unpacks the briefcase and runs the specific kernel.
#ifdef _WIN32
DWORD WINAPI generic_worker(LPVOID arg) {
#else
void* generic_worker(void *arg) {
#endif
    ThreadJob *job = (ThreadJob*)arg;
    
    // Execute the math kernel
    job->kernel(job->u, job->target, job->control, job->param, job->start, job->end);
    
    return 0;
}

// --- THREAD DISPATCHER ---
void dispatch_parallel(Universe *u, KernelFunc kernel, int target, int control, double param) {
    int num_cores = get_cores();
    if (u->dim < 2048) num_cores = 1; // Don't thread small tasks

    ThreadType *threads = malloc(sizeof(ThreadType) * num_cores);
    ThreadJob *jobs = malloc(sizeof(ThreadJob) * num_cores);
    
    long long chunk = u->dim / num_cores;

    for (int i = 0; i < num_cores; i++) {
        jobs[i].u = u;
        jobs[i].target = target;
        jobs[i].control = control;
        jobs[i].param = param;
        jobs[i].kernel = kernel; // Tell the thread which math to use
        jobs[i].start = i * chunk;
        jobs[i].end = (i == num_cores - 1) ? u->dim : (i + 1) * chunk;

        #ifdef _WIN32
            threads[i] = CreateThread(NULL, 0, generic_worker, &jobs[i], 0, NULL);
        #else
            pthread_create(&threads[i], NULL, generic_worker, &jobs[i]);
        #endif
    }

    // Wait for everyone
    for (int i = 0; i < num_cores; i++) {
        #ifdef _WIN32
            WaitForSingleObject(threads[i], INFINITE);
            CloseHandle(threads[i]);
        #else
            pthread_join(threads[i], NULL);
        #endif
    }

    free(threads);
    free(jobs);
}