/* Copyright (c) 2008 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 sample.photos;

import com.google.gdata.client.photos.PicasawebService;
import com.google.gdata.data.Link;
import com.google.gdata.data.photos.AlbumEntry;
import com.google.gdata.data.photos.AlbumFeed;
import com.google.gdata.data.photos.CommentEntry;
import com.google.gdata.data.photos.GphotoEntry;
import com.google.gdata.data.photos.GphotoFeed;
import com.google.gdata.data.photos.PhotoEntry;
import com.google.gdata.data.photos.TagEntry;
import com.google.gdata.data.photos.UserFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * This is a simple client that provides high-level operations on the Picasa Web
 * Albums GData API. It can also be used as a command-line application to test
 * out some of the features of the API.
 *
 * 
 */
public class PicasawebClient {

  private static final String API_PREFIX
      = "https://picasaweb.google.com/data/feed/api/user/";

  private final PicasawebService service;

  /**
   * Constructs a new un-authenticated client.
   */
  public PicasawebClient(PicasawebService service) {
    this(service, null, null);
  }

  /**
   * Constructs a new client with the given username and password.
   */
  public PicasawebClient(PicasawebService service, String uname,
      String passwd) {
    this.service = service;

    if (uname != null && passwd != null) {
      try {
        service.setUserCredentials(uname, passwd);
      } catch (AuthenticationException e) {
        throw new IllegalArgumentException(
            "Illegal username/password combination.");
      }
    }
  }

  /**
   * Retrieves the albums for the given user.
   */
  public List<AlbumEntry> getAlbums(String username) throws IOException,
      ServiceException {

    String albumUrl = API_PREFIX + username;
    UserFeed userFeed = getFeed(albumUrl, UserFeed.class);

    List<GphotoEntry> entries = userFeed.getEntries();
    List<AlbumEntry> albums = new ArrayList<AlbumEntry>();
    for (GphotoEntry entry : entries) {
      GphotoEntry adapted = entry.getAdaptedEntry();
      if (adapted instanceof AlbumEntry) {
        albums.add((AlbumEntry) adapted);
      }
    }
    return albums;
  }

  /**
   * Retrieves the albums for the currently logged-in user.  This is equivalent
   * to calling {@link #getAlbums(String)} with "default" as the username.
   */
  public List<AlbumEntry> getAlbums() throws IOException, ServiceException {
    return getAlbums("default");
  }

  /**
   * Retrieves the tags for the given user.  These are tags aggregated across
   * the entire account.
   */
  public List<TagEntry> getTags(String uname) throws IOException,
      ServiceException {
    String tagUrl = API_PREFIX + uname + "?kind=tag";
    UserFeed userFeed = getFeed(tagUrl, UserFeed.class);

    List<GphotoEntry> entries = userFeed.getEntries();
    List<TagEntry> tags = new ArrayList<TagEntry>();
    for (GphotoEntry entry : entries) {
      GphotoEntry adapted = entry.getAdaptedEntry();
      if (adapted instanceof TagEntry) {
        tags.add((TagEntry) adapted);
      }
    }
    return tags;
  }

  /**
   * Retrieves the tags for the currently logged-in user.  This is equivalent
   * to calling {@link #getTags(String)} with "default" as the username.
   */
  public List<TagEntry> getTags() throws IOException, ServiceException {
    return getTags("default");
  }

  /**
   * Retrieves the photos for the given album.
   */
  public List<PhotoEntry> getPhotos(AlbumEntry album) throws IOException,
      ServiceException {

    String feedHref = getLinkByRel(album.getLinks(), Link.Rel.FEED);
    AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

    List<GphotoEntry> entries = albumFeed.getEntries();
    List<PhotoEntry> photos = new ArrayList<PhotoEntry>();
    for (GphotoEntry entry : entries) {
      GphotoEntry adapted = entry.getAdaptedEntry();
      if (adapted instanceof PhotoEntry) {
        photos.add((PhotoEntry) adapted);
      }
    }
    return photos;
  }

  /**
   * Retrieves the comments for the given photo.
   */
  public List<CommentEntry> getComments(PhotoEntry photo) throws IOException,
      ServiceException {

    String feedHref = getLinkByRel(photo.getLinks(), Link.Rel.FEED);
    AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

    List<GphotoEntry> entries = albumFeed.getEntries();
    List<CommentEntry> comments = new ArrayList<CommentEntry>();
    for (GphotoEntry entry : entries) {
      GphotoEntry adapted = entry.getAdaptedEntry();
      if (adapted instanceof CommentEntry) {
        comments.add((CommentEntry) adapted);
      }
    }
    return comments;
  }

  /**
   * Retrieves the tags for the given taggable entry.  This is valid on user,
   * album, and photo entries only.
   */
  public List<TagEntry> getTags(GphotoEntry<?> parent) throws IOException,
      ServiceException {

    String feedHref = getLinkByRel(parent.getLinks(), Link.Rel.FEED);
    feedHref = addKindParameter(feedHref, "tag");
    AlbumFeed albumFeed = getFeed(feedHref, AlbumFeed.class);

    List<GphotoEntry> entries = albumFeed.getEntries();
    List<TagEntry> tags = new ArrayList<TagEntry>();
    for (GphotoEntry entry : entries) {
      GphotoEntry adapted = entry.getAdaptedEntry();
      if (adapted instanceof TagEntry) {
        tags.add((TagEntry) adapted);
      }
    }
    return tags;
  }

  /**
   * Album-specific insert method to insert into the gallery of the current
   * user, this bypasses the need to have a top-level entry object for parent.
   */
  public AlbumEntry insertAlbum(AlbumEntry album)
      throws IOException, ServiceException {
    String feedUrl = API_PREFIX + "default";
    return service.insert(new URL(feedUrl), album);
  }

  /**
   * Insert an entry into another entry.  Because our entries are a hierarchy,
   * this lets you insert a photo into an album even if you only have the
   * album entry and not the album feed, making it quicker to traverse the
   * hierarchy.
   */
  public <T extends GphotoEntry> T insert(GphotoEntry<?> parent, T entry)
      throws IOException, ServiceException {

    String feedUrl = getLinkByRel(parent.getLinks(), Link.Rel.FEED);
    return service.insert(new URL(feedUrl), entry);
  }

  /**
   * Helper function to allow retrieval of a feed by string url, which will
   * create the URL object for you.  Most of the Link objects have a string
   * href which must be converted into a URL by hand, this does the conversion.
   */
  public <T extends GphotoFeed> T getFeed(String feedHref,
      Class<T> feedClass) throws IOException, ServiceException {
    System.out.println("Get Feed URL: " + feedHref);
    return service.getFeed(new URL(feedHref), feedClass);
    }

  /**
   * Helper function to add a kind parameter to a url.
   */
  public String addKindParameter(String url, String kind) {
    if (url.contains("?")) {
      return url + "&kind=" + kind;
    } else {
      return url + "?kind=" + kind;
    }
  }

  /**
   * Helper function to get a link by a rel value.
   */
  public String getLinkByRel(List<Link> links, String relValue) {
    for (Link link : links) {
      if (relValue.equals(link.getRel())) {
        return link.getHref();
      }
    }
    throw new IllegalArgumentException("Missing " + relValue + " link.");
  }
}
