/*
    Copyright (C) 2018-2019 Daniel Schultz

    This file is part of FLINT.

    FLINT is free software: you can redistribute it and/or modify it under
    the terms of the GNU Lesser General Public License (LGPL) as published
    by the Free Software Foundation; either version 2.1 of the License, or
    (at your option) any later version.  See <http://www.gnu.org/licenses/>.
*/

#include "thread_pool.h"

thread_pool_t global_thread_pool;
int global_thread_pool_initialized = 0;


void * thread_pool_idle_loop(void * varg)
{
    thread_pool_entry_struct * arg = (thread_pool_entry_struct *) varg;

    goto thread_pool_Lock;

thread_pool_DoWork:

    _flint_set_num_workers(arg->max_workers);
    arg->fxn(arg->fxnarg);

thread_pool_Lock:

#if HAVE_PTHREAD
    pthread_mutex_lock(&arg->mutex);
#endif
    arg->working = 0;

thread_pool_CheckExit:

    if (arg->exit != 0)
        goto thread_pool_Unlock;

#if HAVE_PTHREAD
    pthread_cond_signal(&arg->sleep2);
    pthread_cond_wait(&arg->sleep1, &arg->mutex);
#endif

    if (arg->working == 0)
        goto thread_pool_CheckExit;

thread_pool_Unlock:

#if HAVE_PTHREAD
    pthread_mutex_unlock(&arg->mutex);
#endif

    if (arg->exit == 0)
        goto thread_pool_DoWork;

    flint_cleanup();

    return NULL;
}


void thread_pool_init(thread_pool_t T, slong size)
{
    slong i;
    thread_pool_entry_struct * D;
    size = FLINT_MAX(size, WORD(0));

#if HAVE_PTHREAD
    pthread_mutex_init(&T->mutex, NULL);
#endif
    T->length = size;

#if HAVE_CPU_SET_T && HAVE_PTHREAD
    if (0 != pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t),
                                                        &T->original_affinity))
    {
        CPU_ZERO(&T->original_affinity);
    }
#endif

    if (size == 0)
    {
        T->tdata = NULL;
        return;
    }

    D = (thread_pool_entry_struct *) flint_malloc(
                                      size * sizeof(thread_pool_entry_struct));
    T->tdata = D;

    for (i = 0; i < size; i++)
    {
#if HAVE_PTHREAD
        pthread_mutex_init(&D[i].mutex, NULL);
        pthread_cond_init(&D[i].sleep1, NULL);
        pthread_cond_init(&D[i].sleep2, NULL);
#endif
	D[i].idx = i;
        D[i].available = 1;
        D[i].fxn = NULL;
        D[i].fxnarg = NULL;
        D[i].working = -1;
	D[i].max_workers = 0;
        D[i].exit = 0;
#if HAVE_PTHREAD
        pthread_mutex_lock(&D[i].mutex);
        pthread_create(&D[i].pth, NULL, thread_pool_idle_loop, &D[i]);
	while (D[i].working != 0)
            pthread_cond_wait(&D[i].sleep2, &D[i].mutex);
        pthread_mutex_unlock(&D[i].mutex);
#endif
    }
}
