/*
 * Copyright (c) 2005 Aetrion LLC.
 */
package com.googlecode.flickrjandroid.photosets;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.googlecode.flickrjandroid.Flickr;
import com.googlecode.flickrjandroid.FlickrException;
import com.googlecode.flickrjandroid.Parameter;
import com.googlecode.flickrjandroid.Response;
import com.googlecode.flickrjandroid.Transport;
import com.googlecode.flickrjandroid.oauth.OAuthInterface;
import com.googlecode.flickrjandroid.oauth.OAuthUtils;
import com.googlecode.flickrjandroid.people.User;
import com.googlecode.flickrjandroid.photos.Extras;
import com.googlecode.flickrjandroid.photos.Photo;
import com.googlecode.flickrjandroid.photos.PhotoContext;
import com.googlecode.flickrjandroid.photos.PhotoList;
import com.googlecode.flickrjandroid.photos.PhotoUtils;
import com.googlecode.flickrjandroid.util.JSONUtils;
import com.googlecode.flickrjandroid.util.StringUtilities;

/**
 * Interface for working with photosets.
 * 
 * @author Anthony Eden
 * @version $Id: PhotosetsInterface.java,v 1.27 2009/11/08 21:58:00 x-mago Exp $
 */
public class PhotosetsInterface {

	public static final String METHOD_ADD_PHOTO = "flickr.photosets.addPhoto";
	public static final String METHOD_CREATE = "flickr.photosets.create";
	public static final String METHOD_DELETE = "flickr.photosets.delete";
	public static final String METHOD_EDIT_META = "flickr.photosets.editMeta";
	public static final String METHOD_EDIT_PHOTOS = "flickr.photosets.editPhotos";
	public static final String METHOD_GET_CONTEXT = "flickr.photosets.getContext";
	public static final String METHOD_GET_INFO = "flickr.photosets.getInfo";
	public static final String METHOD_GET_LIST = "flickr.photosets.getList";
	public static final String METHOD_GET_PHOTOS = "flickr.photosets.getPhotos";
	public static final String METHOD_ORDER_SETS = "flickr.photosets.orderSets";
	public static final String METHOD_REMOVE_PHOTO = "flickr.photosets.removePhoto";
	public static final String METHOD_REORDER_PHOTOS = "flickr.photosets.reorderPhotos";
	public static final String METHOD_SET_PRIMARY_PHOTO = "flickr.photosets.setPrimaryPhoto";

	private String apiKey;
	private String sharedSecret;
	private Transport transportAPI;

	public PhotosetsInterface(String apiKey, String sharedSecret, Transport transportAPI) {
		this.apiKey = apiKey;
		this.sharedSecret = sharedSecret;
		this.transportAPI = transportAPI;
	}

	/**
	 * Add a photo to the end of the photoset.
	 * <p/>
	 * Note: requires authentication with the new authentication API with 'write' permission.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @param photoId
	 *            The photo ID
	 * @throws JSONException
	 */
	public void addPhoto(String photosetId, String photoId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_ADD_PHOTO));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("photo_id", photoId));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	/**
	 * Create a new photoset.
	 * 
	 * @param title
	 *            The photoset title
	 * @param description
	 *            The photoset description
	 * @param primaryPhotoId
	 *            The primary photo id
	 * @return The new Photset
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public Photoset create(String title, String description, String primaryPhotoId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_CREATE));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("title", title));
		parameters.add(new Parameter("description", description));
		parameters.add(new Parameter("primary_photo_id", primaryPhotoId));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
		JSONObject photosetElement = response.getData().getJSONObject("photoset");
		Photoset photoset = new Photoset();
		photoset.setId(photosetElement.getString("id"));
		photoset.setUrl(photosetElement.getString("url"));
		return photoset;
	}

	/**
	 * Delete the specified photoset.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void delete(String photosetId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_DELETE));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	/**
	 * Modify the meta-data for a photoset.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @param title
	 *            A new title
	 * @param description
	 *            A new description (can be null)
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void editMeta(String photosetId, String title, String description) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_EDIT_META));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("title", title));
		if (description != null) {
			parameters.add(new Parameter("description", description));
		}
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	/**
	 * Edit which photos are in the photoset.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @param primaryPhotoId
	 *            The primary photo Id
	 * @param photoIds
	 *            The photo IDs for the photos in the set
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void editPhotos(String photosetId, String primaryPhotoId, String[] photoIds) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_EDIT_PHOTOS));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("primary_photo_id", primaryPhotoId));
		parameters.add(new Parameter("photo_ids", StringUtilities.join(photoIds, ",")));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	/**
	 * Get a photo's context in the specified photo set.
	 * 
	 * This method does not require authentication.
	 * 
	 * @param photoId
	 *            The photo ID
	 * @param photosetId
	 *            The photoset ID
	 * @return The PhotoContext
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public PhotoContext getContext(String photoId, String photosetId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_GET_CONTEXT));
		parameters.add(new Parameter("api_key", apiKey));

		parameters.add(new Parameter("photo_id", photoId));
		parameters.add(new Parameter("photoset_id", photosetId));

		// the Flickr API page says this method does not require OAuth
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.get(transportAPI.getPath(), parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
		JSONObject payload = response.getData();
		Iterator<?> keys = payload.keys();
		PhotoContext photoContext = new PhotoContext();
		while (keys.hasNext()) {
			String key = String.valueOf(keys.next());
			JSONObject element = payload.optJSONObject(key);
			if (key.equals("prevphoto")) {
				String id = element.getString("id");
				if ("0".equals(id) == false) {
					// this is the first photo
					Photo photo = new Photo();
					photo.setId(id);
					photo.setTitle(element.optString("title"));
					photo.setUrl(element.optString("url"));
					photoContext.setPreviousPhoto(photo);
				}
			} else if (key.equals("nextphoto")) {
				String id = element.getString("id");
				if ("0".equals(id) == false) {
					// this is the last photo
					Photo photo = new Photo();
					photo.setId(id);
					photo.setTitle(element.optString("title"));
					photo.setUrl(element.optString("url"));
					photoContext.setNextPhoto(photo);
				}
			} else if (key.equals("count")) {
				// TODO: process this information
			} else {
				System.err.println("unsupported element name: " + key);
			}
		}
		return photoContext;
	}

	/**
	 * Get the information for a specified photoset.
	 * 
	 * This method does not require authentication.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @return The Photoset
	 * @throws FlickrException
	 * @throws IOException
	 * @throws JSONException
	 */
	public Photoset getInfo(String photosetId) throws FlickrException, IOException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_GET_INFO));
		parameters.add(new Parameter("api_key", apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));

		Response response = transportAPI.get(transportAPI.getPath(), parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
		JSONObject photosetElement = response.getData().getJSONObject("photoset");
		Photoset photoset = new Photoset();
		photoset.setId(photosetElement.getString("id"));

		User owner = new User();
		owner.setId(photosetElement.getString("owner"));
		photoset.setOwner(owner);

		Photo primaryPhoto = new Photo();
		primaryPhoto.setId(photosetElement.getString("primary"));
		primaryPhoto.setSecret(photosetElement.getString("secret")); // TODO verify that this is the secret for the photo
		primaryPhoto.setServer(photosetElement.getString("server")); // TODO verify that this is the server for the photo
		primaryPhoto.setFarm(photosetElement.getString("farm"));
		photoset.setPrimaryPhoto(primaryPhoto);

		// TODO remove secret/server/farm from photoset?
		// It's rather related to the primaryPhoto, then to the photoset itself.
		photoset.setSecret(photosetElement.getString("secret"));
		photoset.setServer(photosetElement.getString("server"));
		photoset.setFarm(photosetElement.getString("farm"));
		photoset.setPhotoCount(photosetElement.getString("photos"));

		photoset.setTitle(JSONUtils.getChildValue(photosetElement, "title"));
		photoset.setDescription(JSONUtils.getChildValue(photosetElement, "description"));
		photoset.setPrimaryPhoto(primaryPhoto);

		return photoset;
	}

	/**
	 * Get a list of all photosets for the specified user.
	 * 
	 * This method does not require authentication. But to get a Photoset into the list, that contains just private photos, the call needs to be authenticated.
	 * 
	 * @param userId
	 *            The User id
	 * @return The Photosets collection
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public Photosets getList(String userId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_GET_LIST));

		boolean signed = OAuthUtils.hasSigned();
		if (signed) {
			parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));
		} else {
			parameters.add(new Parameter("api_key", apiKey));
		}

		if (userId != null) {
			parameters.add(new Parameter("user_id", userId));
		}

		if (signed) {
			OAuthUtils.addOAuthToken(parameters);
		}

		Response response = signed ? transportAPI.postJSON(sharedSecret, parameters) : transportAPI.get(transportAPI.getPath(), parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
		Photosets photosetsObject = new Photosets();
		JSONObject photosetsElement = response.getData().getJSONObject("photosets");
		List<Photoset> photosets = new ArrayList<Photoset>();
		JSONArray photosetElements = photosetsElement.optJSONArray("photoset");
		for (int i = 0; photosetElements != null && i < photosetElements.length(); i++) {
			JSONObject photosetElement = photosetElements.getJSONObject(i);
			Photoset photoset = new Photoset();
			photoset.setId(photosetElement.getString("id"));

			if (photosetElement.has("owner")) {
				User owner = new User();
				owner.setId(photosetElement.getString("owner"));
				photoset.setOwner(owner);
			}

			Photo primaryPhoto = new Photo();
			primaryPhoto.setId(photosetElement.getString("primary"));
			primaryPhoto.setSecret(photosetElement.getString("secret")); // TODO verify that this is the secret for the photo
			primaryPhoto.setServer(photosetElement.getString("server")); // TODO verify that this is the server for the photo
			primaryPhoto.setFarm(photosetElement.getString("farm"));
			photoset.setPrimaryPhoto(primaryPhoto);

			photoset.setSecret(photosetElement.getString("secret"));
			photoset.setServer(photosetElement.getString("server"));
			photoset.setFarm(photosetElement.getString("farm"));
			photoset.setPhotoCount(photosetElement.getString("photos"));

			photoset.setTitle(JSONUtils.getChildValue(photosetElement, "title"));
			photoset.setDescription(JSONUtils.getChildValue(photosetElement, "description"));

			photosets.add(photoset);
		}

		photosetsObject.setPhotosets(photosets);
		return photosetsObject;
	}

	/**
	 * Get a collection of Photo objects for the specified Photoset.
	 * 
	 * This method does not require authentication.
	 * 
	 * @see com.gmail.yuyang226.flickr.photos.Extras
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_NO_FILTER
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_PUBLIC
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS_FAMILY
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FAMILY
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS
	 * @param photosetId
	 *            The photoset ID
	 * @param extras
	 *            Set of extra-fields
	 * @param privacy_filter
	 *            filter value for authenticated calls
	 * @param perPage
	 *            The number of photos per page
	 * @param page
	 *            The page offset
	 * @return PhotoList The Collection of Photo objects
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public PhotoList getPhotos(String photosetId, Set<String> extras, int privacy_filter, int perPage, int page) throws IOException, FlickrException, JSONException {
		PhotoList photos = new PhotoList();
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_GET_PHOTOS));
		boolean signed = OAuthUtils.hasSigned();
		if (signed) {
			parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));
		} else {
			parameters.add(new Parameter("api_key", apiKey));
		}

		parameters.add(new Parameter("photoset_id", photosetId));

		if (perPage > 0) {
			parameters.add(new Parameter("per_page", Integer.valueOf(perPage)));
		}

		if (page > 0) {
			parameters.add(new Parameter("page", Integer.valueOf(page)));
		}

		if (privacy_filter > 0) {
			parameters.add(new Parameter("privacy_filter", "" + privacy_filter));
		}

		if (extras != null && !extras.isEmpty()) {
			parameters.add(new Parameter(Extras.KEY_EXTRAS, StringUtilities.join(extras, ",")));
		}
		if (signed) {
			OAuthUtils.addOAuthToken(parameters);
		}

		Response response = signed ? transportAPI.postJSON(sharedSecret, parameters) : transportAPI.get(transportAPI.getPath(), parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}

		JSONObject photoset = response.getData().getJSONObject("photoset");
		JSONArray photoElements = photoset.optJSONArray("photo");
		photos.setPage(photoset.getString("page"));
		photos.setPages(photoset.getString("pages"));
		photos.setPerPage(photoset.getString("per_page"));
		photos.setTotal(photoset.getString("total"));

		for (int i = 0; photoElements != null && i < photoElements.length(); i++) {
			JSONObject photoElement = photoElements.getJSONObject(i);
			photos.add(PhotoUtils.createPhoto(photoElement));
		}

		return photos;
	}

	/**
	 * Convenience method.
	 * 
	 * Calls getPhotos() with Extras.MIN_EXTRAS and Flickr.PRIVACY_LEVEL_NO_FILTER.
	 * 
	 * This method does not require authentication.
	 * 
	 * @see com.gmail.yuyang226.flickr.photos.Extras
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_NO_FILTER
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_PUBLIC
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS_FAMILY
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FAMILY
	 * @see com.gmail.yuyang226.flickr.Flickr#PRIVACY_LEVEL_FRIENDS
	 * @param photosetId
	 *            The photoset ID
	 * @param perPage
	 *            The number of photos per page
	 * @param page
	 *            The page offset
	 * @return PhotoList The Collection of Photo objects
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public PhotoList getPhotos(String photosetId, int perPage, int page) throws IOException, FlickrException, JSONException {
		return getPhotos(photosetId, Extras.MIN_EXTRAS, Flickr.PRIVACY_LEVEL_NO_FILTER, perPage, page);
	}

	/**
	 * Set the order in which sets are returned for the user.
	 * 
	 * This method requires authentication with 'write' permission.
	 * 
	 * @param photosetIds
	 *            An array of Ids
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void orderSets(String[] photosetIds) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_ORDER_SETS));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_ids", StringUtilities.join(photosetIds, ",")));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	/**
	 * Set the order in which sets are returned for the user.
	 * 
	 * This method requires authentication with 'write' permission.
	 * 
	 * @param photosetIds
	 *            An array of Ids
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void reorderPhotos(String photosetId, List<String> photoIds) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_REORDER_PHOTOS));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("photo_ids", StringUtilities.join(photoIds, ",")));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}
	/**
	 * Remove a photo from the set.
	 * 
	 * @param photosetId
	 *            The photoset ID
	 * @param photoId
	 *            The photo ID
	 * @throws IOException
	 * @throws FlickrException
	 * @throws JSONException
	 */
	public void removePhoto(String photosetId, String photoId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_REMOVE_PHOTO));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("photo_id", photoId));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

	public void setPrimaryPhoto(String photosetId, String photoId) throws IOException, FlickrException, JSONException {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter("method", METHOD_SET_PRIMARY_PHOTO));
		parameters.add(new Parameter(OAuthInterface.PARAM_OAUTH_CONSUMER_KEY, apiKey));

		parameters.add(new Parameter("photoset_id", photosetId));
		parameters.add(new Parameter("photo_id", photoId));
		OAuthUtils.addOAuthToken(parameters);

		Response response = transportAPI.postJSON(sharedSecret, parameters);
		if (response.isError()) {
			throw new FlickrException(response.getErrorCode(), response.getErrorMessage());
		}
	}

}