/* 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.server;

import java.util.List;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.users.User;
import com.google.appengine.demos.sticky.client.model.Note;
import com.google.appengine.demos.sticky.client.model.Surface;

/**
 * A more typesafe wrapper around a {@link MemcacheService}.
 * 
 * @author knorton@google.com (Kelly Norton)
 */
public class Cache {
  private static String createNotesId(String surfaceKey) {
    return NOTES_PREFIX + surfaceKey;
  }

  private static String createNotesId(User user, String surfaceKey) {
    return NOTES_PREFIX + user.getEmail() + surfaceKey;
  }

  private static String createSurfaceId(Key surfaceKey) {
    return SURFACE_PREFIX + KeyFactory.keyToString(surfaceKey);
  }

  private static String createSurfaceKeysId(String email) {
    return SURFACEKEYS_PRFIX + email;
  }

  /**
   * The memcache service to use for caching.
   */
  private final MemcacheService memcache;

  private static final String NOTES_PREFIX = "NOTES/";

  private static final String SURFACE_PREFIX = "SURFACE/";

  private static final String SURFACEKEYS_PRFIX = "SURFACES/";

  /**
   * Creates a new cache.
   * 
   * @param memcache
   *          the memcache service to use for caching objects
   */
  public Cache(MemcacheService memcache) {
    this.memcache = memcache;
  }

  /**
   * Deletes the collection of notes that are cached for a surface. This is used
   * to invalidate cache entries.
   * 
   * @param surfaceKey
   *          the key for the surface
   */
  public void deleteNotes(String surfaceKey) {
    memcache.delete(NOTES_PREFIX + surfaceKey);
  }

  /**
   * Deletes a {@link Surface} from the cache. This is used to invalidate cache
   * entries.
   * 
   * @param surfaceKey
   */
  public void deleteSurface(Key surfaceKey) {
    memcache.delete(createSurfaceId(surfaceKey));
  }

  /**
   * Deletes a collection of surface keys for a user.
   * 
   * @param email
   *          the user's email
   */
  public void deleteSurfaceKeys(String email) {
    memcache.delete(createSurfaceKeysId(email));
  }

  /**
   * Attempts to fetch the collection of notes contained in a surface for a
   * particular user. If there is a value, but the user is not known to have
   * access to this surface, <code>null</code> will be returned.
   * 
   * @param user
   *          the user requesting access to the cache entry
   * @param surfaceKey
   *          they key for the surface
   * @return a collection of notes if there is a cache entry, <code>null</code>
   *         otherwise
   */
  public Note[] getNotes(User user, String surfaceKey) {
    if (!canUserAccessNotes(user, surfaceKey)) {
      return null;
    }
    return (Note[]) memcache.get(createNotesId(surfaceKey));
  }

  /**
   * Attempts to fetch a surface from cache.
   * 
   * @param surfaceKey
   *          the key of the surface
   * @return the surface if there is a cache entry, <code>null</code> otherwise
   */
  public Surface getSurface(Key surfaceKey) {
    return (Surface) memcache.get(createSurfaceId(surfaceKey));
  }

  /**
   * Attempts to fetch a collection of surface keys for a particular user.
   * 
   * @param email
   *          the email for the user
   * @return a collection of keys if there is a cache entry, <code>null</code>
   *         otherwise
   */
  @SuppressWarnings("unchecked")
  public List<Key> getSurfaceKeys(String email) {
    return (List<Key>) memcache.get(createSurfaceKeysId(email));
  }

  /**
   * Adds the collection of notes in a surface to the cache and grants the user
   * access to that entry.
   * 
   * @param user
   *          the user that should be given access to the cache entry
   * @param surfaceKey
   *          the key for the surface
   * @param notes
   *          the collection of notes to cache
   * @return <code>notes</code>, for call chaining
   */
  public Note[] putNotes(User user, String surfaceKey, Note[] notes) {
    memcache.put(createNotesId(surfaceKey), notes);
    allowUserToAccessNotes(user, surfaceKey);
    return notes;
  }

  /**
   * Adds a surface object to the cache.
   * 
   * @param surfaceKey
   *          the key of the surface
   * @param surface
   *          the surface object
   * @return <code>surface</code>, for call chaining
   */
  public Surface putSurface(Key surfaceKey, Surface surface) {
    memcache.put(createSurfaceId(surfaceKey), surface);
    return surface;
  }

  /**
   * Adds the collection of surface keys to the cache for a user (by email).
   * 
   * @param email
   *          the user's email
   * @param surfaceKeys
   *          a collection of surface keys
   * @return <code>surfaceKeys</code>, for call chaining
   */
  public List<Key> putSurfaceKeys(String email, List<Key> surfaceKeys) {
    memcache.put(createSurfaceKeysId(email), surfaceKeys);
    return surfaceKeys;
  }

  private void allowUserToAccessNotes(User user, String surfaceKey) {
    memcache.put(createNotesId(user, surfaceKey), Boolean.TRUE);
  }

  private boolean canUserAccessNotes(User user, String surfaceKey) {
    return memcache.contains(createNotesId(user, surfaceKey));
  }
}
