/**
 * 
 */
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;
import com.beem.project.beem.ui.SendIM;

/**
 * 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", e);
		}
	    }
	    mRemoteChatCreationListeners.finishBroadcast();
	}

	/**
	 * Set a notification of a new chat in android.
	 * 
	 * @param chat
	 *            The chat to access by the notification
	 */
	private void notifyNewChat(IChat chat) {
	    try {
		String text = chat.getParticipant().getJID();
		Notification notif = new Notification(com.beem.project.beem.R.drawable.notify_message, text, System
		        .currentTimeMillis());
		notif.defaults = Notification.DEFAULT_ALL;
		notif.flags = Notification.FLAG_AUTO_CANCEL;
		Intent intent = new Intent(mService, SendIM.class);
		intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
		intent.setData(chat.getParticipant().toUri());
		notif.setLatestEventInfo(mService, text, mService.getString(R.string.BeemChatManagerNewMessage),
		        PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
		int id = chat.hashCode();
		mService.sendNotification(id, notif);
	    } catch (RemoteException e) {
		Log.e(TAG, "notification error", e);
	    }
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void processMessage(Chat chat, Message message) {
	    ChatAdapter newchat = getChat(chat);
	    try {
		Log.d(TAG, message.getBody());
		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) {
		// The RemoteCallbackList will take care of removing the
		// dead listeners.
		Log.w(TAG, "Error while triggering remote connection listeners", e);
	    }
	}

	/**
	 * {@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, "Error while changing chat state", e);
		}
	    }
	    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);
	}
	// create the chat. the adaptee will be add automatically in the map
	mAdaptee.createChat(key, mChatListener);
	return mChats.get(key);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void destroyChat(IChat chat) throws RemoteException {
	// TODO gerer les resources egalement
	Log.d(TAG, "destroy chat jid " + chat.getParticipant().getJID());
	IChat c = mChats.remove(chat.getParticipant().getJID());
	if (c == null)
	    Log.w(TAG, "CA devrait pas 1!!" + chat.getParticipant().getJID());
    }

    /**
     * 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);
    }

}
