package org.realityforge.gwt.webpoller.client;

import com.google.gwt.core.shared.GWT;
import com.google.gwt.http.client.Request;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.HandlerRegistration;
import javax.annotation.Nonnull;
import org.realityforge.gwt.webpoller.client.event.StartEvent;
import org.realityforge.gwt.webpoller.client.event.StopEvent;
import org.realityforge.gwt.webpoller.client.event.ErrorEvent;
import org.realityforge.gwt.webpoller.client.event.MessageEvent;
import org.realityforge.gwt.webpoller.client.html5.Html5WebPoller;

public abstract class WebPoller
{
  public interface RequestFactory
  {
    @Nonnull
    Request newRequest();
  }

  public interface Factory
  {
    @Nonnull
    WebPoller newWebPoller( @Nonnull RequestFactory requestFactory, boolean longPoll );
  }

  private static Factory g_factory;
  private final EventBus _eventBus;
  private final RequestFactory _requestFactory;
  private final boolean _longPoll;
  private boolean _active;

  public static WebPoller newWebPoller( @Nonnull final RequestFactory requestFactory, final boolean longPoll )
  {
    if ( null == g_factory && GWT.isClient() )
    {
      register( new Html5WebPoller.Factory() );
    }
    return ( null != g_factory ) ? g_factory.newWebPoller( requestFactory, longPoll ) : null;
  }

  public static void register( @Nonnull final Factory factory )
  {
    g_factory = factory;
  }

  public static boolean deregister( @Nonnull final Factory factory )
  {
    if ( g_factory != factory )
    {
      return false;
    }
    else
    {
      g_factory = null;
      return true;
    }
  }

  protected WebPoller( @Nonnull final EventBus eventBus,
                       @Nonnull final RequestFactory requestFactory,
                       final boolean longPoll )
  {
    _eventBus = eventBus;
    _requestFactory = requestFactory;
    _longPoll = longPoll;
  }

  /**
   * @return true if the poller is active.
   */
  public boolean isActive()
  {
    return _active;
  }

  /**
   * Start polling.
   *
   * @throws IllegalStateException if the poller is already active.
   */
  public final void start()
    throws IllegalStateException
  {
    if ( isActive() )
    {
      throw new IllegalStateException( "Start invoked on active poller" );
    }
    doStart();
  }

  /**
   * Stop polling.
   *
   * @throws IllegalStateException if the poller is not active.
   */
  public final void stop()
    throws IllegalStateException
  {
    if ( !isActive() )
    {
      throw new IllegalStateException( "Stop invoked on inactive poller" );
    }
    doStop();
  }

  protected final RequestFactory getRequestFactory()
  {
    return _requestFactory;
  }

  protected final boolean isLongPoll()
  {
    return _longPoll;
  }

  protected final EventBus getEventBus()
  {
    return _eventBus;
  }

  /**
   * Sub-classes should override this method to provide functionality.
   */
  protected void doStop()
  {
    _active = false;
    _errorCount = 0;
    onStop();
  }

  /**
   * Sub-classes should override this method to provide functionality.
   */
  protected void doStart()
  {
    _active = true;
    onStart();
  }

  /**
   */

  @Nonnull
  public final HandlerRegistration addStartHandler( @Nonnull StartEvent.Handler handler )
  {
    return getEventBus().addHandler( StartEvent.getType(), handler );
  }

  @Nonnull
  public final HandlerRegistration addStopHandler( @Nonnull StopEvent.Handler handler )
  {
    return getEventBus().addHandler( StopEvent.getType(), handler );
  }

  @Nonnull
  public final HandlerRegistration addMessageHandler( @Nonnull MessageEvent.Handler handler )
  {
    return getEventBus().addHandler( MessageEvent.getType(), handler );
  }

  @Nonnull
  public final HandlerRegistration addErrorHandler( @Nonnull ErrorEvent.Handler handler )
  {
    return getEventBus().addHandler( ErrorEvent.getType(), handler );
  }

  /**
   * Fire a Start event.
   */
  protected final void onStart()
  {
    _eventBus.fireEventFromSource( new StartEvent( this ), this );
  }

  /**
   * Fire a Stop event.
   */
  protected final void onStop()
  {
    _eventBus.fireEventFromSource( new StopEvent( this ), this );
  }

  /**
   * Fire a Message event.
   */
  protected final void onMessage( final String data )
  {
    _eventBus.fireEventFromSource( new MessageEvent( this, data ), this );
  }

  /**
   * Fire an Error event.
   */
  protected final void onError()
  {
    _eventBus.fireEventFromSource( new ErrorEvent( this ), this );
  }
}

@@ -628,16 +628,147 @@
 oller%0A%7B%0A
+  /**%0A   * The number of error before the poller is marked as failed.%0A   */%0A  private static final int ERROR_COUNT_THRESHOLD = 5;%0A%0A
   public
@@ -1159,16 +1159,43 @@
 _active;
+%0A  private int _errorCount;
 %0A%0A  publ
@@ -2194,24 +2194,186 @@
 ctive;%0A  %7D%0A%0A
+  /**%0A   * @return false if stopped, otherwise true if the the last poll resulted in error.%0A   */%0A  public boolean inError()%0A  %7B%0A    return _errorCount %3E 0;%0A  %7D%0A%0A
   /**%0A   * S
@@ -3523,17 +3523,173 @@
 /**%0A   *
-/
+ Invoked after a successful poll, regardless of whether data was received or not.%0A   */%0A  protected final void resetErrorState()%0A  %7B%0A    _errorCount = 0;%0A  %7D
 %0A%0A  @Non
@@ -4858,32 +4858,55 @@
 data ), this );%0A
+    resetErrorState();%0A
   %7D%0A%0A  /**%0A   * 
@@ -4918,32 +4918,119 @@
 an Error event.%0A
+   * If the number of successive errors reaches a threshold then shut-down the poller.%0A
    */%0A  protecte
@@ -5119,18 +5119,112 @@
  ), this );%0A
+    _errorCount++;%0A    if ( _errorCount %3E ERROR_COUNT_THRESHOLD )%0A    %7B%0A      doStop();%0A    %7D%0A
   %7D%0A%7D%0A

Improve error handling by stopping poller after a threshold of errors hit