package com.beem.project.beem.service;

import java.util.HashMap;
import java.util.Map;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ChatState;
import org.jivesoftware.smackx.ChatStateListener;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.beem.project.beem.BeemService;
import com.beem.project.beem.R;
import com.beem.project.beem.service.aidl.IChat;
import com.beem.project.beem.service.aidl.IChatManager;
import com.beem.project.beem.service.aidl.IChatManagerListener;
import com.beem.project.beem.service.aidl.IMessageListener;

/**
 * An adapter for smack's ChatManager. This class provides functionnality to
 * handle chats.
 * 
 * @author darisk
 */
public class BeemChatManager extends IChatManager.Stub {

	/**
	 * A listener for all the chat creation event that happens on the
	 * connection.
	 * 
	 * @author darisk
	 */
	private class ChatListener implements ChatStateListener, ChatManagerListener, MessageListener {

		/**
		 * Constructor.
		 */
		public ChatListener() {
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void chatCreated(Chat chat, boolean locally) {
			IChat newchat = getChat(chat);
			chat.addMessageListener(mChatListener);
			final int n = mRemoteChatCreationListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IChatManagerListener listener = mRemoteChatCreationListeners.getBroadcastItem(i);
				try {
					listener.chatCreated(newchat, locally);
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, " Error while triggering remote connection listeners in chat creation", e);
				}
			}
			mRemoteChatCreationListeners.finishBroadcast();
		}
		
		/**
		 * Create the PendingIntent to launch our activity if the user select this chat notification.
		 * @param chat
		 * @return
		 */
		private PendingIntent makeChatIntent(IChat chat) {
			Intent chatIntent = new Intent(mService, com.beem.project.beem.ui.Chat.class);
			chatIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
			try {
				chatIntent.setData(chat.getParticipant().toUri());
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
			PendingIntent contentIntent = PendingIntent.getActivity(mService, 0, chatIntent, PendingIntent.FLAG_UPDATE_CURRENT);
			return (contentIntent);
		}

		/**
		 * Set a notification of a new chat.
		 * 
		 * @param chat
		 *            The chat to access by the notification
		 */
		private void notifyNewChat(IChat chat) {
			try {
				CharSequence tickerText = chat.getParticipant().getName();
				Notification notification = new Notification(android.R.drawable.stat_notify_chat, tickerText, System
						.currentTimeMillis());
				notification.defaults = Notification.DEFAULT_ALL;
				notification.flags = Notification.FLAG_AUTO_CANCEL;
				notification.setLatestEventInfo(mService, tickerText, mService.getString(R.string.BeemChatManagerNewMessage), makeChatIntent(chat));
				mService.sendNotification(chat.hashCode(), notification);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void processMessage(Chat chat, Message message) {
			ChatAdapter newchat = getChat(chat);
			try {
				if (message.getBody() != null)
					newchat.addMessage(new com.beem.project.beem.service.Message(message));
				final int n = mRemoteMessageListeners.beginBroadcast();
				for (int i = 0; i < n; i++) {
					IMessageListener listener = mRemoteMessageListeners.getBroadcastItem(i);
					listener.processMessage(newchat, new com.beem.project.beem.service.Message(message));
				}
				mRemoteMessageListeners.finishBroadcast();
				if (!newchat.isOpen() && message.getBody() != null) {
					notifyNewChat(newchat);
				}
			} catch (RemoteException e) {
				Log.w(TAG, e.getMessage());
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void stateChanged(Chat chat, ChatState state) {
			IChat newchat = getChat(chat);
			try {
				newchat.setState(state.name());
			} catch (RemoteException e) {
				e.printStackTrace();
			}
			final int n = mRemoteMessageListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IMessageListener listener = mRemoteMessageListeners.getBroadcastItem(i);
				try {
					listener.stateChanged(newchat);
				} catch (RemoteException e) {
					Log.w(TAG, e.getMessage());
				}
			}
			mRemoteMessageListeners.finishBroadcast();
		}
	}

	/**
	 * Tag to use with log methods.
	 */
	public static final String TAG = "BeemChatManager";
	private final ChatManager mAdaptee;
	private final Map<String, ChatAdapter> mChats = new HashMap<String, ChatAdapter>();
	private final ChatListener mChatListener = new ChatListener();
	private final RemoteCallbackList<IChatManagerListener> mRemoteChatCreationListeners = new RemoteCallbackList<IChatManagerListener>();
	private final RemoteCallbackList<IMessageListener> mRemoteMessageListeners = new RemoteCallbackList<IMessageListener>();

	private final BeemService mService;

	/**
	 * Constructor.
	 * 
	 * @param chatManager
	 *            the smack ChatManager to adapt
	 * @param service
	 *            the service which runs the chat manager
	 */
	public BeemChatManager(final ChatManager chatManager, final BeemService service) {
		mService = service;
		mAdaptee = chatManager;
		mAdaptee.addChatListener(mChatListener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addChatCreationListener(IChatManagerListener listener) throws RemoteException {
		mRemoteChatCreationListeners.register(listener);
	}

	/**
	 * Create a chat session.
	 * 
	 * @param contact
	 *            the contact you want to chat with
	 * @param listener
	 *            listener to use for chat events on this chat session
	 * @return the chat session
	 */
	public IChat createChat(Contact contact, IMessageListener listener) {
		String jid = contact.getJID();
		return createChat(jid, listener);
	}

	/**
	 * Create a chat session.
	 * 
	 * @param jid
	 *            the jid of the contact you want to chat with
	 * @param listener
	 *            listener to use for chat events on this chat session
	 * @return the chat session
	 */
	public IChat createChat(String jid, IMessageListener listener) {
		mRemoteMessageListeners.register(listener);
		String key = StringUtils.parseBareAddress(jid);
		if (mChats.containsKey(key)) {
			return mChats.get(key);
		}
		mAdaptee.createChat(key, mChatListener);
		return mChats.get(key);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void destroyChat(IChat chat) throws RemoteException {
		Log.d(TAG, "destroyChat - jid = " + chat.getParticipant().getJID());
		IChat c = mChats.remove(chat.getParticipant().getJID());
		if (c == null)
			Log.w(TAG, "destroyChat - chat = null, jid = " + chat.getParticipant().getJID());
	}

	@Override
	public void deleteChatNotification(IChat chat) {
		mService.deleteNotification(chat.hashCode());
	}

	/**
	 * Get an existing ChatAdapter or create it if necessary.
	 * 
	 * @param chat
	 *            The real instance of smack chat
	 * @return a chat adapter register in the manager
	 */
	private ChatAdapter getChat(Chat chat) {
		String key = StringUtils.parseBareAddress(chat.getParticipant());
		if (mChats.containsKey(key)) {
			return mChats.get(key);
		}
		ChatAdapter res = new ChatAdapter(chat);
		mChats.put(key, res);
		return res;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeChatCreationListener(IChatManagerListener listener) throws RemoteException {
		mRemoteChatCreationListeners.unregister(listener);
	}

}
