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

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.util.Log;

import com.socialize.android.ioc.IBeanFactory;
import com.socialize.android.ioc.Logger;
import com.socialize.api.DeviceRegistrationSystem;
import com.socialize.api.SocializeSession;
import com.socialize.api.action.user.UserSystem;
import com.socialize.config.SocializeConfig;
import com.socialize.entity.DeviceRegistration;
import com.socialize.entity.User;
import com.socialize.error.SocializeException;
import com.socialize.log.SocializeLogger;
import com.socialize.util.StringUtils;

import java.util.List;

/**
 * @author Jason Polites
 */
public class SocializeNotificationRegistrationSystem implements NotificationRegistrationSystem {
	
	public static final String EXTRA_SENDER = "sender";
	public static final String EXTRA_APPLICATION_PENDING_INTENT = "app";
	public static final String REQUEST_UNREGISTRATION_INTENT = "com.google.android.c2dm.intent.UNREGISTER";
	public static final String REQUEST_REGISTRATION_INTENT = "com.google.android.c2dm.intent.REGISTER";
	
	private SocializeConfig config;
	private SocializeLogger logger;
	private DeviceRegistrationSystem deviceRegistrationSystem;
	private UserSystem userSystem;
	private IBeanFactory<DeviceRegistration> deviceRegistrationFactory;
	private NotificationRegistrationState notificationRegistrationState;
	
	@Override
	public boolean isRegistrationPending() {
		return notificationRegistrationState.isC2dmPending();
	}

	@Override
	public boolean isRegisteredC2DM(Context context) {
		return notificationRegistrationState.isRegisteredC2DM(context);
	}
	
	@Override
	public boolean isSocializeRegistrationPending() {
		return notificationRegistrationState.isSocializeRegistrationPending();
	}

	@Override
	public boolean isRegisteredSocialize(Context context, User user) {
		return notificationRegistrationState.isRegisteredSocialize(context, user);
	}
	
	@Override
	public void registerC2DMFailed(Context context, String cause) {
		notificationRegistrationState.setC2dmPendingRequestTime(0);
	}

	@Override
	public void registerC2DMAsync(final Context context) {
		new AsyncTask<Void, Void, Void>() {
			@Override
			protected Void doInBackground(Void... params) {
				try {
					registerC2DM(context);
				}
				catch (Exception e) {
					if(logger != null) {
						logger.error("Error during smart alert registration", e);
					}
					else {
						Log.e(Logger.LOG_KEY, "Error during smart alert registration", e);
					}
				}

				return null;
			}
		}.execute();
	}

	@Override
	public synchronized void registerC2DM(final Context context) {
		if(!isRegisteredC2DM(context) && !notificationRegistrationState.isC2dmPending() && config.getBooleanProperty(SocializeConfig.GCM_REGISTRATION_ENABLED, true)) {
			if(logger != null && logger.isDebugEnabled()) {
				logger.debug("Registering with GCM");
			}
			
			notificationRegistrationState.setC2dmPendingRequestTime(System.currentTimeMillis());
			
			@SuppressWarnings("deprecation")
			String senderId = config.getProperty(SocializeConfig.SOCIALIZE_GCM_SENDER_ID, config.getProperty(SocializeConfig.SOCIALIZE_C2DM_SENDER_ID));
			String customSender = config.getProperty(SocializeConfig.SOCIALIZE_CUSTOM_GCM_SENDER_ID);
			
			// Only supported in GCM
			if(!StringUtils.isEmpty(senderId) && !StringUtils.isEmpty(customSender) && !senderId.equals(customSender)) {
				senderId = senderId + "," + customSender;
			}
			
			Intent implicitIntent = newIntent(REQUEST_REGISTRATION_INTENT);
			PackageManager pm = context.getPackageManager();
			List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);

			if (resolveInfos != null
					&& resolveInfos.size() > 1) {

				ResolveInfo serviceInfo = resolveInfos.get(0);
				String packageName = serviceInfo.serviceInfo.packageName;
				String className = serviceInfo.serviceInfo.name;
				ComponentName component = new ComponentName(packageName, className);
				
				Intent registrationIntent = newIntent(implicitIntent);
				registrationIntent.setComponent(component);
				registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT, newPendingIntent(context));
				registrationIntent.putExtra(EXTRA_SENDER, senderId);
				context.startService(registrationIntent);

			}

		}	
		else {
			if(logger != null && logger.isDebugEnabled()) {
				logger.debug("GCM registration already in progress, complete or disabled");
			}
		}
	}
	
	@Override
	public void registerSocialize(final Context context, final String registrationId) {
		
		notificationRegistrationState.setPendingSocializeRequestTime(System.currentTimeMillis());
		notificationRegistrationState.save(context);
		
		if(!StringUtils.isEmpty(registrationId)) {
			try {
				// This should be synchronous.  We don't want to launch an async task off the main UI thread.
				SocializeSession session = userSystem.authenticateSynchronous(context, config.getProperty(SocializeConfig.SOCIALIZE_CONSUMER_KEY), config.getProperty(SocializeConfig.SOCIALIZE_CONSUMER_SECRET));
				doRegistrationSocialize(context, session, registrationId);			
			}
			catch (SocializeException e) {
				logError(e);
			}	
		}
	}
	
	protected void doRegistrationSocialize(final Context context, final SocializeSession session, final String registrationId) {
		
		// Record the registration with Socialize
		DeviceRegistration registration = deviceRegistrationFactory.getBean();
		registration.setRegistrationId(registrationId);
		
		
		try {
			deviceRegistrationSystem.registerDeviceSynchronous(session, registration);
			
			notificationRegistrationState.setC2DMRegistrationId(registrationId);
			notificationRegistrationState.setRegisteredSocialize(session.getUser());
			notificationRegistrationState.save(context);
			
			if(logger != null && logger.isInfoEnabled()) {
				logger.info("Registration with Socialize for GCM successful.");
			}			
		}
		catch (SocializeException e) {
			if(logger != null) {
				logger.error("Error registering device with Socialize.  Will retry on next start", e);
			}
		}
	}
	
	protected void logError(Exception e) {
		if(logger != null) {
			logger.error("Error during device registration", e);
		}
		else {
			SocializeLogger.e(e.getMessage(), e);
		}
	}
	
	// So we can mock
	protected Intent newIntent(String action) {
		return new Intent(action);
	}
	
	protected Intent newIntent(Intent intent) {
		return new Intent(intent);
	}
	
	// So we can mock
	protected PendingIntent newPendingIntent(Context context) {
		return PendingIntent.getBroadcast(context, 0, new Intent(), 0);
	}

	public void setConfig(SocializeConfig config) {
		this.config = config;
	}

	public void setLogger(SocializeLogger logger) {
		this.logger = logger;
	}

	public void setDeviceRegistrationSystem(DeviceRegistrationSystem deviceRegistrationSystem) {
		this.deviceRegistrationSystem = deviceRegistrationSystem;
	}

	public void setNotificationRegistrationState(NotificationRegistrationState notificationRegistrationState) {
		this.notificationRegistrationState = notificationRegistrationState;
	}

	public void setDeviceRegistrationFactory(IBeanFactory<DeviceRegistration> deviceRegistrationFactory) {
		this.deviceRegistrationFactory = deviceRegistrationFactory;
	}
	
	public void setUserSystem(UserSystem userSystem) {
		this.userSystem = userSystem;
	}
}
