/* Copyright (c) 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.appengine.demos.sticky.client.model;

import java.util.HashMap;
import java.util.Map;

import com.google.appengine.demos.sticky.client.model.Service.GetSurfacesResult;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * Controls all aspects of loading the set of {@link Surface}s associated with
 * the current author. This class takes care of performing (and possibly
 * retrying) a query for the initial set of Notes and then continues polling the
 * server for updates.
 * 
 * @author knorton@google.com (Kelly Norton)
 */
class SurfaceLoader extends Timer implements AsyncCallback<GetSurfacesResult> {

  /**
   * Controls the initial laod of surfaces from the server and will retry on
   * failure.
   */
  private class InitialLoader extends RetryTimer implements
      AsyncCallback<GetSurfacesResult> {

    /**
     * Constructs and starts an initial load.
     */
    public InitialLoader() {
      start();
    }

    public void onFailure(Throwable caught) {
      model.onServerFailed(caught instanceof Service.AccessDeniedException);

      retryLater();
    }

    public void onSuccess(GetSurfacesResult result) {
      model.onServerSucceeded();

      startPolling();

      assert result != null;

      timestamp = result.getTimestamp();
      final Surface[] surfaces = result.getSurfaces();
      for (int i = 0, n = surfaces.length; i < n; ++i) {
        final Surface surface = surfaces[i];
        surface.initialize(model);
        surfaceCache.put(surface.getKey(), surface);
      }
      model.notifySurfacesReceived(surfaces);
    }

    private void start() {
      model.getService().getSurfaces(null /* timestamp */, this);
    }

    @Override
    protected void retry() {
      start();
    }
  }

  /**
   * The model being controlled by this loader.
   */
  private final Model model;

  /**
   * A cache of {@link Surface}s that have been loaded. Used to ensure that
   * there is only one instance of each surface.
   */
  private final Map<String, Surface> surfaceCache = new HashMap<String, Surface>();

  /**
   * The polling period.
   */
  private final int interval;

  /**
   * The most recent timestamp returned by the server.
   */
  private String timestamp;

  /**
   * Constructs a new loader for the given model.
   * 
   * @param model
   *          the model this loader will control
   * @param interval
   *          the polling interval
   */
  public SurfaceLoader(Model model, int interval) {
    this.model = model;
    this.interval = interval;
  }

  public void onFailure(Throwable caught) {
    model.onServerFailed(caught instanceof Service.AccessDeniedException);
  }

  public void onSuccess(GetSurfacesResult result) {
    model.onServerSucceeded();
    if (result != null) {
      timestamp = result.getTimestamp();
      final Surface[] surfaces = result.getSurfaces();
      for (int i = 0, n = surfaces.length; i < n; ++i) {
        final Surface surface = surfaces[i];
        final Surface existing = surfaceCache.get(surface.getKey());
        if (existing == null) {
          surface.initialize(model);
          surfaceCache.put(surface.getKey(), surface);
          model.notifySurfaceCreated(surface);
        } else {
          existing.update(model, surface);
        }
      }
    }
  }

  @Override
  public void run() {
    model.getService().getSurfaces(timestamp, this);
  }

  /**
   * Starts the loader by issuing an initial load. When that load completes, the
   * loader will start polling.
   */
  public void start() {
    new InitialLoader();
  }

  private void startPolling() {
    scheduleRepeating(interval);
  }

  /**
   * Invoked by {@link Model} when it creates a {@link Surface}.
   * 
   * @param key
   *          the key for the newly saved surface
   * @param surface
   *          a reference to the surface
   */
  void cacheSurface(String key, Surface surface) {
    assert key != null;
    surfaceCache.put(key, surface);
  }
}
