/*
 * $Id: mrt.c,v 1.6 1999/11/11 01:16:16 labovit Exp $
 */

#include <string.h>
#include <time.h>
#include <signal.h>

#include <mrt.h>
#include <select.h>
#include <trace.h>
#include <timer.h>

mrt_t *MRT;

/* mrt_process_signal
 * invoke registered handler routines, and exit on sigint
 */
void mrt_process_signal (int sig) {

  trace (TR_WARN, MRT->trace, "MRT signal (%d) received\n", sig);
  if (sig == SIGINT) MRT->force_exit_flag = 1;
  /* Should we exit in all cases? */
  signal (sig, mrt_process_signal);
}


void
init_mrt_thread_signals ()
{
#ifdef HAVE_LIBPTHREAD
    sigset_t set;

    sigemptyset (&set);
    sigaddset (&set, SIGALRM);
    sigaddset (&set, SIGHUP);
    sigaddset (&set, SIGINT);
    sigaddset (&set, SIGPIPE);
    pthread_sigmask (SIG_BLOCK, &set, NULL);
#endif /* HAVE_LIBPTHREAD */
}


void
mrt_exit (status)
    int status;
{
    trace (INFO, MRT->trace, "exit (%d)\n", status);
    exit (status);
}
  

/* mrt_thread_create
 * a wrapper around pthread create, but also adds
 * thread to bookkeeping structures
 * Note: not locking down structure because (for moment) only main
 *       thread calls this routine. Maybe this will change.
 */
#ifdef notdef /* old code */
mrt_thread_t *mrt_thread_create (char *name, schedule_t *schedule,
				 thread_fn_t call_fn, void *arg) 
{
#ifdef HAVE_LIBPTHREAD
  static pthread_attr_t  tattr = { NULL };
  int rc, ret;
#endif /* HAVE_LIBPTHREAD */
  mrt_thread_t *mrt_thread;
  pthread_t thread = 0;

  mrt_thread = New (mrt_thread_t);
  mrt_thread->name = (name)? strdup (name): NULL;

#ifdef HAVE_LIBPTHREAD
#ifdef SOLARIS26
  /* we know this is solaris dependent way -- masaki */
  if (tattr.__pthread_attrp != NULL)
    rc = pthread_attr_destroy (&tattr);
#else
  if (tattr.pthread_attrp != NULL)
    rc = pthread_attr_destroy (&tattr);
#endif /* SOLARIS26 */
  /*pthread_attr_destroy (&tattr);  */
  ret = pthread_attr_init(&tattr);
  ret = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM);
  ret = pthread_attr_setdetachstate (&tattr, PTHREAD_CREATE_DETACHED);
  if (pthread_create (&thread, &tattr, (thread_fn_t) call_fn, arg) < 0) {
    trace (ERROR, MRT->trace, "ERROR -- Could not start %s thread: %s\n",
	   name, strerror (errno));
    return (NULL);
  }
#else
  thread = MRT->threadn++;
  set_thread_id (thread);
  if (schedule)
     schedule->self = thread;
  (*call_fn) (arg);
#endif /* HAVE_LIBPTHREAD */

  mrt_thread->thread = thread;
  mrt_thread->schedule = schedule;
  LL_Add (MRT->ll_threads, mrt_thread);
  return (mrt_thread);
}

#else /* new code */

/* I'm chaging a way to handle pthread_attr being passed to pthread_create ().
   man page doesn't say if I can destroy it right after pthread_create (),
   so I'm saving it into mrt_thread_t structure to delete it when exiting. */
/* This code is important so that I keep the older code for later reference */

mrt_thread_t *mrt_thread_create (char *name, schedule_t *schedule,
				 thread_fn_t call_fn, void *arg) 
{
  mrt_thread_t *mrt_thread;
  pthread_t thread;
  pthread_attr_t tattr;

  /* Locking is required to avoid a confusion 
     in case a created thread executes mrt_thread_exit()
     before adding a mrt_thread structure. */
  pthread_mutex_lock (&MRT->mutex_lock);

  pthread_attr_init (&tattr);
#ifdef HAVE_LIBPTHREAD
  /* as long as I remember, the following attributes are required
     to reuse a thread id. */
  pthread_attr_setscope (&tattr, PTHREAD_SCOPE_SYSTEM);
  pthread_attr_setdetachstate (&tattr, PTHREAD_CREATE_DETACHED);

  if (pthread_create (&thread, &tattr, (thread_fn_t) call_fn, arg) < 0) {
    trace (ERROR, MRT->trace, "ERROR -- Could not start %s thread: %s\n",
	   name, strerror (errno));
    pthread_mutex_unlock (&MRT->mutex_lock);
    pthread_attr_destroy (&tattr);
    return (NULL);
  }

#else
  thread = MRT->threadn++;
  /* a call should be delayed at the end */
#endif /* HAVE_LIBPTHREAD */

  mrt_thread = New (mrt_thread_t);
  /* this expects New () clears the memory */
  mrt_thread->name = (name)? strdup (name): NULL;
  mrt_thread->thread = thread;
  mrt_thread->schedule = schedule;
  mrt_thread->attr = tattr;
  LL_Add (MRT->ll_threads, mrt_thread);
  pthread_mutex_unlock (&MRT->mutex_lock);

#ifndef HAVE_LIBPTHREAD
  if (schedule)
     schedule->self = thread;
  set_thread_id (thread);
  (*call_fn) (arg);
#endif /* HAVE_LIBPTHREAD */
  return (mrt_thread);
}


static void
mrt_thread_process_schedule (event_t *event)
{
    schedule_t *schedule = event->args[1];

    init_mrt_thread_signals ();
    if (event->call_fn)
	event->call_fn (event->args[0]);

    Deref_Event (event);
    if (schedule == NULL)
	mrt_thread_exit ();
#ifdef HAVE_LIBPTHREAD
    while (1)
        schedule_wait_for_event (schedule);
    /* NOT REACHED */
#else
    /* We should put the schedule into the global linked list here
       so that they will be processed by the main program sequentially.
       But, they are already on the list when it was created. */
#endif /* HAVE_LIBPTHREAD */
}


mrt_thread_t *
mrt_thread_create2 (char *name, schedule_t *schedule,
		    thread_fn_t init_fn, void *arg) 
{
    event_t *event;

    /* here I borrowed event structure 
       since pthread_create can pass only one argument */
    event = New_Event (2);
    event->call_fn = (event_fn_t) init_fn;
    event->args[0] = arg;
    event->args[1] = schedule;
    return (mrt_thread_create (name, schedule, 
			       (thread_fn_t) mrt_thread_process_schedule, 
			       event));
}
#endif


/*
 * mrt_thread_exit
 */
void mrt_thread_exit () {
  mrt_thread_t *mrt_thread;
  pthread_t thread = pthread_self ();

  assert (thread != 0);
  pthread_mutex_lock (&MRT->mutex_lock);
  LL_Iterate (MRT->ll_threads, mrt_thread) {
     if (mrt_thread->thread == thread) {
        LL_Remove (MRT->ll_threads, mrt_thread);
#ifdef HAVE_LIBPTHREAD
  	pthread_attr_destroy (&mrt_thread->attr);
#endif /* HAVE_LIBPTHREAD */
        Delete (mrt_thread->name);
        Delete (mrt_thread);
	break;
     }
  }
  pthread_mutex_unlock (&MRT->mutex_lock);

#ifdef HAVE_LIBPTHREAD
  pthread_exit (NULL);
#endif /* HAVE_LIBPTHREAD */
}


#ifndef HAVE_LIBPTHREAD
/* use real assert */
#undef assert
#include </usr/include/assert.h>
int
pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
{
    assert (mutex);
    *mutex = 0;
    return (0);
}

int
pthread_mutex_lock (pthread_mutex_t *mutex)
{
    assert (mutex);
    while (*mutex == 1)
        mrt_busy_loop (mutex, 1);
    *mutex = 1;
    return (0);
}

int
pthread_mutex_trylock (pthread_mutex_t *mutex)
{
    assert (mutex);
    if (*mutex == 0) {
	*mutex = 1;
	return (0);
    }
    return (EBUSY);
}

int
pthread_mutex_unlock (pthread_mutex_t *mutex)
{
    assert (mutex);
    /* assert (*mutex == 1); */
    *mutex = 0;
    return (0);
}

int
pthread_mutex_destroy (pthread_mutex_t *mutex)
{
    assert (mutex);
    return (0);
}

int 
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr)
{
    assert (cond);
    *cond = 0;
    return (0);
}

int 
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex)
{
    assert (cond);
    assert (mutex);
    assert (*mutex == 1);
    while (*cond == 0) {
	pthread_mutex_unlock (mutex);
        mrt_busy_loop (cond, 0);
	pthread_mutex_lock (mutex);
    }
    return (0);
}

int 
pthread_cond_signal (pthread_cond_t *cond)
{
    assert (cond);
    /* assert (*cond == 0); */
    *cond = 1;
    return (0);
}

int 
pthread_cond_destroy (pthread_cond_t *cond)
{
    assert (cond);
    return (0);
}

#endif /* HAVE_LIBPTHREAD */


#ifndef HAVE_LIBPTHREAD
void mrt_busy_loop (int *force_exit_flag, int ok) {
  while (MRT->force_exit_flag == 0 && 
	    (force_exit_flag == NULL || *force_exit_flag == ok)) {
    /* process all first */
    while (MRT->force_exit_flag == 0 && 
		(force_exit_flag == NULL || *force_exit_flag == ok) &&
		process_all_schedules ());
    if (MRT->force_exit_flag == 0 &&
            (force_exit_flag == NULL || *force_exit_flag == ok))
        mrt_select ();
    if (MRT->force_exit_flag == 0 &&
            (force_exit_flag == NULL || *force_exit_flag == ok))
        mrt_alarm ();
  }
}
#endif /* HAVE_LIBPTHREAD */

void mrt_main_loop () {
#ifndef HAVE_LIBPTHREAD
  mrt_busy_loop (NULL, 0);
#else
   /* starts select thread seperately */
  /*start_select ();*/
  /* loop */
  /* XXX we need to implement a mechanism to wait mrt threds */
  while (MRT->force_exit_flag == 0) {
    /* main thread catches alarm interrupts by sigwait() */
    mrt_alarm ();
  }
#endif /* HAVE_LIBPTHREAD */
}


/* init_mrt
 * initialize MRT bookeeping structures. *ALL* MRT programs must call this 
 * routine.
 */
#ifdef HAVE_THR_SETCONCURRENCY
#include <thread.h>
#endif /* HAVE_THR_SETCONCURRENCY */

int init_mrt (trace_t *tr) {
   mrt_thread_t *mrt_thread;

   assert (MRT == NULL);
#ifdef HAVE_THR_SETCONCURRENCY
   /* NOTE: Solaris Thread, not Pthread */
   thr_setconcurrency (40);
#endif /* HAVE_THR_SETCONCURRENCY */

   signal (SIGPIPE, mrt_process_signal);
   signal (SIGHUP, mrt_process_signal);
   signal (SIGINT, mrt_process_signal);
   
   MRT = New (mrt_t);
   MRT->start_time = time (NULL);
   MRT->ll_threads = LL_Create (0);
   MRT->ll_trace = LL_Create (0);
   MRT->ll_signal_call_fn = LL_Create (0);
   MRT->ll_gateways = LL_Create (0);
   MRT->trace = tr;
   MRT->config_file_name = NULL;

   pthread_mutex_init (&MRT->mutex_lock, NULL);
   init_schedules (tr);

   mrt_thread = New (mrt_thread_t);
   mrt_thread->name = strdup ("MAIN thread");
   mrt_thread->schedule = NULL;
   mrt_thread->thread = pthread_self ();
   LL_Add (MRT->ll_threads, mrt_thread);

   MRT->threadn = 10; /* only used on non-thread capable machines */

   /* init_uii (tr); */
   init_timer (tr);
   init_select (tr);
#ifdef notdef
   /* some commands don't need information on own interfaces */
   init_interfaces (tr);
#endif

  return (1);
}
