/*
 * -------------------------------------------------------------
 *
 * Module: semaphore.c
 *
 * Purpose:
 *	Semaphores aren't actually part of the PThreads standard.
 *	They are defined by the POSIX Standard:
 *
 *		POSIX 1003.1b-1993	(POSIX.1b)
 *
 * -------------------------------------------------------------
 *
 * Pthreads-win32 - POSIX Threads Library for Win32
 * Copyright (C) 1998 Ben Elliston and Ross Johnson
 * Copyright (C) 1999,2000,2001 Ross Johnson
 *
 * Contact Email: rpj@ise.canberra.edu.au
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/* ignore warning "unreferenced formal parameter" */
#ifdef _MSC_VER
#pragma warning( disable : 4100 )
#endif

#ifndef _UWIN
#   include <process.h>
#endif
#ifndef NEED_FTIME
#include <sys/timeb.h>
#endif
#include "pthread.h"
#include "semaphore.h"
#include "implement.h"

int
sem_init (sem_t * sem, int pshared, unsigned int value)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function initializes an unnamed semaphore. the
      *      initial value of the semaphore is 'value'
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      *      pshared
      * 	     if zero, this semaphore may only be shared between
      * 	     threads in the same process.
      * 	     if nonzero, the semaphore can be shared between
      * 	     processes
      *
      *      value
      * 	     initial value of the semaphore counter
      *
      * DESCRIPTION
      *      This function initializes an unnamed semaphore. The
      *      initial value of the semaphore is set to 'value'.
      *
      * RESULTS
      * 	     0		     successfully created semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOSPC	     a required resource has been exhausted,
      * 	     ENOSYS	     semaphores are not supported,
      * 	     EPERM	     the process lacks appropriate privilege
      *
      * ------------------------------------------------------
      */
{
  int result = 0;
  sem_t s;

  if (pshared != 0)
    {
      /*
       * Creating a semaphore that can be shared between
       * processes
       */
      result = EPERM;

    }
  else
    {

      s = (sem_t) calloc (1, sizeof (*s));

      if (NULL == s)
	{
	  result = ENOMEM;
	}

#ifdef NEED_SEM

      else
	{
	  s->value = value;
	  s->event = CreateEvent (NULL,
				  FALSE,	/* manual reset */
				  FALSE,	/* initial state */
				  NULL);
	  if (0 == s->event)
	    {
	      result = ENOSPC;
	    }
	  else
	    {
	      if (value != 0)
		{
		  SetEvent(s->event);
		}

	      InitializeCriticalSection(&s->sem_lock_cs);
	    }
	}

#else /* NEED_SEM */

      s->sem = CreateSemaphore (NULL,	     /* Always NULL */
				value,	     /* Initial value */
				0x7FFFFFFFL, /* Maximum value */
				NULL);	     /* Name */

      if (0 == s->sem)
	{
	  result = ENOSPC;
	}

#endif /* NEED_SEM */

    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

  *sem = s;

  return 0;

}				/* sem_init */


int
sem_destroy (sem_t * sem)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function destroys an unnamed semaphore.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      * DESCRIPTION
      *      This function destroys an unnamed semaphore.
      *
      * RESULTS
      * 	     0		     successfully destroyed semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOSYS	     semaphores are not supported,
      * 	     EBUSY	     threads (or processes) are currently
      * 				     blocked on 'sem'
      *
      * ------------------------------------------------------
      */
{
  int result = 0;
  sem_t s;

  if (sem == NULL || *sem == NULL)
    {
      result = EINVAL;
    }
  else
    {
      s = *sem;
      *sem = NULL;

#ifdef NEED_SEM

      if (! CloseHandle(s->event))
	{
	  *sem = s;
	  result = EINVAL;
	}
      else
	{
	  DeleteCriticalSection(&s->sem_lock_cs);
	}

#else /* NEED_SEM */

      if (! CloseHandle (s->sem))
	{
	  *sem = s;
	  result = EINVAL;
	}

#endif /* NEED_SEM */

    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

  free(s);

  return 0;

}				/* sem_destroy */


int
sem_trywait (sem_t * sem)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function tries to wait on a semaphore.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      * DESCRIPTION
      *      This function tries to wait on a semaphore. If the
      *      semaphore value is greater than zero, it decreases
      *      its value by one. If the semaphore value is zero, then
      *      this function returns immediately with the error EAGAIN
      *
      * RESULTS
      * 	     0		     successfully decreased semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EAGAIN	     the semaphore was already locked,
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOTSUP	     sem_trywait is not supported,
      * 	     EINTR	     the function was interrupted by a signal,
      * 	     EDEADLK	     a deadlock condition was detected.
      *
      * ------------------------------------------------------
      */
{
#ifdef NEED_SEM

  /*
   * not yet implemented!
   */
  errno = ENOTSUP;
  return -1;

#else /* NEED_SEM */

  int result = 0;

  if (sem == NULL || *sem == NULL)
    {
      result = EINVAL;
    }
  else if (WaitForSingleObject ((*sem)->sem, 0) == WAIT_TIMEOUT)
    {
      result = EAGAIN;
    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

  return 0;

#endif /* NEED_SEM */

}				/* sem_trywait */


#ifdef NEED_SEM

void 
ptw32_decrease_semaphore(sem_t * sem)
{
  register sem_t s = *sem;

  EnterCriticalSection(&s->sem_lock_cs);

  if (s->value != 0)
    {
      s->value--;
      if (s->value != 0)
	{
	  SetEvent(s->event);
	}
    }
  else
    {
      /* this case should not happen! */
    }

  LeaveCriticalSection(&s->sem_lock_cs);
}

BOOL 
ptw32_increase_semaphore(sem_t * sem, unsigned int n)
{
  BOOL result;
  register sem_t s = *sem;

  EnterCriticalSection(&s->sem_lock_cs);

  if (s->value + n > s->value)
    {
       s->value += n;
       SetEvent(s->event);
       result = TRUE;
    }
  else
    {
       result = FALSE;
    }

  LeaveCriticalSection(&s->sem_lock_cs);
  return result;
}

#endif /* NEED_SEM */

int
sem_wait (sem_t * sem)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function  waits on a semaphore.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      * DESCRIPTION
      *      This function waits on a semaphore. If the
      *      semaphore value is greater than zero, it decreases
      *      its value by one. If the semaphore value is zero, then
      *      the calling thread (or process) is blocked until it can
      *      successfully decrease the value or until interrupted by
      *      a signal.
      *
      * RESULTS
      * 	     0		     successfully decreased semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOSYS	     semaphores are not supported,
      * 	     EINTR	     the function was interrupted by a signal,
      * 	     EDEADLK	     a deadlock condition was detected.
      *
      * ------------------------------------------------------
      */
{
  int result = 0;

  if (sem == NULL || *sem == NULL)
    {
      result = EINVAL;
    }
  else
    {

#ifdef NEED_SEM

	result = pthreadCancelableWait ((*sem)->event);

#else /* NEED_SEM */

	result = pthreadCancelableWait ((*sem)->sem);

#endif /* NEED_SEM */

    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

#ifdef NEED_SEM

  ptw32_decrease_semaphore(sem);

#endif /* NEED_SEM */

  return 0;

}				/* sem_wait */


int
sem_timedwait (sem_t * sem, const struct timespec * abstime)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function waits on a semaphore possibly until
      *      'abstime' time.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      *      abstime
      * 	     pointer to an instance of struct timespec
      *
      * DESCRIPTION
      *      This function waits on a semaphore. If the
      *      semaphore value is greater than zero, it decreases
      *      its value by one. If the semaphore value is zero, then
      *      the calling thread (or process) is blocked until it can
      *      successfully decrease the value or until interrupted by
      *      a signal.
      *
      *      If 'abstime' is a NULL pointer then this function will
      *      block until it can successfully decrease the value or
      *      until interrupted by a signal.
      *
      * RESULTS
      * 	     0		     successfully decreased semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOSYS	     semaphores are not supported,
      * 	     EINTR	     the function was interrupted by a signal,
      * 	     EDEADLK	     a deadlock condition was detected.
      * 	     ETIMEDOUT	     abstime elapsed before success.
      *
      * ------------------------------------------------------
      */
{
  int result = 0;

#ifdef NEED_FTIME

  struct timespec currSysTime;

#else /* NEED_FTIME */

  struct _timeb currSysTime;

#endif /* NEED_FTIME */

  const DWORD NANOSEC_PER_MILLISEC = 1000000;
  const DWORD MILLISEC_PER_SEC = 1000;
  DWORD milliseconds;

  if (sem == NULL)
    {
      result = EINVAL;
    }
  else
    {
      if (abstime == NULL)
	{
	  milliseconds = INFINITE;
	}
      else
	{
	  /* 
	   * Calculate timeout as milliseconds from current system time. 
	   */

	  /* get current system time */

#ifdef NEED_FTIME

	  {
	    FILETIME ft;
	    SYSTEMTIME st;

	    GetSystemTime(&st);
	    SystemTimeToFileTime(&st, &ft);
	    /*
	     * GetSystemTimeAsFileTime(&ft); would be faster,
	     * but it does not exist on WinCE
	     */

	    filetime_to_timespec(&ft, &currSysTime);
	  }

	  /*
	   * subtract current system time from abstime
	   */
	  milliseconds = (abstime->tv_sec - currSysTime.tv_sec) * MILLISEC_PER_SEC;
	  milliseconds += ((abstime->tv_nsec - currSysTime.tv_nsec) + (NANOSEC_PER_MILLISEC/2)) / NANOSEC_PER_MILLISEC;

#else /* NEED_FTIME */
	  _ftime(&currSysTime);

	  /*
	   * subtract current system time from abstime
	   */
	  milliseconds = (abstime->tv_sec - currSysTime.time) * MILLISEC_PER_SEC;
	  milliseconds += ((abstime->tv_nsec + (NANOSEC_PER_MILLISEC/2)) / NANOSEC_PER_MILLISEC) -
	    currSysTime.millitm;

#endif /* NEED_FTIME */


	  if (((int) milliseconds) < 0)
	    milliseconds = 0;
	}

#ifdef NEED_SEM

      result = (pthreadCancelableTimedWait ((*sem)->event, milliseconds));

#else /* NEED_SEM */

      result = (pthreadCancelableTimedWait ((*sem)->sem, milliseconds));

#endif

    }

  if (result != 0)
    {

      errno = result;
      return -1;

    }

#ifdef NEED_SEM

  ptw32_decrease_semaphore(sem);

#endif /* NEED_SEM */

  return 0;

}				/* sem_timedwait */


int
sem_post (sem_t * sem)
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function posts a wakeup to a semaphore.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      * DESCRIPTION
      *      This function posts a wakeup to a semaphore. If there
      *      are waiting threads (or processes), one is awakened;
      *      otherwise, the semaphore value is incremented by one.
      *
      * RESULTS
      * 	     0		     successfully posted semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore,
      * 	     ENOSYS	     semaphores are not supported,
      *
      * ------------------------------------------------------
      */
{
  int result = 0;

  if (sem == NULL || *sem == NULL)
    {
	result = EINVAL;
    }

#ifdef NEED_SEM

  else if (! ptw32_increase_semaphore (sem, 1))

#else /* NEED_SEM */

  else if (! ReleaseSemaphore ((*sem)->sem, 1, 0))

#endif /* NEED_SEM */

    {
	result = EINVAL;
    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

  return 0;

}				/* sem_post */


int
sem_post_multiple (sem_t * sem, int count )
     /*
      * ------------------------------------------------------
      * DOCPUBLIC
      *      This function posts multiple wakeups to a semaphore.
      *
      * PARAMETERS
      *      sem
      * 	     pointer to an instance of sem_t
      *
      *      count
      * 	     counter, must be greater than zero.
      *
      * DESCRIPTION
      *      This function posts multiple wakeups to a semaphore. If there
      *      are waiting threads (or processes), n <= count are awakened;
      *      the semaphore value is incremented by count - n.
      *
      * RESULTS
      * 	     0		     successfully posted semaphore,
      * 	     -1 	     failed, error in errno
      * ERRNO
      * 	     EINVAL	     'sem' is not a valid semaphore
      * 			     or count is less than or equal to zero.
      *
      * ------------------------------------------------------
      */
{
  int result = 0;

  if (sem == NULL || *sem == NULL || count <= 0)
    {
      result = EINVAL;
    }

#ifdef NEED_SEM

  else if (! ptw32_increase_semaphore (sem, count))

#else /* NEED_SEM */

  else if (! ReleaseSemaphore ((*sem)->sem, count, 0))

#endif /* NEED_SEM */

    {
      result = EINVAL;
    }

  if (result != 0)
    {
      errno = result;
      return -1;
    }

  return 0;

}				/* sem_post_multiple */


int
sem_open (const char * name, int oflag, mode_t mode, unsigned int value)
{
  errno = ENOSYS;
  return -1;
}				/* sem_open */


int
sem_close (sem_t * sem)
{
  errno = ENOSYS;
  return -1;
}				/* sem_close */


int
sem_unlink (const char * name)
{
  errno = ENOSYS;
  return -1;
}				/* sem_unlink */


int
sem_getvalue (sem_t * sem, int * sval)
{
  errno = ENOSYS;
  return -1;
}				/* sem_getvalue */