//-----------------------------------------------------------------------------
// name: bleep_synth.cpp
// desc: bleep synth engine
//-----------------------------------------------------------------------------
#include "bleep_synth.h"
#include "bleep_factory.h"

#include <iostream>
using namespace std;

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BleepSynthNode::BleepSynthNode( BLUG * blug, MUH_INT audio_size )
{
    // clean
    m_blug = NULL;
    m_time = 0;
    m_input = NULL;
    m_output = NULL;
    m_size = 0;
    m_name = "[noone]";
	m_root = FALSE;
    
    // alloc
    this->init( blug, audio_size );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BleepSynthNode::~BleepSynthNode()
{
    // clean
    this->dealloc();
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthNode::init( BLUG * blug, MUH_INT audio_size )
{
    // sanity check
    if( m_blug || m_input || m_output )
    {
        cerr << "BleepSynthNode::init(): double init!" << endl;
        return FALSE;
    }
    
    // allocate buffer
    m_input = new SAMPLE[audio_size];
    m_output = new SAMPLE[audio_size];
    if( !m_input || !m_output )
    {
        cerr << "BleepSynthNode::init(): out of memory!" << endl;
        dealloc();
        return FALSE;
    }
    
    // set size
    m_size = audio_size;
    
    // set blug
    m_blug = blug;

    // done
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthNode::dealloc()
{
    // sanity check
    if( !m_blug && !m_input && !m_output )
    {
        cerr << "BleepSynthNode::dealloc(): nothing to do!" << endl;
        return FALSE;
    }
    
    // go
    MUH_SAFE_DELETE_ARRAY( m_input );
    MUH_SAFE_DELETE_ARRAY( m_output );
    // TODO: deal with m_blug
    m_size = 0;
    m_time = 0;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthNode::connect( BleepSynthNode * from )
{
    // make sure not already connected
    if( m_inputs.find( from->getName() ) != m_inputs.end() )
    {
        // nope
        cerr << "BleepSynthNode: '" << getName() << "' already connected from '"
             << from->getName() << "'..." << endl;
        return FALSE;
    }
    
    // add it
    m_inputs[from->getName()] = from;
    
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthNode::disconnect( const string & name )
{
    // make sure already connected
    if( m_inputs.find( name ) == m_inputs.end() )
    {
        // nope
        cerr << "BleepSynthNode: '" << getName() << "' can't disconnect from '"
        << name << "'..." << endl;
        return FALSE;
    }
    
    // add it
    m_inputs.erase( name );

    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthNode::disconnect( BleepSynthNode * from )
{
    return disconnect( from->getName() );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
SAMPLE * BleepSynthNode::synthesize( MUH_FLOAT time )
{
    // only if time is new
    if( time > m_time )
    {
		if( !m_root )
		{
            // clear our buffer
            memset( m_input, 0, m_size * sizeof(SAMPLE) );
            // pointer
            SAMPLE * buffy = NULL;

            // loop over map
            map<string, BleepSynthNode *>::iterator iter;
            for( iter = m_inputs.begin(); iter != m_inputs.end(); iter++ )
            {
                // synthesize the node
                buffy = (*iter).second->synthesize( time );
                // add it in
                for( MUH_INT i = 0; i < m_size; i++ )
                    m_input[i] += buffy[i];
            }
		}
        
        // if we have blug
        if( m_blug )
        {
            // clear
            memset( m_output, 0, m_size * sizeof(SAMPLE) );
            // process our blug
            m_blug->process( m_input, m_output, m_size );
        }
        else
        {
            // copy
            memcpy( m_output, m_input, m_size * sizeof(SAMPLE) );
        }
    }
    
    // result
    return m_output;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::init( MUH_INT audio_size )
{
    // sanity check
    if( m_init )
    {
        cerr << "BleepSynthEngine: already initialized!" << endl;
        return FALSE;
    }

    // set it
    m_size = audio_size;
    
    // allocate sink
    m_sink = new BleepSynthNode( NULL, m_size );
    m_sink->setName( "dac" );
    m_sink->setType( "[special BLUG]" );

    // allocate source
    m_src = new BleepSynthNode( NULL, m_size );
    m_src->setName( "adc" );
    m_src->setType( "[special BLUG]" );
	m_src->setRoot( TRUE );

	// add to map
	insertToMap( "adc", m_src );
	insertToMap( "dac", m_sink );
    
    // flag it
    m_init = TRUE;
    
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::go( SAMPLE * input, SAMPLE * output )
{
    // sanity check
    if( !m_init )
    {
        cerr << "BleepSynthEngine::go(): not initialized!" << endl;
        return FALSE;
    }
    
    // copy input into adc
    memcpy( m_src->getInput(), input, m_size * sizeof(SAMPLE) );
    
    // run from dac
    SAMPLE * buffy = m_sink->synthesize( m_time );
    
    // copy output
    memcpy( output, buffy, m_size * sizeof(SAMPLE) );

    // update time
    m_time += m_size;
    
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void BleepSynthEngine::cleanUp()
{
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::createBLUG( const string & type, const string & name )
{
    // sanity check
    if( !m_init )
    {
        cerr << "BleepSynthEngine::createBLUG(): not initialized!" << endl;
        return FALSE;
    }
    
    // check if type exists
    if( !isValidType( type ) )
    {
        cerr << "BleepSynthEngine: unknown type '" << type << "'..." << endl;
        return FALSE;
    }
    
    // check if name is valid
    if( !isValidName( name ) )
    {
        cerr << "BleepSynthEngine: invalid name '" << name << "'..." << endl;
        return FALSE;
    }
    
    // check if name already used
    if( m_map.find( name ) != m_map.end() )
    {
        cerr << "BleepSynthEngine: name '" << name << "' already in use..." << endl;
        return FALSE;
    }
    
    // instantiate BLUG
    BLUG * blug = makeBLUG( type );
    // sanity check
    assert( blug != NULL );
    // instantiate Node
    BleepSynthNode * node = new BleepSynthNode( blug, m_size );
    // set type
    node->setType( type );
    // set name
    node->setName( name );

    // insert node
	insertToMap( name, node );
    
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::destroyBLUG( const string & name )
{
    // sanity check
    if( !m_init )
    {
        cerr << "BleepSynthEngine::destroyBLUG(): not initialized!" << endl;
        return FALSE;
    }
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::connectBLUG( const string & from, const string & to )
{
    // sanity check
    if( !m_init )
    {
        cerr << "BleepSynthEngine::connectBLUG(): not initialized!" << endl;
        return FALSE;
    }
	
	// find from and to
	BleepSynthNode * src = findNode( from );
	if( !src )
	{
	    cerr << "BleepSynthEngine: cannot find BLUG '" << from << "'..." << endl;
		return FALSE;
	}
	BleepSynthNode * dest = findNode( to );
	if( !dest )
	{
	    cerr << "BleepSynthEngine: cannot find BLUG '" << to << "'..." << endl;
		return FALSE;
	}
	
	// connect
	dest->connect( src );
	
	return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::disconnectBLUG( const string & from, const string & to )
{
    // sanity check
    if( !m_init )
    {
        cerr << "BleepSynthEngine::disconnectBLUG(): not initialized!" << endl;
        return FALSE;
    }

	// find from and to
	BleepSynthNode * src = findNode( from );
	if( !src )
	{
	    cerr << "BleepSynthEngine: cannot find BLUG '" << from << "'..." << endl;
		return FALSE;
	}
	BleepSynthNode * dest = findNode( to );
	if( !dest )
	{
	    cerr << "BleepSynthEngine: cannot find BLUG '" << to << "'..." << endl;
		return FALSE;
	}
    
    // disconnect
    dest->disconnect( src );
	
	return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::insertToMap( const string & name, BleepSynthNode * node )
{
    // TODO: what if key already in map?
    m_map[name] = node;

	return TRUE;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::isValidType( const string & type )
{
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
MUH_BOOL BleepSynthEngine::isValidName( const string & name )
{
    return TRUE;
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BLUG * BleepSynthEngine::makeBLUG( const string & type )
{
    return BleepFactory::ourInstance()->makeBLUG( type );
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BleepSynthNode * BleepSynthEngine::findNode( const string & name )
{
    // look
	map<string, BleepSynthNode *>::iterator iter;
	iter = m_map.find( name );
	
	// got?
	if( iter != m_map.end() ) return (*iter).second;
	else return NULL;
}
