#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#include "xmlrpc_config.h"

#include "bool.h"
#include "mallocvar.h"
#include "xmlrpc-c/string_int.h"
#include "pthreadx.h"

#include "xmlrpc-c/abyss.h"

#include "thread.h"



struct abyss_thread {
    pthread_t       thread;
    void *          userHandle;
    TThreadProc *   func;
    TThreadDoneFn * threadDone;
};

/* We used to have THREAD_STACK_SIZE = 16K, which was said to be the
   minimum stack size on Win32.  Scott Kolodzeski found in November
   2005 that this was insufficient for 64 bit Solaris -- we fail
   when creating the first thread.  So we changed to 128K.
*/
#define  THREAD_STACK_SIZE (128*1024L)


typedef void * (pthreadStartRoutine)(void *);



static pthreadStartRoutine pthreadStart;

static void *
pthreadStart(void * const arg) {

    struct abyss_thread * const threadP = arg;
    bool const executeTrue = true;

    pthread_cleanup_push(threadP->threadDone, threadP->userHandle);

    threadP->func(threadP->userHandle);

    pthread_cleanup_pop(executeTrue);

    return NULL;
}



void
ThreadCreate(TThread **      const threadPP,
             void *          const userHandle,
             TThreadProc   * const func,
             TThreadDoneFn * const threadDone,
             bool            const useSigchld ATTR_UNUSED,
             const char **   const errorP) {

    TThread * threadP;

    MALLOCVAR(threadP);
    if (threadP == NULL)
        xmlrpc_asprintf(errorP,
                        "Can't allocate memory for thread descriptor.");
    else {
        pthread_attr_t attr;
        int rc;

        pthread_attr_init(&attr);

        pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);
        
        threadP->userHandle = userHandle;
        threadP->func       = func;
        threadP->threadDone = threadDone;

        rc = pthread_create(&threadP->thread, &attr,
                            pthreadStart, threadP);
        if (rc == 0) {
            *errorP = NULL;
            *threadPP = threadP;
        } else
            xmlrpc_asprintf(
                errorP, "pthread_create() failed, errno = %d (%s)",
                errno, strerror(errno));
        
        pthread_attr_destroy(&attr);

        if (*errorP)
            free(threadP);
    }
}



bool
ThreadRun(TThread * const threadP ATTR_UNUSED) {
    return TRUE;    
}



bool
ThreadStop(TThread * const threadP ATTR_UNUSED) {
    return TRUE;
}



bool
ThreadKill(TThread * const threadP ATTR_UNUSED) {

    return (pthread_kill(threadP->thread, SIGTERM) == 0);
}



void
ThreadWaitAndRelease(TThread * const threadP) {

    void * threadReturn;

    pthread_join(threadP->thread, &threadReturn);

    free(threadP);
}



void
ThreadExit(int const retValue) {

    pthread_exit((void*)&retValue);

    /* Note that the above runs our cleanup routine (which we registered
       with pthread_cleanup_push() before exiting.
    */
}



void
ThreadRelease(TThread * const threadP) {

    pthread_detach(threadP->thread);

    free(threadP);
}



bool
ThreadForks(void) {

    return FALSE;
}



void
ThreadUpdateStatus(TThread * const threadP ATTR_UNUSED) {

    /* Threads keep their own statuses up to date, so there's nothing
       to do here.
    */
}



void
ThreadHandleSigchld(pid_t const pid ATTR_UNUSED) {

    /* Death of a child signals have nothing to do with pthreads */
}



/*********************************************************************
** Mutex
*********************************************************************/

struct abyss_mutex {
    pthread_mutex_t pthreadMutex;
};


bool
MutexCreate(TMutex ** const mutexPP) {

    TMutex * mutexP;
    bool succeeded;

    MALLOCVAR(mutexP);

    if (mutexP) {
        int rc;
        rc = pthread_mutex_init(&mutexP->pthreadMutex, NULL);

        succeeded = (rc == 0);
    } else
        succeeded = FALSE;

    if (!succeeded)
        free(mutexP);

    *mutexPP = mutexP;

    return succeeded;
}



bool
MutexLock(TMutex * const mutexP) {
    return (pthread_mutex_lock(&mutexP->pthreadMutex) == 0);
}



bool
MutexUnlock(TMutex * const mutexP) {
    return (pthread_mutex_unlock(&mutexP->pthreadMutex) == 0);
}



bool
MutexTryLock(TMutex * const mutexP) {
    return (pthread_mutex_trylock(&mutexP->pthreadMutex) == 0);
}



void
MutexDestroy(TMutex * const mutexP) {
    pthread_mutex_destroy(&mutexP->pthreadMutex);

    free(mutexP);
}
