/*
 * iaxclient: a cross-platform IAX softphone library
 *
 * Copyrights:
 * Copyright (C) 2003 HorizonLive.com, (c) 2004, Horizon Wimba, Inc.
 *
 * Contributors:
 * Steve Kann <stevek@stevek.com>
 *
 *
 * This program is free software, distributed under the terms of
 * the GNU Lesser (Library) General Public License
 */

#include <sys/time.h>
#include "iaxclient_lib.h"

#ifndef NULL
#define NULL (0)
#endif

/* Unix-specific functions */

void os_init(void)
{
}

void iaxc_millisleep(long ms)
{
	struct timespec req;

        req.tv_nsec=10*1000*1000;  /* 10 ms */
        req.tv_sec=0;

        /* yes, it can return early.  We don't care */
        nanosleep(&req,NULL);
}


/* TODO: Implement for X/MacOSX? */
int post_event_callback(iaxc_event ev) {
#if 0
  iaxc_event *e;
  e = malloc(sizeof(ev));
  *e = ev;

  /* XXX Test return value? */
  PostMessage(post_event_handle,post_event_id,(WPARAM) NULL, (LPARAM) e);
#endif
  return 0;
}

#ifdef MACOSX
    /* Presently, OSX allows user-level processes to request RT
     * priority.  The API is nice, but the scheduler presently ignores
     * the parameters (but the API validates that you're not asking for
     * too much).  See
     * http://lists.apple.com/archives/darwin-development/2004/Feb/msg00079.html
     */
int iaxc_prioboostbegin() {
      struct thread_time_constraint_policy ttcpolicy;
      int params [2] = {CTL_HW,HW_BUS_FREQ};
      int hzms;
      size_t sz;
      int ret;

      /* get hz */
      sz = sizeof (hzms);
      sysctl (params, 2, &hzms, &sz, NULL, 0);

      /* make hzms actually hz per ms */
      hzms /= 1000;

      /* give us at least how much? 6-8ms every 10ms (we generally need less) */
      ttcpolicy.period = 10 * hzms; /* 10 ms */
      ttcpolicy.computation = 2 * hzms;
      ttcpolicy.constraint = 3 * hzms;
      ttcpolicy.preemptible = 1;

      if ((ret=thread_policy_set(mach_thread_self(),
        THREAD_TIME_CONSTRAINT_POLICY, (int *)&ttcpolicy,
        THREAD_TIME_CONSTRAINT_POLICY_COUNT)) != KERN_SUCCESS) {
            fprintf(stderr, "thread_policy_set failed: %d.\n", ret);
      }    
}

int iaxc_prioboostend() {
    /* TODO */
}

#else


/* Priority boosting/monitoring:  Adapted from portaudio/pa_unix.c ,
 * which carries the following copyright notice:
 * PortAudio Portable Real-Time Audio Library
 * Latest Version at: http://www.portaudio.com
 * Linux OSS Implementation by douglas repetto and Phil Burk
 *
 * Copyright (c) 1999-2000 Phil Burk
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * Any person wishing to distribute modifications to the Software is
 * requested to send the modifications to the original developer so that
 * they can be incorporated into the canonical version.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 */ 

/* It has been clarified by the authors that the request to send modifications
   is a request, and not a condition */

/* Theory:
 *  The main thread is boosted to a medium real-time priority.
 *  Two additional threads are created:
 *  Canary:  Runs as normal priority, updates a timevalue every second.
 *  WatchDog:  Runs as a higher real-time priority.  Checks to see that
 *	      Canary is running.  If Canary isn't running, lowers
 *	      priority of calling thread, which has presumably run away
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sched.h>
#include <pthread.h>
#include <errno.h>

//#define DBUG(...) fprintf(stderr, __VA_ARGS__)
#define DBUG(...) 
#define ERR_RPT(...) fprintf(stderr, __VA_ARGS__)

#define SCHEDULER_POLICY SCHED_RR
#define WATCHDOG_INTERVAL_USEC 1000000
#define WATCHDOG_MAX_SECONDS 3

typedef void *(*pthread_function_t)(void *);

typedef struct {
	int priority;
	pthread_t ThreadID;

	struct timeval CanaryTime;
	int CanaryRun;
	pthread_t CanaryThread;
	int IsCanaryThreadValid;
	
	int WatchDogRun;
	pthread_t WatchDogThread;
	int IsWatchDogThreadValid;
	
} prioboost;

static prioboost *pb;

static int CanaryProc( prioboost   *b)
{
    int   result = 0;
    struct sched_param    schat = { 0 };

    /* set us up with normal priority, please */
    if( pthread_setschedparam(pthread_self(), SCHED_OTHER, &schat) != 0)
	return 1;

    while( b->CanaryRun) {
      usleep( WATCHDOG_INTERVAL_USEC );
      gettimeofday( &b->CanaryTime, NULL );
    }

    return result;
}

static int WatchDogProc( prioboost   *b )
{
    struct sched_param    schp = { 0 };
    int                   maxPri;

/* Run at a priority level above main thread so we can still run if it hangs. */
/* Rise more than 1 because of rumored off-by-one scheduler bugs. */
    schp.sched_priority = b->priority + 4;
    maxPri = sched_get_priority_max(SCHEDULER_POLICY);
    if( schp.sched_priority > maxPri ) schp.sched_priority = maxPri;

    if (pthread_setschedparam(pthread_self(), SCHEDULER_POLICY, &schp) != 0)
    {
        ERR_RPT("WatchDogProc: cannot set watch dog priority!\n");
        goto killAudio;
    }

    DBUG("prioboost: WatchDog priority set to level %d!\n", schp.sched_priority);        

    /* Compare watchdog time with audio and canary thread times. */
    /* Sleep for a while or until thread cancelled. */
    while( b->WatchDogRun )
    {

        int              delta;
        struct timeval   currentTime;

        usleep( WATCHDOG_INTERVAL_USEC );
        gettimeofday( &currentTime, NULL );

#if 0
        /* If audio thread is not advancing, then it must be hung so kill it. */
        delta = currentTime.tv_sec - b->EntryTime.tv_sec;
        DBUG(("WatchDogProc: audio delta = %d\n", delta ));
        if( delta > WATCHDOG_MAX_SECONDS )
        {
            goto killAudio;
        }
#endif

        /* If canary died, then lower audio priority and halt canary. */
        delta = currentTime.tv_sec - b->CanaryTime.tv_sec;
    DBUG("WatchDogProc: dogging, delta = %ld, mysec=%d\n", delta, currentTime.tv_sec);
        if( delta > WATCHDOG_MAX_SECONDS )
        {
            ERR_RPT("WatchDogProc: canary died!\n");
            goto lowerAudio;
        }
    }

    DBUG("WatchDogProc: exiting.\n");
    return 0;

lowerAudio:
    {
        struct sched_param    schat = { 0 };
        if( pthread_setschedparam(b->ThreadID, SCHED_OTHER, &schat) != 0)
        {
            ERR_RPT("WatchDogProc: failed to lower audio priority. errno = %d\n", errno );
            /* Fall through into killing audio thread. */
        }
        else
        {
            ERR_RPT("WatchDogProc: lowered audio priority to prevent hogging of CPU.\n");
            goto cleanup;
        }
    }

killAudio:
    ERR_RPT("WatchDogProc: killing hung audio thread!\n");
    //pthread_cancel( b->ThreadID);
    //pthread_join( b->ThreadID);
    exit(1);

cleanup:
    b->CanaryRun = 0;
    DBUG("WatchDogProc: cancel Canary\n");
    pthread_cancel( b->CanaryThread );
    DBUG("WatchDogProc: join Canary\n");
    pthread_join( b->CanaryThread, NULL );
    DBUG("WatchDogProc: forget Canary\n");
    b->IsCanaryThreadValid = 0;


#ifdef GNUSTEP
    GSUnregisterCurrentThread();  /* SB20010904 */
#endif
    return 0;
}


static void StopWatchDog( prioboost   *b )
{
/* Cancel WatchDog thread if there is one. */
    if( b->IsWatchDogThreadValid )
    {
        b->WatchDogRun = 0;
        DBUG("StopWatchDog: cancel WatchDog\n");
        pthread_cancel( b->WatchDogThread );
        pthread_join( b->WatchDogThread, NULL );
        b->IsWatchDogThreadValid = 0;
    }
/* Cancel Canary thread if there is one. */
    if( b->IsCanaryThreadValid )
    {
        b->CanaryRun = 0;
        DBUG("StopWatchDog: cancel Canary\n");
        pthread_cancel( b->CanaryThread );
        DBUG("StopWatchDog: join Canary\n");
        pthread_join( b->CanaryThread, NULL );
        b->IsCanaryThreadValid = 0;
    }
}


static int StartWatchDog( prioboost *b) {
    int  hres;
    int  result = 0;

    /* The watch dog watches for these timer updates */
    gettimeofday( &b->CanaryTime, NULL );

    /* Launch a canary thread to detect priority abuse. */
    b->CanaryRun = 1;
    hres = pthread_create(&(b->CanaryThread),
                      NULL /*pthread_attr_t * attr*/,
                      (pthread_function_t)CanaryProc, b);
    if( hres != 0 )
    {
        b->IsCanaryThreadValid = 0;
        result = 1;
        goto error;
    }
    b->IsCanaryThreadValid = 1;

    /* Launch a watchdog thread to prevent runaway audio thread. */
    b->WatchDogRun = 1;
    hres = pthread_create(&(b->WatchDogThread),
                      NULL /*pthread_attr_t * attr*/,
                      (pthread_function_t)WatchDogProc, b);
    if( hres != 0 )     {
        b->IsWatchDogThreadValid = 0;
        result = 1;
        goto error;
    }
    b->IsWatchDogThreadValid = 1;
    return result;

error:
    StopWatchDog( b );
    return result;
}

int iaxc_prioboostbegin() {
    struct sched_param   schp = { 0 };
    prioboost *b = calloc(sizeof(*b),1);

    int result = 0;    

    b->priority = (sched_get_priority_max(SCHEDULER_POLICY) -
                                  sched_get_priority_min(SCHEDULER_POLICY)) / 2;
    schp.sched_priority = b->priority;

    b->ThreadID = pthread_self();

    if (pthread_setschedparam(b->ThreadID, SCHEDULER_POLICY, &schp) != 0)
    {
        DBUG("prioboost: only superuser can use real-time priority.\n");
    }
    else
    {
        DBUG("prioboost: priority set to level %d!\n", schp.sched_priority);        /* We are running at high priority so we should have a watchdog in case audio goes wild. */
        result = StartWatchDog( b );
    }

    if(result == 0)  {
      pb = b;
    } else {
      pb = NULL;
      schp.sched_priority = 0;
      pthread_setschedparam(b->ThreadID, SCHED_OTHER, &schp);
    }

    return result; 
}

int iaxc_prioboostend()  {
	if(pb) StopWatchDog(pb);
	return 0;
}

#endif

