/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/*  scheduling stuff  */
#include "m_imp.h"

    /* LATER consider making this variable.  It's now the LCM of all sample
    rates we expect to see. */
#define TIMEUNITPERSEC (32.*441000.)

static int sys_quit;
static double sys_time;
static double sys_time_per_dsp_tick;
static double sys_time_per_msec;

typedef void (*t_clockmethod)(void *client);

struct _clock
{
    double c_settime;
    void *c_owner;
    t_clockmethod c_fn;
    struct _clock *c_next;
};

t_clock *clock_setlist;

#if 0
#ifdef UNIX
#include <unistd.h>
#endif

void glob_foo(void *dummy, t_symbol *s, int argc, t_atom *argv)
{
    post("foo 1");
#ifdef UNIX
    sleep(5);
#else
    {
    	int i, j = 0;
    	for (i = 0; i < 1000000000; i++) j += i;
    	post("foo %x", j);
    }
#endif
    post("foo 2");
}
#endif

t_clock *clock_new(void *owner, t_method fn)
{
    t_clock *x = (t_clock *)getbytes(sizeof *x);
    x->c_settime = -1;
    x->c_owner = owner;
    x->c_fn = (t_clockmethod)fn;
    x->c_next = 0;
}

void clock_unset(t_clock *x)
{
    if (x->c_settime >= 0)
    {
    	if (x == clock_setlist) clock_setlist = x->c_next;
    	else
    	{
    	    t_clock *x2 = clock_setlist;
    	    while (x2->c_next != x) x2 = x2->c_next;
    	    x2->c_next = x->c_next;
    	}
    	x->c_settime = -1;
    }
}

    /* set the clock to call back at an absolute system time */
void clock_set(t_clock *x, double setticks)
{
    if (setticks < sys_time) setticks = sys_time;
    clock_unset(x);
    x->c_settime = setticks;
    if (clock_setlist && clock_setlist->c_settime <= setticks)
    {
    	t_clock *cbefore, *cafter;
    	for (cbefore = clock_setlist, cafter = clock_setlist->c_next;
    	    cbefore; cbefore = cafter, cafter = cbefore->c_next)
    	{
    	    if (!cafter || cafter->c_settime > setticks)
    	    {
    	    	cbefore->c_next = x;
    	    	x->c_next = cafter;
    	    	return;
    	    }
    	}
    }
    else x->c_next = clock_setlist, clock_setlist = x;
}

    /* set the clock to call back after a delay in msec */
void clock_delay(t_clock *x, double delaytime)
{
    clock_set(x, sys_time + sys_time_per_msec * delaytime);
}

    /* get current system time */
double clock_getsystime()
{
    return (sys_time);
}

    /* elapsed time in milliseconds since the given system time */
double clock_gettimesince(double prevsystime)
{
    return ((sys_time - prevsystime)/sys_time_per_msec);
}

    /* what value the system clock will have after a delay */
double clock_getsystimeafter(double delaytime)
{
    return (sys_time + sys_time_per_msec * delaytime);
}

void clock_free(t_clock *x)
{
    clock_unset(x);
    freebytes(x, sizeof *x);
}

void dsp_tick(void);

static int m_nodacs = 0;


int m_scheduler(void)
{
    static double next = 0.0;
    sys_time_per_dsp_tick =
    	(TIMEUNITPERSEC) * ((double)DACBLKSIZE) / sys_dacsr;
    sys_time_per_msec =
    	TIMEUNITPERSEC / 1000.;
    post("running.\n");
    while (1)
    {
    	int didsomething = 0;
        static int diddacs = 1;
        static t_systime lastdactime;
    	
    	int timeforward;
    	if (m_nodacs)
    	{
    	    double elapsed = sys_secsince(&lastdactime);
    	    if (elapsed > next)
    	    {
    	    	timeforward = 1;
    	    	next += (double)DACBLKSIZE / sys_dacsr;
    	    }
    	    else timeforward = 0;
    	}
    	else timeforward = sys_send_dacs();
    	if (timeforward)
    	{
    	    /* time has moved forward.  Check MIDI and clocks */
    	    
    	    double next_sys_time = sys_time + sys_time_per_dsp_tick;
    	    while (clock_setlist && clock_setlist->c_settime < next_sys_time)
    	    {
    	    	t_clock *c = clock_setlist;
    	    	sys_time = c->c_settime;
    	    	clock_unset(clock_setlist);
    	    	(*c->c_fn)(c->c_owner);
    	    }
    	    sys_time = next_sys_time;
    	    sys_poll_midi();
    	    if (sys_quit) break;
    	    dsp_tick();
    	    didsomething = 1;
    	    diddacs = 1;
    	}
    	if (sys_pollgui()) didsomething = 1;
    	if (!didsomething)
    	{
    	    sys_microsleep(sys_schedadvance >> 1);
    	    if (!m_nodacs)
    	    {
    		if (diddacs)
    		{
    	    	    sys_gettime(&lastdactime);
    	    	    diddacs = 0;
    		}
    		else
    		{
    	    	    if (sys_microsecsince(&lastdactime) > 1000000)
    	    	    {
    	    		post("dacs died... switching to alternate time source\n");
    	    		m_nodacs = 1;
    	    		sys_close_audio();
    	    		sys_gettime(&lastdactime);
    	    	    }
    		}
    	    }
    	}
    }
}


