/*         ______   ___    ___
 *        /\  _  \ /\_ \  /\_ \
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      Timer module for Unix, using pthreads.
 *
 *      By Peter Wang.
 *
 *      See readme.txt for copyright information.
 */


#include "allegro.h"
#include "allegro/internal/aintern.h"

/* See hack later.  */
#ifdef ALLEGRO_LINUX
#include <unistd.h>
#ifdef HAVE_SYS_IO_H
#include <sys/io.h>
#endif
#include "allegro/platform/aintlnx.h"
#endif



#ifdef HAVE_LIBPTHREAD

#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>


#define TIMER_TO_USEC(x)  ((long)((x) / 1.193181))
#define USEC_TO_TIMER(x)  ((long)((x) * (TIMERS_PER_SECOND / 1000000.)))


static int ptimer_init(void);
static void ptimer_exit(void);



TIMER_DRIVER timerdrv_unix_pthreads =
{
   TIMERDRV_UNIX_PTHREADS,
   empty_string,
   empty_string,
   "Unix pthreads timers",
   ptimer_init,
   ptimer_exit,
   NULL, NULL,		/* install_int, remove_int */
   NULL, NULL,		/* install_param_int, remove_param_int */
   NULL, NULL,		/* can_simulate_retrace, simulate_retrace */
   NULL			/* rest */
};



static pthread_t thread;
static int thread_alive;



static void block_all_signals(void)
{
   sigset_t mask;
   sigfillset(&mask);
   pthread_sigmask(SIG_BLOCK, &mask, NULL);
}



/* ptimer_thread_func:
 *  The timer thread.
 */
static void *ptimer_thread_func(void *unused)
{
   struct timeval old_time;
   struct timeval new_time;
   struct timeval delay;
   long interval = 0x8000;

   block_all_signals();

#ifdef ALLEGRO_LINUX
   /* privileges hack for Linux:
    *  One of the jobs of the timer thread is to update the mouse pointer
    *  on screen.  When using the Mode-X driver under Linux console, this
    *  involves selecting different planes (in modexgfx.s), which requires
    *  special priviledges.
    */
   if ((system_driver == &system_linux) && (__al_linux_have_ioperms)) {
      seteuid(0);
      iopl(3);
      seteuid(getuid());
   }
#endif

#ifdef ALLEGRO_QNX
   /* thread priority adjustment for QNX:
    *  The timer thread is set to the highest relative priority.
    *  (see the comment in src/qnx/qsystem.c about the scheduling policy)
    */
   {
      struct sched_param sparam;
      int spolicy;
   
      if (pthread_getschedparam(pthread_self(), &spolicy, &sparam) == EOK) {
         sparam.sched_priority += 4;
         pthread_setschedparam(pthread_self(), spolicy, &sparam);
      }
   }
#endif

   gettimeofday(&old_time, 0);

   while (thread_alive) {
      /* `select' is more accurate than `usleep' on my system.  */
      delay.tv_sec = interval / TIMERS_PER_SECOND;
      delay.tv_usec = TIMER_TO_USEC(interval) % 1000000L;
      select(0, NULL, NULL, NULL, &delay);

      /* Calculate actual time elapsed.  */
      gettimeofday(&new_time, 0);
      interval = USEC_TO_TIMER((new_time.tv_sec - old_time.tv_sec) * 1000000L
			       + (new_time.tv_usec - old_time.tv_usec));
      old_time = new_time;

      /* Handle a tick.  */
      interval = _handle_timer_tick(interval);
   }

   return NULL;
}



/* ptimer_init:
 *  Installs the timer thread.
 */
static int ptimer_init(void)
{
   thread_alive = 1;

   if (pthread_create(&thread, NULL, ptimer_thread_func, NULL) != 0) {
      thread_alive = 0;
      return -1;
   }

   return 0;
}



/* ptimer_exit:
 *  Shuts down the timer thread.
 */
static void ptimer_exit(void)
{
   if (thread_alive) {
      thread_alive = 0;
      pthread_join(thread, NULL);
   }
}


#endif
