#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <ulocks.h>
#include <errno.h>

#define HDR_SIZE        2680    /* sizeof(ushdr_t) */
#define MAXPROC         100     /* max # of threads that can be started */

static usptr_t *shared_arena;
static ulock_t count_lock;      /* protection for some variables */
static ulock_t wait_lock;       /* lock used to wait for other threads */
static int waiting_for_threads; /* protected by count_lock */
static int nthreads;            /* protected by count_lock */
static int exit_status;
static int exiting;             /* we're already exiting (for maybe_exit) */
static pid_t my_pid;            /* PID of main thread */
static struct pidlist {
    pid_t parent;
    pid_t child;
} pidlist[MAXPROC];     /* PIDs of other threads; protected by count_lock */
static int maxpidindex;         /* # of PIDs in pidlist */
/*
 * Initialization.
 */
static void PyThread__init_thread(void)
{
#ifdef USE_DL
    long addr, size;
#endif /* USE_DL */


#ifdef USE_DL
    if ((size = usconfig(CONF_INITSIZE, 64*1024)) < 0)
        perror("usconfig - CONF_INITSIZE (check)");
    if (usconfig(CONF_INITSIZE, size) < 0)
        perror("usconfig - CONF_INITSIZE (reset)");
    addr = (long) dl_getrange(size + HDR_SIZE);
    dprintf(("trying to use addr %p-%p for shared arena\n", addr, addr+size));
    errno = 0;
    if ((addr = usconfig(CONF_ATTACHADDR, addr)) < 0 && errno != 0)
        perror("usconfig - CONF_ATTACHADDR (set)");
#endif /* USE_DL */
    if (usconfig(CONF_INITUSERS, 16) < 0)
        perror("usconfig - CONF_INITUSERS");
    my_pid = getpid();          /* so that we know which is the main thread */
    if (usconfig(CONF_ARENATYPE, US_SHAREDONLY) < 0)
        perror("usconfig - CONF_ARENATYPE");
    usconfig(CONF_LOCKTYPE, US_DEBUG); /* XXX */
#ifdef Py_DEBUG
    if (thread_debug & 4)
        usconfig(CONF_LOCKTYPE, US_DEBUGPLUS);
    else if (thread_debug & 2)
        usconfig(CONF_LOCKTYPE, US_DEBUG);
#endif /* Py_DEBUG */
    if ((shared_arena = usinit(tmpnam(0))) == 0)
        perror("usinit");
#ifdef USE_DL
    if (usconfig(CONF_ATTACHADDR, addr) < 0) /* reset address */
        perror("usconfig - CONF_ATTACHADDR (reset)");
#endif /* USE_DL */
    if ((count_lock = usnewlock(shared_arena)) == NULL)
        perror("usnewlock (count_lock)");
    (void) usinitlock(count_lock);
    if ((wait_lock = usnewlock(shared_arena)) == NULL)
        perror("usnewlock (wait_lock)");
    dprintf(("arena start: %p, arena size: %ld\n",  shared_arena, (long) usconfig(CONF_GETSIZE, shared_arena)));
}

/*
 * Thread support.
 */

static void clean_threads(void)
{
    int i, j;
    pid_t mypid, pid;

    /* clean up any exited threads */
    mypid = getpid();
    i = 0;
    while (i < maxpidindex) {
        if (pidlist[i].parent == mypid && (pid = pidlist[i].child) > 0) {
            pid = waitpid(pid, 0, WNOHANG);
            if (pid > 0) {
                /* a thread has exited */
                pidlist[i] = pidlist[--maxpidindex];
                /* remove references to children of dead proc */
                for (j = 0; j < maxpidindex; j++)
                    if (pidlist[j].parent == pid)
                        pidlist[j].child = -1;
                continue; /* don't increment i */
            }
        }
        i++;
    }
    /* clean up the list */
    i = 0;
    while (i < maxpidindex) {
        if (pidlist[i].child == -1) {
            pidlist[i] = pidlist[--maxpidindex];
            continue; /* don't increment i */
        }
        i++;
    }
}

long PyThread_start_new_thread(void (*func)(void *), void *arg)
{
#ifdef USE_DL
    long addr, size;
    static int local_initialized = 0;
#endif /* USE_DL */
    int success = 0;            /* init not needed when SOLARIS_THREADS and */
                /* C_THREADS implemented properly */

    dprintf(("PyThread_start_new_thread called\n"));
    if (!initialized)
        PyThread_init_thread();
    switch (ussetlock(count_lock)) {
    case 0: return 0;
    case -1: perror("ussetlock (count_lock)");
    }
    if (maxpidindex >= MAXPROC)
        success = -1;
    else {
#ifdef USE_DL
        if (!local_initialized) {
            if ((size = usconfig(CONF_INITSIZE, 64*1024)) < 0)
                perror("usconfig - CONF_INITSIZE (check)");
            if (usconfig(CONF_INITSIZE, size) < 0)
                perror("usconfig - CONF_INITSIZE (reset)");
            addr = (long) dl_getrange(size + HDR_SIZE);
            dprintf(("trying to use addr %p-%p for sproc\n",
                     addr, addr+size));
            errno = 0;
            if ((addr = usconfig(CONF_ATTACHADDR, addr)) < 0 &&
                errno != 0)
                perror("usconfig - CONF_ATTACHADDR (set)");
        }
#endif /* USE_DL */
        clean_threads();
        if ((success = sproc(func, PR_SALL, arg)) < 0)
            perror("sproc");
#ifdef USE_DL
        if (!local_initialized) {
            if (usconfig(CONF_ATTACHADDR, addr) < 0)
                /* reset address */
                perror("usconfig - CONF_ATTACHADDR (reset)");
            local_initialized = 1;
        }
#endif /* USE_DL */
        if (success >= 0) {
            nthreads++;
            pidlist[maxpidindex].parent = getpid();
            pidlist[maxpidindex++].child = success;
            dprintf(("pidlist[%d] = %d\n",
                     maxpidindex-1, success));
        }
    }
    if (usunsetlock(count_lock) < 0)
        perror("usunsetlock (count_lock)");
    return success;
}

long PyThread_get_thread_ident(void)
{
    return getpid();
}

void PyThread_exit_thread(void)
{
    dprintf(("PyThread_exit_thread called\n"));
    if (!initialized)
        exit(0);
    if (ussetlock(count_lock) < 0)
        perror("ussetlock (count_lock)");
    nthreads--;
    if (getpid() == my_pid) {
        /* main thread; wait for other threads to exit */
        exiting = 1;
        waiting_for_threads = 1;
        if (ussetlock(wait_lock) < 0)
            perror("ussetlock (wait_lock)");
        for (;;) {
            if (nthreads < 0) {
                dprintf(("really exit (%d)\n", exit_status));
                exit(exit_status);
            }
            if (usunsetlock(count_lock) < 0)
                perror("usunsetlock (count_lock)");
            dprintf(("waiting for other threads (%d)\n", nthreads));
            if (ussetlock(wait_lock) < 0)
                perror("ussetlock (wait_lock)");
            if (ussetlock(count_lock) < 0)
                perror("ussetlock (count_lock)");
        }
    }
    /* not the main thread */
    if (waiting_for_threads) {
        dprintf(("main thread is waiting\n"));
        if (usunsetlock(wait_lock) < 0)
            perror("usunsetlock (wait_lock)");
    }
    if (usunsetlock(count_lock) < 0)
        perror("usunsetlock (count_lock)");
    _exit(0);
}

/*
 * Lock support.
 */
PyThread_type_lock PyThread_allocate_lock(void)
{
    ulock_t lock;

    dprintf(("PyThread_allocate_lock called\n"));
    if (!initialized)
        PyThread_init_thread();

    if ((lock = usnewlock(shared_arena)) == NULL)
        perror("usnewlock");
    (void) usinitlock(lock);
    dprintf(("PyThread_allocate_lock() -> %p\n", lock));
    return (PyThread_type_lock) lock;
}

void PyThread_free_lock(PyThread_type_lock lock)
{
    dprintf(("PyThread_free_lock(%p) called\n", lock));
    usfreelock((ulock_t) lock, shared_arena);
}

int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
    int success;

    dprintf(("PyThread_acquire_lock(%p, %d) called\n", lock, waitflag));
    errno = 0;                  /* clear it just in case */
    if (waitflag)
        success = ussetlock((ulock_t) lock);
    else
        success = uscsetlock((ulock_t) lock, 1); /* Try it once */
    if (success < 0)
        perror(waitflag ? "ussetlock" : "uscsetlock");
    dprintf(("PyThread_acquire_lock(%p, %d) -> %d\n", lock, waitflag, success));
    return success;
}

void PyThread_release_lock(PyThread_type_lock lock)
{
    dprintf(("PyThread_release_lock(%p) called\n", lock));
    if (usunsetlock((ulock_t) lock) < 0)
        perror("usunsetlock");
}