/*
 * Copyright (C) 2005 by Dr. Marc Boris Duerner
 * Copyright (C) 2005 Stephan Beal
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include "cxxtools/signal.h"

namespace cxxtools {

SignalBase::Sentry::Sentry(const SignalBase* signal)
: _signal(signal)
{
    _signal->_sentry = this;
    _signal->_sending = true;
    _signal->_dirty = false;
}


SignalBase::Sentry::~Sentry()
{
    if( _signal )
        this->detach();
}


void SignalBase::Sentry::detach()
{
    _signal->_sending = false;

    if( _signal->_dirty == false )
    {
        _signal->_sentry = 0;
        _signal = 0;
        return;
    }

    std::list<Connection>::iterator it = _signal->_connections.begin();
    while( it != _signal->_connections.end() )
    {
        if( it->valid() )
        {
            ++it;
        }
        else
        {
            it = _signal->_connections.erase(it);
        }
    }

    _signal->_dirty = false;
    _signal->_sentry = 0;
    _signal = 0;
}


SignalBase::SignalBase()
: _sentry(0)
, _sending(false)
, _dirty(false)
{ }


SignalBase::~SignalBase()
{
    if(_sentry)
    {
        _sentry->detach();
    }
}


SignalBase& SignalBase::operator=(const SignalBase& other)
{
    this->clear();

    std::list<Connection>::const_iterator it = other.connections().begin();
    std::list<Connection>::const_iterator end = other.connections().end();

    for( ; it != end; ++it)
    {
        const Connectable& signal = it->sender();
        if( &signal == &other)
        {
            const Slot& slot = it->slot();
            Connection connection( *this, slot.clone()  );
        }
    }

    return *this;
}


void SignalBase::onConnectionOpen(const Connection& c)
{
    Connectable::onConnectionOpen(c);
}


void SignalBase::onConnectionClose(const Connection& c)
{
    // if the signal is currently calling its slots, do not
    // remove the connection now, but only set the cleanup flag
    // Any invalid connection objects will be removed after
    // the signal has finished calling its slots by the Sentry.
    if( _sending )
    {
        _dirty = true;
    }
    else
    {
        Connectable::onConnectionClose(c);
    }
}


void SignalBase::disconnectSlot(const Slot& slot)
{
    std::list<Connection>::iterator it = Connectable::connections().begin();
    std::list<Connection>::iterator end = Connectable::connections().end();

    for(; it != end; ++it)
    {
        if( it->slot().equals(slot) )
        {
            it->close();
            return;
        }
    }
}


bool CompareEventTypeInfo::operator()(const std::type_info* t1,
                                      const std::type_info* t2) const
{
    if(t2 == 0)
        return false;

    if(t1 == 0)
        return true;

    return t1->before(*t2) != 0;
}


Signal<const cxxtools::Event&>::Sentry::Sentry(const Signal* signal)
: _signal(signal)
{
    _signal->_sentry = this;
    _signal->_sending = true;
    _signal->_dirty = false;
}


Signal<const cxxtools::Event&>::Sentry::~Sentry()
{
    if( _signal )
        this->detach();
}


void Signal<const cxxtools::Event&>::Sentry::detach()
{
    _signal->_sending = false;

    if( _signal->_dirty == false )
    {
        _signal->_sentry = 0;
        _signal = 0;
        return;
    }

    Signal::RouteMap::iterator it = _signal->_routes.begin();
    while( it != _signal->_routes.end() )
    {
        if( it->second->valid() )
        {
            ++it;
        }
        else
        {
            delete it->second;
            _signal->_routes.erase(it++);
        }
    }

    _signal->_dirty = false;
    _signal->_sentry = 0;
    _signal = 0;
}


Signal<const cxxtools::Event&>::Signal()
: _sentry(0)
, _sending(false)
, _dirty(false)
{}


Signal<const cxxtools::Event&>::~Signal()
{
	if(_sentry)
		_sentry->detach();

	while( ! _routes.empty() )
	{
		IEventRoute* route = _routes.begin()->second;
		route->connection().close();
	}
}


void Signal<const cxxtools::Event&>::send(const cxxtools::Event& ev) const
{
	// The sentry will set the Signal to the sending state and
	// reset it to not-sending upon destruction. In the sending
	// state, removing connection will leave invalid connections
	// in the connection list to keep the iterator valid, but mark
	// the Signal dirty. If the Signal is dirty, all invalid
	// connections will be removed by the Sentry when it destructs..
	Signal::Sentry sentry(this);

	RouteMap::iterator it = _routes.begin();
	while( true )
	{
		if( it == _routes.end() )
			return;

		if(it->first != 0)
			break;

		// The following scenarios must be considered when the
		// slot is called:
		// - The slot might get deleted and thus disconnected from
		//   this signal
		// - The slot might delete this signal and we must end
		//   calling any slots immediately
		// - A new Connection might get added to this Signal in
		//   the slot
		IEventRoute* route = it->second;
		if( route->valid() )
			route->route(ev);

		// if this signal gets deleted by the slot, the Sentry
		// will be detached. In this case we bail out immediately
		if( !sentry )
			return;

		++it;
	}

	const std::type_info& ti = ev.typeInfo();

	it = _routes.lower_bound( &ti );
	while(it != _routes.end() && *(it->first) == ti)
	{
		IEventRoute* route = it->second;

		if(route)
			route->route(ev);

		++it;

		// if this signal gets deleted by the slot, the Sentry
		// will be detached. In this case we bail out immediately
		if( !sentry )
			return;
	}
}


void Signal<const cxxtools::Event&>::onConnectionOpen(const Connection& c)
{
	const Connectable& sender = c.sender();
	if(&sender != this)
	{
		return Connectable::onConnectionOpen(c);
	}
}


void Signal<const cxxtools::Event&>::onConnectionClose(const Connection& c)
{
	// if the signal is currently calling its slots, do not
	// remove the connection now, but only set the cleanup flag
	// Any invalid connection objects will be removed after
	// the signal has finished calling its slots by the Sentry.
	if( _sending )
	{
		_dirty = true;
	}
	else
	{
		RouteMap::iterator it;
		for(it = _routes.begin(); it != _routes.end(); ++it )
		{
			IEventRoute* route = it->second;
			if(route->connection() == c )
			{
				delete route;
				_routes.erase(it++);
				return;
			}
		}

		Connectable::onConnectionClose(c);
	}
}


void Signal<const cxxtools::Event&>::addRoute(const std::type_info* ti, IEventRoute* route)
{
    RouteMap::value_type elem(ti, route);
    _routes.insert( elem );
}


void Signal<const cxxtools::Event&>::removeRoute(const Slot& slot)
{
	RouteMap::iterator it = _routes.begin();
	while( it != _routes.end() && it->first == 0 )
	{
		IEventRoute* route = it->second;
		if(route->connection().slot().equals(slot) )
		{
			route->connection().close();
			break;
		}
	}
}


void Signal<const cxxtools::Event&>::removeRoute(const std::type_info* ti, const Slot& slot)
{
	RouteMap::iterator it = _routes.lower_bound( ti );
	while(it != _routes.end() && *(it->first) == *ti)
	{
		IEventRoute* route = it->second;
		if(route->connection().slot().equals(slot) )
		{
			route->connection().close();
			break;
		}
	}
}

}
