/*
 * Copyright (c) 2012 Socialize Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.socialize.networks.facebook;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import com.facebook.HttpMethod;
import com.socialize.ConfigUtils;
import com.socialize.ShareUtils;
import com.socialize.Socialize;
import com.socialize.SocializeService;
import com.socialize.api.SocializeSession;
import com.socialize.api.action.share.ShareOptions;
import com.socialize.api.action.share.SocialNetworkShareListener;
import com.socialize.api.action.user.UserSystem;
import com.socialize.auth.*;
import com.socialize.auth.facebook.FacebookActivity;
import com.socialize.auth.facebook.FacebookAuthProviderInfo;
import com.socialize.auth.facebook.FacebookAuthProviderInfo.PermissionType;
import com.socialize.config.SocializeConfig;
import com.socialize.entity.Entity;
import com.socialize.entity.PropagationInfo;
import com.socialize.error.SocializeException;
import com.socialize.listener.AuthProviderListener;
import com.socialize.listener.ListenerHolder;
import com.socialize.listener.SocializeAuthListener;
import com.socialize.log.SocializeLogger;
import com.socialize.networks.*;
import com.socialize.ui.profile.UserSettings;
import com.socialize.util.ArrayUtils;
import com.socialize.util.ImageUtils;

import java.util.*;
import java.util.Map.Entry;


/**
 * @author Jason Polites
 */
public abstract class BaseFacebookFacade implements FacebookFacade {
	
	protected ListenerHolder holder; // This is a singleton
	protected UserSystem userSystem;
	protected SocializeLogger logger;
	protected SocializeConfig config;	
	protected ImageUtils imageUtils;

	/*
	 * (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#authenticate(android.app.Activity, com.socialize.auth.facebook.FacebookAuthProviderInfo, com.socialize.listener.AuthProviderListener)
	 */
	@Override
	public void authenticateWithActivity(Activity context, FacebookAuthProviderInfo info, boolean sso, final AuthProviderListener listener) {

		final String listenerKey = "auth";
		
		holder.push(listenerKey, new AuthProviderListener() {
			
			@Override
			public void onError(SocializeException error) {
				holder.remove(listenerKey);
				listener.onError(error);
			}
			
			@Override
			public void onAuthSuccess(AuthProviderResponse response) {
				holder.remove(listenerKey);
				listener.onAuthSuccess(response);
			}
			
			@Override
			public void onAuthFail(SocializeException error) {
				holder.remove(listenerKey);
				listener.onAuthFail(error);
			}

			@Override
			public void onCancel() {
				holder.remove(listenerKey);
				listener.onCancel();
			}
		});
		
		Intent i = new Intent(context, FacebookActivity.class);
		i.putExtra("appId", info.getAppId());
		i.putExtra("sso", sso);
		i.putExtra("type", info.getPermissionType().toString());
		
		switch(info.getPermissionType()) {
			case READ:
				if(info.getReadPermissions() != null) {
					i.putExtra("permissions", info.getReadPermissions());
				}
				break;
				
			case WRITE:
				if(info.getWritePermissions() != null) {
					i.putExtra("permissions", info.getWritePermissions());
				}
				break;
		}
		
		context.startActivity(i);	
	}		
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#link(android.app.Activity, com.socialize.listener.SocializeAuthListener)
	 */
	@Deprecated
	@Override
	public void link(Activity context, SocializeAuthListener listener) {
		linkForWrite(context, listener);
	}

	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#link(android.app.Activity, com.socialize.listener.SocializeAuthListener, java.lang.String[])
	 */
	@Deprecated
	@Override
	public void link(Activity context, SocializeAuthListener listener, String... permissions) {
		getSocialize().authenticate(context, AuthProviderType.FACEBOOK, listener, permissions);
	}	
	
	@Deprecated
	@Override
	public void link(Activity context, String token, boolean verifyPermissions, SocializeAuthListener listener) {
		doLink(context, token, verifyPermissions, false, listener, DEFAULT_PERMISSIONS);
	}

	@Override
	public void linkForRead(Activity context, SocializeAuthListener listener, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.READ_PERMISSIONS;
		}			
		getSocialize().authenticateForRead(context, AuthProviderType.FACEBOOK, listener, permissions);
	}

	@Override
	public void linkForWrite(Activity context, SocializeAuthListener listener, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.WRITE_PERMISSIONS;
		}			
		getSocialize().authenticateForWrite(context, AuthProviderType.FACEBOOK, listener, permissions);
	}

	@Override
	public void linkForRead(Activity context, String token, boolean verifyPermissions, SocializeAuthListener listener, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.READ_PERMISSIONS;
		}				
		doLink(context, token, verifyPermissions, true, listener, permissions);
	}

	@Override
	public void linkForWrite(Activity context, String token, boolean verifyPermissions, SocializeAuthListener listener, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.WRITE_PERMISSIONS;
		}				
		doLink(context, token, verifyPermissions, false, listener, permissions);
	}

	private void doLink(final Activity context, final String token, final boolean verifyPermissions, final boolean read, final SocializeAuthListener listener, final String...permissions) {
		SocializeConfig config = ConfigUtils.getConfig(context);
		final FacebookAuthProviderInfo fbInfo = new FacebookAuthProviderInfo();
		fbInfo.setAppId(config.getProperty(SocializeConfig.FACEBOOK_APP_ID));
		
		if(read) {
			fbInfo.setPermissionType(PermissionType.READ);
		}
		else {
			fbInfo.setPermissionType(PermissionType.WRITE);
		}
		
		final boolean sso = config.getBooleanProperty(SocializeConfig.FACEBOOK_SSO_ENABLED, true);
		
		if(verifyPermissions) {
			// Get the permissions for this token
			getCurrentPermissions(context, token, new OnPermissionResult() {
				
				@Override
				public void onError(SocializeException error) {
					if(listener != null) {
						listener.onError(error);
					}
				}
				
				@Override
				public void onSuccess(String[] current) {
					
					// Set the permissions on the session to the REAL permissions.
					if(read) {
						fbInfo.setReadPermissions(current);
					}
					else {
						fbInfo.setWritePermissions(current);
					}
					
					// Ensure the user has the required permissions
					String[] required = (read) ? READ_PERMISSIONS : WRITE_PERMISSIONS;
					
					boolean authRequired = false;
					
					for (String permission : required) {
						if(Arrays.binarySearch(current, permission) < 0) {
							// Does NOT have permission, we need to auth
							authRequired = true;
							break;
						}
					}
					
					if(authRequired) {
						// We need to merge in the default permissions...
						// Just add to a set
						Set<String> allPermissions = new HashSet<String>();
						allPermissions.addAll(Arrays.asList(permissions));
						allPermissions.addAll(Arrays.asList(current));
						allPermissions.addAll(Arrays.asList(required));
						
						// Now set the merged permissions.  This is the final set
						if(read) {
							fbInfo.setReadPermissions(allPermissions.toArray(new String[allPermissions.size()]));
						}
						else {
							fbInfo.setWritePermissions(allPermissions.toArray(new String[allPermissions.size()]));
						}						
						
						// Now try to auth
						authenticateWithActivity(context, fbInfo, sso, new AuthProviderListener() {
							
							@Override
							public void onError(SocializeException error) {
								if(listener != null) {
									listener.onError(error);
								}
							}
							
							@Override
							public void onCancel() {
								if(listener != null) {
									listener.onCancel();
								}
							}
							
							@Override
							public void onAuthSuccess(AuthProviderResponse response) {
								// Now to the actual auth!
								doSocializeAuthKnownUser(context, fbInfo, token, listener);
							}
							
							@Override
							public void onAuthFail(SocializeException error) {
								if(listener != null) {
									listener.onAuthFail(error);
								}
							}
						});
					}
					else {
						doSocializeAuthKnownUser(context, fbInfo, token, listener);
					}
				}
			});
		}
		else {
			if(read) {
				fbInfo.setReadPermissions(permissions);
			}
			else {
				fbInfo.setWritePermissions(permissions);
			}
			
			doSocializeAuthKnownUser(context, fbInfo, token, listener);
		}		
	}	

	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#isLinked(android.content.Context)
	 */
	@Deprecated
	@Override
	public boolean isLinked(Context context) {
		return getSocialize().isAuthenticated(AuthProviderType.FACEBOOK);
	}
	
	@Override
	public boolean isLinkedForRead(Context context, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.READ_PERMISSIONS;
		}			
		return getSocialize().isAuthenticatedForRead(AuthProviderType.FACEBOOK, permissions);
	}

	@Override
	public boolean isLinkedForWrite(Context context, String...permissions) {
		if(ArrayUtils.isEmpty(permissions)) {
			permissions = FacebookFacade.WRITE_PERMISSIONS;
		}			
		return getSocialize().isAuthenticatedForWrite(AuthProviderType.FACEBOOK, permissions);
	}

	/*
	 * (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#getAccessToken(android.content.Context)
	 */
	@Override
	public String getAccessToken(Context context) {
		UserProviderCredentials creds = getSocialize().getSession().getUserProviderCredentials(AuthProviderType.FACEBOOK);
		if(creds != null) {
			return creds.getAccessToken();
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#unlink(android.content.Context, com.socialize.networks.SocializeDeAuthListener)
	 */
	@Override
	public void unlink(final Context context, final SocializeDeAuthListener listener) {
		// Clear the FB session
		try {
			new AsyncTask<Void, Void, Void>() {

				@Override
				protected Void doInBackground(Void... params) {
					
					try {
						
						SocializeSession session = getSocialize().getSession();
						session.clear(AuthProviderType.FACEBOOK);
						
						UserSettings userSettings = session.getUserSettings();
						
						if(userSettings != null) {
							userSettings.setAutoPostFacebook(false);
						}
						
						userSystem.saveSession(context, session);	
						
						logout(context);
						
						if(listener != null) {
							listener.onSuccess();
						}
					}
					catch (Exception e) {
						if(listener != null) {
							listener.onError(SocializeException.wrap(e));
						}
					}
					return null;
				}
			}.execute();
		}
		catch (Exception e) {
			if(logger != null) {
				logger.error("Error while logging out of Facebook", e);
			}
			else {
				e.printStackTrace();
			}
		}		
	}	
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#postLike(android.app.Activity, com.socialize.entity.Entity, com.socialize.entity.PropagationInfo, com.socialize.networks.SocialNetworkListener)
	 */
	@Override
	public void postLike(Activity parent, Entity entity, PropagationInfo propInfo, SocialNetworkListener listener) {
		if(config.isOGLike()) {
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("object", propInfo.getEntityUrl());
			post(parent, "me/og.likes",  params, listener);			
		}
		else {
			post(parent, entity, "", propInfo, listener);	
		}		
	}	
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#postEntity(android.app.Activity, com.socialize.entity.Entity, java.lang.String, com.socialize.api.action.share.SocialNetworkShareListener)
	 */
	@Override
	public void postEntity(Activity context, Entity entity, String text, SocialNetworkShareListener listener) {
		ShareOptions options = ShareUtils.getUserShareOptions(context);
		options.setText(text);
		options.setShowAuthDialog(false);
		ShareUtils.shareViaSocialNetworks(context, entity, options, listener, SocialNetwork.FACEBOOK);			
	}
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#postComment(android.app.Activity, com.socialize.entity.Entity, java.lang.String, com.socialize.entity.PropagationInfo, com.socialize.networks.SocialNetworkListener)
	 */
	@Override
	public void postComment(Activity parent, Entity entity, String comment, PropagationInfo propInfo, SocialNetworkListener listener) {
		post(parent, entity, comment, propInfo, listener);
	}
	
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#postOG(android.app.Activity, com.socialize.entity.Entity, java.lang.String, java.lang.String, com.socialize.entity.PropagationInfo, com.socialize.networks.SocialNetworkListener)
	 */
	@Override
	public void postOG(Activity parent, Entity entity, String message, String action, PropagationInfo propInfo, SocialNetworkListener listener) {
		String entityUrl = propInfo.getEntityUrl();
		String linkName = entityUrl;
		String link = entityUrl;
		
		if(entity != null) {
			linkName = entity.getDisplayName();
		}
			
		final Map<String, Object> params = new HashMap<String, Object>();
		params.put("name", linkName);
		params.put("message", message);
		params.put("link", link);
		params.put("type", "link");
		
		DefaultPostData postData = new DefaultPostData();
		postData.setPostValues(params);
		postData.setEntity(entity);
		postData.setPropagationInfo(propInfo);

		post(parent, listener, postData);
	}	

	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#post(android.app.Activity, com.socialize.entity.Entity, java.lang.String, com.socialize.entity.PropagationInfo, com.socialize.networks.SocialNetworkListener)
	 */
	@Override
	public void post(Activity parent, Entity entity, String message, PropagationInfo propInfo, SocialNetworkListener listener) {
		postOG(parent, entity, message, null, propInfo, listener);
	}
	
	/*
	 * (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#post(android.app.Activity, com.socialize.networks.SocialNetworkListener, com.socialize.networks.PostData)
	 */
	@Override
	public void post(Activity parent, SocialNetworkListener listener, PostData postData) {
		doFacebookCall(parent, postData.getPostValues(), "me/feed", HttpMethod.POST, listener);
	}	
		
	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#post(android.app.Activity, java.lang.String, java.util.Map, com.socialize.networks.SocialNetworkPostListener)
	 */
	@Override
	public void post(Activity parent, String graphPath, Map<String, Object> postData, SocialNetworkPostListener listener) {
		doFacebookCall(parent, postData, graphPath, HttpMethod.POST, listener);
	}

	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#get(android.app.Activity, java.lang.String, java.util.Map, com.socialize.networks.SocialNetworkPostListener)
	 */
	@Override
	public void get(Activity parent, String graphPath, Map<String, Object> postData, SocialNetworkPostListener listener) {
		doFacebookCall(parent, postData, graphPath, HttpMethod.GET, listener);
	}

	/* (non-Javadoc)
	 * @see com.socialize.networks.facebook.FacebookFacade#delete(android.app.Activity, java.lang.String, java.util.Map, com.socialize.networks.SocialNetworkPostListener)
	 */
	@Override
	public void delete(Activity parent, String graphPath, Map<String, Object> postData, SocialNetworkPostListener listener) {
		doFacebookCall(parent, postData, graphPath, HttpMethod.DELETE, listener);
	}	
	
	protected void doSocializeAuthKnownUser(Context context, AuthProviderInfo fbInfo, String token, SocializeAuthListener listener) {
		DefaultUserProviderCredentials credentials = new DefaultUserProviderCredentials();
		credentials.setAuthProviderInfo(fbInfo);
		credentials.setAccessToken(token);
		getSocialize().authenticateKnownUser(
				context, 
				credentials, 
				listener);	
	}

	protected void onError(final Activity parent, final String msg, final Throwable e, final SocialNetworkPostListener listener) {
		
		if(logger != null) {
			if(e != null) {
				logger.error(msg, e);
			}
			else {
				logger.error(msg);
			}
		}
		else {
			if(e != null) {
				SocializeLogger.e(msg, e);
			}
			else {
				System.err.println(msg);
			}
		}
		
		if(listener != null) {
			parent.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					listener.onNetworkError(parent, SocialNetwork.FACEBOOK, SocializeException.wrap(e));
				}
			});
		}
	}		
	
	protected void handlePermissionError(Activity parent, final OnPermissionResult callback, final Exception e) {
		if(callback != null) {
			parent.runOnUiThread(new Runnable() {
				
				@Override
				public void run() {
					callback.onError(SocializeException.wrap(e));
				}
			});
		}
	}
	
	protected void handleFacebookError(final Activity parent, int code, String msg, Throwable e, SocialNetworkPostListener listener) {
		
		// Check for token error:
		// http://fbdevwiki.com/wiki/Error_codes
		if(code == 190) {
			// Clear the session cache
			getSocialize().clear3rdPartySession(parent, AuthProviderType.FACEBOOK);
		}
		
		onError(parent, msg, e, listener);
	}	
	
	protected void doFacebookCall(Activity parent, Map<String, Object> postData, String graphPath, HttpMethod method, SocialNetworkPostListener listener) {
		Bundle bundle = new Bundle();
		
		if(postData != null) {
			Set<Entry<String, Object>> entries = postData.entrySet();
			for (Entry<String, Object> entry : entries) {
				
				Object value = entry.getValue();
				
				if(value instanceof byte[]) {
					bundle.putByteArray(entry.getKey(), (byte[]) value);
				}
				else {
					bundle.putString(entry.getKey(), value.toString());
				}
			}	
		}

		doFacebookCall(parent, bundle, graphPath, method, listener);
	}	
	
	protected abstract void doFacebookCall(Activity parent, Bundle data, String graphPath, HttpMethod method, SocialNetworkPostListener listener);
	
	// So we can mock
	protected SocializeService getSocialize() {
		return Socialize.getSocialize();
	}
	
	// So we can mock
	protected String getFacebookAppId() {
		return config.getProperty(SocializeConfig.FACEBOOK_APP_ID);
	}	
	
	public void setUserSystem(UserSystem userSystem) {
		this.userSystem = userSystem;
	}
	public void setLogger(SocializeLogger logger) {
		this.logger = logger;
	}
	public void setConfig(SocializeConfig config) {
		this.config = config;
	}
	public void setImageUtils(ImageUtils imageUtils) {
		this.imageUtils = imageUtils;
	}
	public void setHolder(ListenerHolder holder) {
		this.holder = holder;
	}
	
}
