package ca.szc.keratin.core.net.mbassador;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import net.engio.mbassy.IPublicationErrorHandler;
import net.engio.mbassy.bus.BusConfiguration;
import net.engio.mbassy.bus.MBassador;
import net.engio.mbassy.bus.MessagePublication;
import net.engio.mbassy.listener.Handler;

import org.pmw.tinylog.Logger;

import ca.szc.keratin.core.event.BusWatchdogEvent;
import ca.szc.keratin.core.event.IrcEvent;

/**
 * Wraps a MBassador object, so it can be replaced when it breaks without leaving stale references. Uses a watch-dog
 * thread to detect when message delivery stops working. Automatically replaces the broken MBassador when this occurs.
 */
public class MBassadorWrapper
{
    private MBassador<IrcEvent> delegate;

    private final BusConfiguration configuration;

    private final Set<Object> listeners;

    private final Set<IPublicationErrorHandler> errorHandlers;

    public MBassadorWrapper( BusConfiguration configuration )
    {
        this.configuration = configuration;

        listeners = new HashSet<Object>();
        errorHandlers = new HashSet<IPublicationErrorHandler>();

        rebootDelegate();

        Thread wd = new Thread()
        {
            private Semaphore activityMonitor = new Semaphore( 0 );

            private Semaphore watchdogMonitor = new Semaphore( 0 );

            public void run()
            {
                Logger.trace( "Bus watchdog running" );
                Thread.currentThread().setName( "BusWatchdog" );

                while ( !Thread.interrupted() )
                {
                    try
                    {
                        boolean activityTimedout = !activityMonitor.tryAcquire( 10, TimeUnit.SECONDS );
                        if ( activityTimedout )
                        {
                            Logger.trace( "No activity, checking if the bus is alive" );
                            publish( new BusWatchdogEvent( null ) );
                            boolean watchdogTimedout = !activityMonitor.tryAcquire( 5, TimeUnit.SECONDS );
                            if ( watchdogTimedout )
                            {
                                Logger.trace( "The bus is not alive, rebooting the bus" );
                                rebootDelegate();
                            }
                            else
                            {
                                Logger.trace( "The bus is alive" );
                            }
                        }
                    }
                    catch ( InterruptedException e )
                    {
                        break;
                    }
                }
                Logger.trace( "Interrupted, exiting" );
            };

            @Handler
            public void activityMonitor( IrcEvent event )
            {
                activityMonitor.release();
            }

            @Handler
            public void watchdogMonitor( BusWatchdogEvent event )
            {
                watchdogMonitor.release();
            }
        };
        subscribe( wd );
        wd.start();
    }

    private synchronized void rebootDelegate()
    {
        // Make sure old delegate is disposed of
        if ( delegate != null )
            delegate.shutdown();

        // New delegate
        delegate = new MBassador<IrcEvent>( configuration );

        // Add the active listeners and handler the old delegate had.
        for ( Object listener : listeners )
            delegate.subscribe( listener );
        for ( IPublicationErrorHandler handler : errorHandlers )
            delegate.addErrorHandler( handler );
    }

    public synchronized void addErrorHandler( IPublicationErrorHandler handler )
    {
        delegate.addErrorHandler( handler );
    }

    public synchronized void publish( IrcEvent message )
    {
        delegate.publish( message );
    }

    public synchronized MessagePublication publishAsync( IrcEvent message )
    {
        return delegate.publishAsync( message );
    }

    public synchronized void shutdown()
    {
        delegate.shutdown();
    }

    public synchronized void subscribe( Object listener )
    {
        delegate.subscribe( listener );
        listeners.add( listener );
    }

    public synchronized boolean unsubscribe( Object listener )
    {
        listeners.remove( listener );
        return delegate.unsubscribe( listener );
    }
}

@@ -1784,9 +1784,9 @@
 re( 
-1
+3
 0, T

Increase watchdog activity timeout to reduce log noise.