package org.realityforge.replicant.client.json.gwt;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.user.client.Window;
import java.util.Map;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.realityforge.gwt.webpoller.client.AbstractHttpRequestFactory;
import org.realityforge.gwt.webpoller.client.WebPoller;
import org.realityforge.gwt.webpoller.client.WebPollerListenerAdapter;
import org.realityforge.replicant.client.ChangeMapper;
import org.realityforge.replicant.client.EntityChangeBroker;
import org.realityforge.replicant.client.EntityRepository;
import org.realityforge.replicant.client.EntitySubscriptionManager;
import org.realityforge.replicant.client.transport.CacheService;
import org.realityforge.replicant.client.transport.ClientSession;
import org.realityforge.replicant.client.transport.DataLoadStatus;
import org.realityforge.replicant.client.transport.SessionContext;
import org.realityforge.replicant.shared.transport.ReplicantContext;

public abstract class WebPollerDataLoaderService<T extends ClientSession<T, G>, G extends Enum>
  extends GwtDataLoaderService<T, G>
{
  private final WebPoller _webPoller = WebPoller.newWebPoller();
  private String _basePollURL;

  class ReplicantRequestFactory
    extends AbstractHttpRequestFactory
  {
    @Override
    protected RequestBuilder getRequestBuilder()
    {
      final RequestBuilder rb = new RequestBuilder( RequestBuilder.GET, getPollURL() );
      rb.setHeader( ReplicantContext.SESSION_ID_HEADER, getSessionID() );
      return rb;
    }
  }

  public WebPollerDataLoaderService( @Nonnull final SessionContext sessionContext,
                                     @Nonnull final ChangeMapper changeMapper,
                                     @Nonnull final EntityChangeBroker changeBroker,
                                     @Nonnull final EntityRepository repository,
                                     @Nonnull final CacheService cacheService,
                                     @Nonnull final EntitySubscriptionManager subscriptionManager,
                                     @Nonnull final ReplicantConfig replicantConfig )
  {
    super( sessionContext, changeMapper, changeBroker, repository, cacheService, subscriptionManager, replicantConfig );
    _webPoller.setListener( new WebPollerListenerAdapter()
    {
      @Override
      public void onMessage( @Nonnull final WebPoller webPoller,
                             @Nonnull final Map<String, String> context,
                             @Nonnull final String data )
      {
        handlePollSuccess( data );
      }

      @Override
      public void onError( @Nonnull final WebPoller webPoller, @Nonnull final Throwable exception )
      {
        handleSystemFailure( exception, "Failed to poll" );
      }
    } );
    setupCloseHandler();
    _basePollURL = deriveDefaultPollURL();
  }

  protected String deriveDefaultPollURL()
  {
    return guessContextURL() + "api";
  }

  protected String guessContextURL()
  {
    final String moduleBaseURL = GWT.getModuleBaseURL();
    final String moduleName = GWT.getModuleName();
    return moduleBaseURL.substring( 0, moduleBaseURL.length() - moduleName.length() - 1 );
  }

  protected void setupCloseHandler()
  {
    final Window.ClosingHandler handler = new Window.ClosingHandler()
    {
      @Override
      public void onWindowClosing( final Window.ClosingEvent event )
      {
        disconnect( null );
      }
    };
    Window.addWindowClosingHandler( handler );
  }

  @Nonnull
  protected abstract T createSession( @Nonnull String sessionID );

  protected final void onSessionCreated( @Nonnull final String sessionID, @Nullable final Runnable runnable )
  {
    setSession( createSession( sessionID ), runnable );
    scheduleDataLoad();
    startPolling();
  }

  public void disconnect( @Nullable final Runnable runnable )
  {
    stopPolling();
    setSession( null, runnable );
  }

  /**
   * Set the base url at which the replicant jaxrs resource is anchored.
   */
  public void setBasePollURL( @Nullable final String basePollURL )
  {
    _basePollURL = basePollURL;
  }

  /**
   * Return the base url at which the replicant jaxrs resource is anchored.
   */
  @Nonnull
  protected String getBasePollURL()
  {
    return _basePollURL;
  }

  /**
   * Return the url to poll for replicant data stream, derived from getBasePollURL().
   */
  @Nonnull
  protected String getPollURL()
  {
    return getBasePollURL() + ReplicantContext.REPLICANT_URL_FRAGMENT + "?rx=" + getSession().getLastRxSequence();
  }

  final void handlePollSuccess( final String rawJsonData )
  {
    if ( null != rawJsonData )
    {
      logResponse( rawJsonData );
      getSession().enqueueDataLoad( rawJsonData );
      if ( !_webPoller.isPaused() )
      {
        _webPoller.pause();
      }
    }
  }

  private void logResponse( final String rawJsonData )
  {
    if ( LOG.isLoggable( Level.INFO ) )
    {
      final int threshold = getThresholdForResponseLogging();
      final String messageData =
        0 != threshold && rawJsonData.length() > threshold ?
        rawJsonData.substring( 0, threshold ) + "..." :
        rawJsonData;
      LOG.info( getSessionContext().getKey() + ".Poll - Received data: " + messageData );
    }
  }

  protected int getThresholdForResponseLogging()
  {
    return 300;
  }

  @Override
  protected void onDataLoadComplete( @Nonnull final DataLoadStatus status )
  {
    if ( _webPoller.isPaused() )
    {
      _webPoller.resume();
    }
  }

  @Override
  protected void progressDataLoadFailure( @Nonnull final Exception e )
  {
    handleSystemFailure( e, "Failed to progress data load" );
  }

  protected abstract void handleSystemFailure( @Nullable Throwable caught, @Nonnull String message );

  private void startPolling()
  {
    stopPolling();
    _webPoller.setRequestFactory( new ReplicantRequestFactory() );
    _webPoller.setInterRequestDuration( 0 );
    _webPoller.start();
  }

  private void stopPolling()
  {
    if ( isConnected() )
    {
      _webPoller.stop();
    }
  }

  public boolean isConnected()
  {
    return _webPoller.isActive();
  }
}

@@ -1294,28 +1294,24 @@
 String _base
-Poll
 URL;%0A%0A  clas
@@ -2915,28 +2915,24 @@
 );%0A    _base
-Poll
 URL = derive
@@ -2934,28 +2934,24 @@
 eriveDefault
-Poll
 URL();%0A  %7D%0A%0A
@@ -2982,20 +2982,16 @@
 eDefault
-Poll
 URL()%0A  
@@ -4109,28 +4109,24 @@
 void setBase
-Poll
 URL( @Nullab
@@ -4141,28 +4141,24 @@
  String base
-Poll
 URL )%0A  %7B%0A  
@@ -4164,20 +4164,16 @@
    _base
-Poll
 URL = ba
@@ -4170,28 +4170,24 @@
 seURL = base
-Poll
 URL;%0A  %7D%0A%0A  
@@ -4305,28 +4305,24 @@
 ring getBase
-Poll
 URL()%0A  %7B%0A  
@@ -4335,20 +4335,16 @@
 rn _base
-Poll
 URL;%0A  %7D
@@ -4422,28 +4422,24 @@
 from getBase
-Poll
 URL().%0A   */
@@ -4504,20 +4504,16 @@
  getBase
-Poll
 URL() + 

Rename _basePollURL to _baseURL