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

#ifdef MTHREAD_STRICT
static struct __mthread_mutex *vm_front, *vm_rear;
static void mthread_mutex_add(mthread_mutex_t *m);
static void mthread_mutex_remove(mthread_mutex_t *m);
#else
# define mthread_mutex_add(m)		((*m)->mm_magic = MTHREAD_INIT_MAGIC)
# define mthread_mutex_remove(m)	((*m)->mm_magic = MTHREAD_NOT_INUSE)
#endif

/*===========================================================================*
 *				mthread_init_valid_mutexes		     *
 *===========================================================================*/
void mthread_init_valid_mutexes(void)
{
#ifdef MTHREAD_STRICT
/* Initialize list of valid mutexes */
  vm_front = vm_rear = NULL;
#endif
}


/*===========================================================================*
 *				mthread_mutex_add			     *
 *===========================================================================*/
#ifdef MTHREAD_STRICT
static void mthread_mutex_add(m) 
mthread_mutex_t *m;
{
/* Add mutex to list of valid, initialized mutexes */

  if (vm_front == NULL) {	/* Empty list */
  	vm_front = *m;
  	(*m)->mm_prev = NULL;
  } else {
  	vm_rear->mm_next = *m;
  	(*m)->mm_prev = vm_rear;
  }

  (*m)->mm_next = NULL;
  vm_rear = *m;
}
#endif

/*===========================================================================*
 *				mthread_mutex_destroy			     *
 *===========================================================================*/
int mthread_mutex_destroy(mutex)
mthread_mutex_t *mutex;
{
/* Invalidate mutex and deallocate resources. */

  mthread_thread_t t;
  mthread_tcb_t *tcb;

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

  if (mutex == NULL)
  	return(EINVAL);

  if (!mthread_mutex_valid(mutex)) 
  	return(EINVAL);
  else if ((*mutex)->mm_owner != NO_THREAD)
  	return(EBUSY);

  /* Check if this mutex is not associated with a condition */
  for (t = (mthread_thread_t) 0; t < no_threads; t++) {
  	tcb = mthread_find_tcb(t);
	if (tcb->m_state == MS_CONDITION) {
		if (tcb->m_cond != NULL && tcb->m_cond->mc_mutex == *mutex) 
			return(EBUSY);
	}
  }

  /* Not in use; invalidate it */
  mthread_mutex_remove(mutex);	
  free(*mutex);
  *mutex = NULL;

  return(0);
}


/*===========================================================================*
 *				mthread_mutex_init			     *
 *===========================================================================*/
int mthread_mutex_init(mutex, mattr)
mthread_mutex_t *mutex;	/* Mutex that is to be initialized */
mthread_mutexattr_t *mattr;	/* Mutex attribute */
{
/* Initialize the mutex to a known state. Attributes are not supported */

  struct __mthread_mutex *m;

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

  if (mutex == NULL)
  	return(EAGAIN);
  else if (mattr != NULL)
  	return(ENOSYS);
#ifdef MTHREAD_STRICT
  else if (mthread_mutex_valid(mutex))
  	return(EBUSY);
#endif
  else if ((m = malloc(sizeof(struct __mthread_mutex))) == NULL) 
  	return(ENOMEM);

  mthread_queue_init(&m->mm_queue);
  m->mm_owner = NO_THREAD;
  *mutex = (mthread_mutex_t) m;
  mthread_mutex_add(mutex); /* Validate mutex; mutex now in use */

  return(0);
}

/*===========================================================================*
 *				mthread_mutex_lock			     *
 *===========================================================================*/
int mthread_mutex_lock(mutex)
mthread_mutex_t *mutex;	/* Mutex that is to be locked */
{
/* Try to lock this mutex. If already locked, append the current thread to
 * FIFO queue associated with this mutex and suspend the thread. */

  struct __mthread_mutex *m;

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

  if (mutex == NULL)
  	return(EINVAL);

  m = (struct __mthread_mutex *) *mutex;
  if (!mthread_mutex_valid(&m)) 
  	return(EINVAL);
  else if (m->mm_owner == NO_THREAD) { /* Not locked */
	m->mm_owner = current_thread;
  } else if (m->mm_owner == current_thread) {
  	return(EDEADLK);
  } else {
	mthread_queue_add(&m->mm_queue, current_thread);
	mthread_suspend(MS_MUTEX);
  }

  /* When we get here we acquired the lock. */
  return(0);
}


/*===========================================================================*
 *				mthread_mutex_remove			     *
 *===========================================================================*/
#ifdef MTHREAD_STRICT
static void mthread_mutex_remove(m)
mthread_mutex_t *m;
{
/* Remove mutex from list of valid, initialized mutexes */

  if ((*m)->mm_prev == NULL)
  	vm_front = (*m)->mm_next;
  else
  	(*m)->mm_prev->mm_next = (*m)->mm_next;

  if ((*m)->mm_next == NULL)
  	vm_rear = (*m)->mm_prev;
  else
  	(*m)->mm_next->mm_prev = (*m)->mm_prev;
}
#endif

/*===========================================================================*
 *				mthread_mutex_trylock			     *
 *===========================================================================*/
int mthread_mutex_trylock(mutex)
mthread_mutex_t *mutex;	/* Mutex that is to be locked */
{
/* Try to lock this mutex and return OK. If already locked, return error. */

  struct __mthread_mutex *m;

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

  if (mutex == NULL) 
  	return(EINVAL);

  m = (struct __mthread_mutex *) *mutex;
  if (!mthread_mutex_valid(&m))
  	return(EINVAL);
  else if (m->mm_owner == current_thread)
	return(EDEADLK);
  else if (m->mm_owner == NO_THREAD) {
	m->mm_owner = current_thread;
	return(0);
  } 

  return(EBUSY);
}


/*===========================================================================*
 *				mthread_mutex_unlock			     *
 *===========================================================================*/
int mthread_mutex_unlock(mutex)
mthread_mutex_t *mutex;	/* Mutex that is to be unlocked */
{
/* Unlock a previously locked mutex. If there is a pending lock for this mutex 
 * by another thread, mark that thread runnable. */

  struct __mthread_mutex *m;

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

  if (mutex == NULL) 
	return(EINVAL);

  m = (struct __mthread_mutex *) *mutex;
  if (!mthread_mutex_valid(&m))
	return(EINVAL);
  else if (m->mm_owner != current_thread) 
  	return(EPERM);	/* Can't unlock a mutex locked by another thread. */

  m->mm_owner = mthread_queue_remove(&m->mm_queue);
  if (m->mm_owner != NO_THREAD) mthread_unsuspend(m->mm_owner);
  return(0);
}


/*===========================================================================*
 *				mthread_mutex_valid			     *
 *===========================================================================*/
#ifdef MTHREAD_STRICT
int mthread_mutex_valid(m)
mthread_mutex_t *m;
{
/* Check to see if mutex is on the list of valid mutexes */
  struct __mthread_mutex *loopitem;

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

  loopitem = vm_front;

  while (loopitem != NULL) {
	if (loopitem == *m)
		return(1);

	loopitem = loopitem->mm_next;
  }

  return(0);
}
#endif

/*===========================================================================*
 *				mthread_mutex_verify			     *
 *===========================================================================*/
#ifdef MDEBUG
int mthread_mutex_verify(void)
{
  /* Return true when no mutexes are in use */
  int r = 1;
  struct __mthread_mutex *loopitem;

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

#ifdef MTHREAD_STRICT
  loopitem = vm_front;

  while (loopitem != NULL) {
  	printf("mutex corruption: owner: %d\n", loopitem->mm_owner);
	loopitem = loopitem->mm_next;
  	r = 0;
  }
#endif

  return(r);
}
#endif


