/* 

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 flbuf.cpp
    \brief Implementation of the buffer abstraction class.
*/
 
#include "flext.h"

#if FLEXT_SYS != FLEXT_SYS_JMAX

#if FLEXT_SYS == FLEXT_SYS_MAX
#include "flmspbuffer.h" // include inofficial buffer.h
#endif

#if FLEXT_SYS == FLEXT_SYS_PD
#define DIRTY_INTERVAL 0   // buffer dirty check in msec
#endif

// check if PD API supports buffer dirty time
#if defined(PD_DEVEL_VERSION) && defined(PD_MAJOR_VERSION) && defined(PD_MINOR_VERSION)
#if PD_MINOR_VERSION >= 36
	#define FLEXT_PDBUFDIRTYTIME
#endif
#endif

flext::buffer::buffer(const t_symbol *bn,bool delayed):
	sym(NULL),data(NULL),
	chns(0),frames(0)
{
#if FLEXT_SYS == FLEXT_SYS_PD
	arr = NULL;
	interval = DIRTY_INTERVAL;
	isdirty = false;
	ticking = false;
    tick = clock_new(this,(t_method)cb_tick);
#endif

	if(bn) Set(bn,delayed);

	ClearDirty();
}

flext::buffer::~buffer()
{
#if FLEXT_SYS == FLEXT_SYS_PD
    clock_free(tick);
#endif
}

int flext::buffer::Set(const t_symbol *s,bool nameonly)
{
	int ret = 0;
	bool valid = data != NULL; // valid now? (before change)

	if(s && sym != s) {
		ret = 1;
		data = NULL; 
		frames = 0;
		chns = 0; 
	}

	if(s && *GetString(s))	sym = s;

	if(!sym) {
		if(valid) ret = -1;
	}	
	else if(!nameonly) {
#if FLEXT_SYS == FLEXT_SYS_PD
		int frames1;
		t_sample *data1;
    
		arr = (t_garray *)pd_findbyclass(const_cast<t_symbol *>(sym), garray_class);
		if(!arr)
		{
    		if (*GetString(sym)) FLEXT_LOG1("buffer: no such array '%s'",GetString(sym));
    		sym = NULL;
			if(valid) ret = -1;
		}
		else if(!garray_getfloatarray(arr, &frames1, &data1))
		{
    		error("buffer: bad template '%s'",GetString(sym)); 
    		data = NULL;
			frames = 0;
			if(valid) ret = -1;
		}
		else {
            ret = 0;
			garray_usedindsp(arr);
			if(frames != frames1) { frames = frames1; if(!ret) ret = 1; }
			if(data != data1) { data = data1; if(!ret) ret = 1; }
			chns = 1;
		}
#elif FLEXT_SYS == FLEXT_SYS_MAX
		if(sym->s_thing) {
			const _buffer *p = (const _buffer *)sym->s_thing;
			
			if(NOGOOD(p) || !p->b_valid) {
				post("buffer: buffer object '%s' no good",GetString(sym)); 
				if(valid) ret = -2;
			}
			else {
#ifdef FLEXT_DEBUG
//				post("flext: buffer object '%s' - valid:%i samples:%i channels:%i frames:%i",GetString(sym),p->b_valid,p->b_frames,p->b_nchans,p->b_frames);
#endif
				if(data != p->b_samples) { data = p->b_samples; if(!ret) ret = 1; }
				if(chns != p->b_nchans) { chns = p->b_nchans; if(!ret) ret = 1; }
				if(frames != p->b_frames) { frames = p->b_frames; if(!ret) ret = 1; }
			}
		}
		else {
    		FLEXT_LOG1("buffer: symbol '%s' not defined", GetString(sym)); 
    		/*if(valid)*/ ret = -1;
		}
#else
#error not implemented
#endif
	}

	return ret;
}

bool flext::buffer::Valid() const
{
	if(sym) {
#if FLEXT_SYS == FLEXT_SYS_PD
		int frames1;
		t_sample *data1;
		return garray_getfloatarray(arr, &frames1, &data1) != 0;
#elif FLEXT_SYS == FLEXT_SYS_MAX
		const _buffer *p = (const _buffer *)sym->s_thing;
		return p && p->b_valid;
#else
#error
#endif 
	}
	else return false;
}


bool flext::buffer::Update()
{
	if(!Ok()) return false;

	bool ok = false;

#if FLEXT_SYS == FLEXT_SYS_PD
	int frames1;
	t_sample *data1;
	if(!garray_getfloatarray(arr, &frames1, &data1)) {
		frames = 0;
		data = NULL;
		chns = 0;
		ok = true;
	}
	else if(data != data1 || frames != frames1) {
		frames = frames1;
		data = data1;
		ok = true;
	}
#elif FLEXT_SYS == FLEXT_SYS_MAX
	if(sym->s_thing) {
		const _buffer *p = (const _buffer *)sym->s_thing;
		if(data != p->b_samples || chns != p->b_nchans || frames != p->b_frames) {
			data = p->b_samples;
			chns = p->b_nchans;
			frames = p->b_frames;
			ok = true;
		}
	}
#else
#error not implemented
#endif
	return ok;
}

void flext::buffer::Frames(int fr,bool keep,bool zero)
{
#if FLEXT_SYS == FLEXT_SYS_PD
    // is this function guaranteed to keep memory and set rest to zero?
	::garray_resize(arr,(float)fr);
	Update();
#elif FLEXT_SYS == FLEXT_SYS_MAX
	t_sample *tmp = NULL;
	int sz = frames;  
	if(fr < sz) sz = fr;

	if(keep) {
		// copy buffer data to tmp storage
		tmp = new t_sample[sz];
        if(tmp)
			CopySamples(tmp,data,sz);
		else
			error("flext::buffer - not enough memory for keeping buffer~ contents");
	}
	
	t_atom msg;
	_buffer *buf = (_buffer *)sym->s_thing;
	// b_msr reflects buffer sample rate... is this what we want?
	// Max bug: adding half a sample to prevent roundoff errors....
	float ms = (fr+0.5)/buf->b_msr;
	
	SetFloat(msg,ms); 
	::typedmess((object *)buf,gensym("size"),1,&msg);
	
	Update();

	if(tmp) {
		// copy data back
		CopySamples(data,tmp,sz);
		delete[] tmp;
        if(zero && sz < fr) ZeroSamples(data+sz,fr-sz);
	}
    else
        if(zero) ZeroSamples(data,fr);
#else
#error
#endif
}


#if FLEXT_SYS == FLEXT_SYS_PD
void flext::buffer::SetRefrIntv(float intv) 
{ 
	interval = intv; 
	if(interval == 0 && ticking) {
		clock_unset(tick);
		ticking = false;
	}
}
#elif FLEXT_SYS == FLEXT_SYS_MAX
void flext::buffer::SetRefrIntv(float) {}
#else
#error
#endif


void flext::buffer::Dirty(bool force)
{
	if(sym) {
#if FLEXT_SYS == FLEXT_SYS_PD
		if((!ticking) && (interval || force)) {
			ticking = true;
			cb_tick(this); // immediately redraw
		}
		else {
			if(force) clock_delay(tick,0);
			isdirty = true;
		}
#elif FLEXT_SYS == FLEXT_SYS_MAX
		if(sym->s_thing) {
			_buffer *p = (_buffer *)sym->s_thing;
			
			if(NOGOOD(p)) {
				post("buffer: buffer object '%s' no good",sym->s_name);
			}
			else {
				p->b_modtime = gettime();
			}
		}
		else {
    		FLEXT_LOG1("buffer: symbol '%s' not defined",sym->s_name);
		}
#else
#error Not implemented
#endif 
	}
}

#if FLEXT_SYS == FLEXT_SYS_PD
void flext::buffer::cb_tick(buffer *b)
{
	if(b->arr) garray_redraw(b->arr);
#ifdef FLEXT_DEBUG
	else error("buffer: array is NULL");
#endif

	if(b->isdirty && b->interval) {
			b->isdirty = false;
			b->ticking = true;
			clock_delay(b->tick,b->interval);
	}
	else 
		b->ticking = false;
}
#endif

void flext::buffer::ClearDirty()
{
#if FLEXT_SYS == FLEXT_SYS_PD
	cleantime = clock_getlogicaltime();
#elif FLEXT_SYS == FLEXT_SYS_MAX
	cleantime = gettime();
#else
#error Not implemented
#endif
}

bool flext::buffer::IsDirty() const
{
#if FLEXT_SYS == FLEXT_SYS_PD
	if(!arr) return false;
	#ifdef FLEXT_PDBUFDIRTYTIME
	return isdirty || garray_updatetime(arr) > cleantime;
	#else
	// Don't know.... (no method in PD judging whether buffer has been changed from outside flext...)
	return true; 
	#endif
#elif FLEXT_SYS == FLEXT_SYS_MAX
	if(!sym->s_thing) return false;

	_buffer *p = (_buffer *)sym->s_thing;
#ifdef FLEXT_DEBUG
	if(NOGOOD(p)) {
		post("buffer: buffer object '%s' no good",sym->s_name);
		return false;
	}
#endif
	return p->b_modtime > cleantime;
#else
#error Not implemented
#endif
}

#endif // Jmax