/* 

flext - C++ layer for Max/MSP and pd (pure data) externals

Copyright (c) 2001-2003 Thomas Grill (xovo@gmx.net)
For information on usage and redistribution, and for a DISCLAIMER OF ALL
WARRANTIES, see the file, "license.txt," in this distribution.  

*/

/*! \file flqueue.cpp
    \brief Implementation of the flext message queuing functionality.

    \todo Let's see if queuing can be implemented for Max/MSP with defer_low
*/

#include "flext.h"
#include "flinternal.h"

class flext_base::qmsg
{
public:
	qmsg(): nxt(NULL),tp(tp_none) {}
	~qmsg();

	qmsg *nxt;

	void Clear();

	void SetBang(int o) { Clear(); out = o; tp = tp_bang; }
	void SetFloat(int o,float f) { Clear(); out = o; tp = tp_float; _float = f; }
	void SetInt(int o,int i) { Clear(); out = o; tp = tp_int; _int = i; }
	void SetSymbol(int o,const t_symbol *s) { Clear(); out = o; tp = tp_sym; _sym = s; }
	void SetList(int o,int argc,const t_atom *argv) { Clear(); out = o; tp = tp_list; _list.argc = argc,_list.argv = CopyList(argc,argv); }
	void SetAny(int o,const t_symbol *s,int argc,const t_atom *argv) { Clear(); out = o; tp = tp_any; _any.s = s,_any.argc = argc,_any.argv = CopyList(argc,argv); }

	int out;
	enum { tp_none,tp_bang,tp_float,tp_int,tp_sym,tp_list,tp_any } tp;
	union {
		float _float;
		int _int;
		const t_symbol *_sym;
		struct { int argc; t_atom *argv; } _list;
		struct { const t_symbol *s; int argc; t_atom *argv; } _any;
	};
};

flext_base::qmsg::~qmsg() 
{ 
	Clear();
	if(nxt) delete nxt; 
}

void flext_base::qmsg::Clear() 
{ 
	if(tp == tp_list) { if(_list.argv) delete[] _list.argv; }
	else if(tp == tp_any) { if(_any.argv) delete[] _any.argv; }
	tp = tp_none;
}

#if FLEXT_SYS == FLEXT_SYS_JMAX
void flext_base::QTick(fts_object_t *c, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
	flext_base *th = thisObject(c);
#else
void flext_base::QTick(flext_base *th)
{
#endif
//	post("qtick");
#if defined(FLEXT_THREADS) && defined(FLEXT_DEBUG)
	if(!th->IsSystemThread()) {
		error("flext - Queue tick called by wrong thread!");
		return;
	}
#endif

#ifdef FLEXT_THREADS
	th->qmutex.Lock();
#endif
	for(;;) {
		qmsg *m = th->qhead;
		if(!m) break;

        if(m->out < 0) {
            // message to self

            const int n = -1-m->out;
            t_atom tmp;

		    switch(m->tp) {
		    case qmsg::tp_bang: 
                th->m_methodmain(n,sym_bang,0,&tmp); 
                break;
		    case qmsg::tp_float: 
                SetFloat(tmp,m->_float);
                th->m_methodmain(n,sym_float,1,&tmp); 
                break;
            case qmsg::tp_int: 
                SetInt(tmp,m->_int);
#if FLEXT_SYS == FLEXT_SYS_PD
                th->m_methodmain(n,sym_float,1,&tmp); 
#elif FLEXT_SYS == FLEXT_SYS_MAX
                th->m_methodmain(n,sym_int,1,&tmp); 
#else
#error Not implemented!
#endif
		    case qmsg::tp_sym: 
                SetSymbol(tmp,m->_sym);
                th->m_methodmain(n,sym_symbol,1,&tmp); 
                break;
		    case qmsg::tp_list: 
                th->m_methodmain(n,sym_list,m->_list.argc,m->_list.argv); 
                break;
		    case qmsg::tp_any: 
                th->m_methodmain(n,m->_any.s,m->_any.argc,m->_any.argv); 
                break;
    #ifdef FLEXT_DEBUG
		    default: ERRINTERNAL();
    #endif
		    }
        }
        else {
            // message to outlet

		    switch(m->tp) {
		    case qmsg::tp_bang: th->ToOutBang(m->out); break;
		    case qmsg::tp_float: th->ToOutFloat(m->out,m->_float); break;
		    case qmsg::tp_int: th->ToOutInt(m->out,m->_int); break;
		    case qmsg::tp_sym: th->ToOutSymbol(m->out,m->_sym); break;
		    case qmsg::tp_list: th->ToOutList(m->out,m->_list.argc,m->_list.argv); break;
		    case qmsg::tp_any: th->ToOutAnything(m->out,m->_any.s,m->_any.argc,m->_any.argv); break;
    #ifdef FLEXT_DEBUG
		    default: ERRINTERNAL();
    #endif
		    }
        }

		th->qhead = m->nxt;
		if(!th->qhead) th->qtail = NULL;
		m->nxt = NULL;
		delete m;
	}
#ifdef FLEXT_THREADS
	th->qmutex.Unlock();
#endif
}

void flext_base::Queue(qmsg *m)
{
//	post("Queue");

#ifdef FLEXT_THREADS
	qmutex.Lock();
#endif
	if(qtail) qtail->nxt = m;
	else qhead = m;
	qtail = m;
#ifdef FLEXT_THREADS
	qmutex.Unlock();
#endif

#if FLEXT_SYS == FLEXT_SYS_PD
	clock_delay(qclk,0);
#elif FLEXT_SYS == FLEXT_SYS_MAX
	qelem_set(qclk); 
#elif FLEXT_SYS == FLEXT_SYS_JMAX
	// this is dangerous because there may be other timers on this object!
	fts_timebase_add_call(fts_get_timebase(), (fts_object_t *)thisHdr(), QTick, NULL, 0);
#else
#error
#endif
}

void flext_base::ToQueueBang(int o) const 
{
	qmsg *m = new qmsg(); 
	m->SetBang(o);
	const_cast<flext_base &>(*this).Queue(m);
}

void flext_base::ToQueueFloat(int o,float f) const
{
	qmsg *m = new qmsg; 
	m->SetFloat(o,f);
	const_cast<flext_base &>(*this).Queue(m);
}

void flext_base::ToQueueInt(int o,int f) const
{
	qmsg *m = new qmsg; 
	m->SetInt(o,f);
	const_cast<flext_base &>(*this).Queue(m);
}

void flext_base::ToQueueSymbol(int o,const t_symbol *s) const
{
	qmsg *m = new qmsg; 
	m->SetSymbol(o,s);
	const_cast<flext_base &>(*this).Queue(m);
}

void flext_base::ToQueueList(int o,int argc,const t_atom *argv) const
{
	qmsg *m = new qmsg; 
	m->SetList(o,argc,argv);
	const_cast<flext_base &>(*this).Queue(m);
}

void flext_base::ToQueueAnything(int o,const t_symbol *s,int argc,const t_atom *argv) const
{
	qmsg *m = new qmsg; 
	m->SetAny(o,s,argc,argv);
	const_cast<flext_base &>(*this).Queue(m);
}


void flext_base::ToSelfBang(int n) const 
{
    ToQueueBang(-1-n);
}

void flext_base::ToSelfFloat(int n,float f) const
{
    ToQueueFloat(-1-n,f);
}

void flext_base::ToSelfInt(int n,int f) const
{
    ToQueueInt(-1-n,f);
}

void flext_base::ToSelfSymbol(int n,const t_symbol *s) const
{
    ToQueueSymbol(-1-n,s);
}

void flext_base::ToSelfList(int n,int argc,const t_atom *argv) const
{
    ToQueueList(-1-n,argc,argv);
}

void flext_base::ToSelfAnything(int n,const t_symbol *s,int argc,const t_atom *argv) const
{
    ToQueueAnything(-1-n,s,argc,argv);
}