#include <minix/mthread.h>
#include <string.h>
#include "global.h"
#include "proto.h"

static int keys_used = 0;
static struct {
  int used;
  int nvalues;
  void *mvalue;
  void **value;
  void (*destr)(void *);
} keys[MTHREAD_KEYS_MAX];

/*===========================================================================*
 *				mthread_init_keys			     *
 *===========================================================================*/
void mthread_init_keys(void)
{
/* Initialize the table of key entries.
 */
  mthread_key_t k;

  for (k = 0; k < MTHREAD_KEYS_MAX; k++)
	keys[k].used = FALSE;
}

/*===========================================================================*
 *				mthread_key_create			     *
 *===========================================================================*/
int mthread_key_create(mthread_key_t *key, void (*destructor)(void *))
{
/* Allocate a key.
 */
  mthread_key_t k;

  MTHREAD_CHECK_INIT();	/* Make sure libmthread is initialized */
  keys_used = 1;

  /* We do not yet allocate storage space for the values here, because we can
   * not estimate how many threads will be created in the common case that the
   * application creates keys before spawning threads.
   */
  for (k = 0; k < MTHREAD_KEYS_MAX; k++) {
	if (!keys[k].used) {
		keys[k].used = TRUE;
		keys[k].nvalues = 0;
		keys[k].mvalue = NULL;
		keys[k].value = NULL;
		keys[k].destr = destructor;
		*key = k;

		return(0);
	}
  }

  return(EAGAIN);
}

/*===========================================================================*
 *				mthread_key_delete			     *
 *===========================================================================*/
int mthread_key_delete(mthread_key_t key)
{
/* Free up a key, as well as any associated storage space.
 */

  MTHREAD_CHECK_INIT();	/* Make sure libmthread is initialized */

  if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used)
	return(EINVAL);

  free(keys[key].value);

  keys[key].used = FALSE;

  return(0);
}

/*===========================================================================*
 *				mthread_getspecific			     *
 *===========================================================================*/
void *mthread_getspecific(mthread_key_t key)
{
/* Get this thread's local value for the given key. The default is NULL.
 */

  MTHREAD_CHECK_INIT();	/* Make sure libmthread is initialized */

  if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used)
	return(NULL);

  if (current_thread == MAIN_THREAD)
	return keys[key].mvalue;

  if (current_thread < keys[key].nvalues)
	return(keys[key].value[current_thread]);

  return(NULL);
}

/*===========================================================================*
 *				mthread_setspecific			     *
 *===========================================================================*/
int mthread_setspecific(mthread_key_t key, void *value)
{
/* Set this thread's value for the given key. Allocate more resources as
 * necessary.
 */
  void **p;

  MTHREAD_CHECK_INIT();	/* Make sure libmthread is initialized */

  if (key < 0 || key >= MTHREAD_KEYS_MAX || !keys[key].used)
	return(EINVAL);

  if (current_thread == MAIN_THREAD) {
	keys[key].mvalue = value;

	return(0);
  }

  if (current_thread >= keys[key].nvalues) {
	if (current_thread >= no_threads)
		mthread_panic("Library state corrupt");

	if ((p = (void **) realloc(keys[key].value,
			sizeof(void*) * no_threads)) == NULL)
		return(ENOMEM);

	memset(&p[keys[key].nvalues], 0,
		sizeof(void*) * (no_threads - keys[key].nvalues));

	keys[key].nvalues = no_threads;
	keys[key].value = p;
  }

  keys[key].value[current_thread] = value;

  return(0);
}

/*===========================================================================*
 *				mthread_cleanup_values			     *
 *===========================================================================*/
void mthread_cleanup_values(void)
{
/* Clean up all the values associated with an exiting thread, calling keys'
 * destruction procedures as appropriate.
 */
  mthread_key_t k;
  void *value;
  int found;

  if (!keys_used) return;	/* Only clean up if we used any keys at all */

  /* Any of the destructors may set a new value on any key, so we may have to
   * loop over the table of keys multiple times. This implementation has no
   * protection against infinite loops in this case.
   */
  do {
	found = FALSE;

	for (k = 0; k < MTHREAD_KEYS_MAX; k++) {
		if (!keys[k].used) continue;
		if (keys[k].destr == NULL) continue;

		if (current_thread == MAIN_THREAD) {
			value = keys[k].mvalue;

			keys[k].mvalue = NULL;
		} else {
			if (current_thread >= keys[k].nvalues) continue;

			value = keys[k].value[current_thread];

			keys[k].value[current_thread] = NULL;
		}

		if (value != NULL) {
			/* Note: calling mthread_exit() from a destructor
			 * causes undefined behavior.
			 */
			keys[k].destr(value);

			found = TRUE;
		}
	}
  } while (found);
}
