package com.beem.project.beem.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterGroup;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;

import android.content.Context;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.beem.project.beem.R;
import com.beem.project.beem.service.aidl.IBeemRosterListener;
import com.beem.project.beem.utils.Status;

/**
 * This class implement a Roster adapter for BEEM.
 * @author darisk
 */
public class RosterAdapter extends com.beem.project.beem.service.aidl.IRoster.Stub {

    private static final String TAG = "RosterAdapter";
    private Roster mAdaptee;
    private RemoteCallbackList<IBeemRosterListener> mRemoteRosListeners = new RemoteCallbackList<IBeemRosterListener>();
    private Map<Integer, String> mDefaultStatusMessages;
    private RosterListenerAdapter mRosterListener = new RosterListenerAdapter();

    /**
     * Constructor.
     * @param roster the roster to adapt
     */
    public RosterAdapter(final Roster roster, Context context) {
	mAdaptee = roster;
	roster.addRosterListener(mRosterListener);
	mDefaultStatusMessages = createDefaultStatusMessagesMap(context);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addRosterListener(IBeemRosterListener listen) throws RemoteException {
	if (listen != null)
	    mRemoteRosListeners.register(listen);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Contact addContact(String user, String name, String[] groups) throws RemoteException {
	RosterEntry contact = mAdaptee.getEntry(user);
	try {
	    mAdaptee.createEntry(user, name, groups);
	    contact = mAdaptee.getEntry(user);
	} catch (XMPPException e) {
	    Log.e(TAG, "Error while adding new contact", e);
	    return null;
	}
	if (groups != null) {
	    for (String groupStr : groups) {
		RosterGroup group = mAdaptee.getGroup(groupStr);
		if (group == null) {
		    group = mAdaptee.createGroup(groupStr);
		}
		try {
		    group.addEntry(contact);
		} catch (XMPPException e) {
		    e.printStackTrace();
		    return null;
		}
	    }
	}
	return getContactFromRosterEntry(contact);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteContact(Contact contact) throws RemoteException {
	try {
	    RosterEntry entry = mAdaptee.getEntry(contact.getJID());
	    mAdaptee.removeEntry(entry);
	} catch (XMPPException e) {
	    e.printStackTrace();
	}
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createGroup(String groupname) throws RemoteException {
	try {
	    mAdaptee.createGroup(groupname);
	} catch (IllegalArgumentException e) {
	    Log.e(TAG, "Error while creating group", e);
	}
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Contact getContact(String jid) throws RemoteException {
	if (mAdaptee.contains(jid))
	    return getContactFromRosterEntry(mAdaptee.getEntry(jid));
	return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Contact> getContactList() throws RemoteException {
	List<Contact> coList = new ArrayList<Contact>(mAdaptee.getEntries().size());
	for (RosterEntry entry : mAdaptee.getEntries()) {
	    coList.add(getContactFromRosterEntry(entry));
	}
	return coList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<String> getGroupsNames() throws RemoteException {
	Collection<RosterGroup> groups = mAdaptee.getGroups();
	List<String> result = new ArrayList<String>(groups.size());
	for (RosterGroup rosterGroup : groups) {
	    result.add(rosterGroup.getName());
	}
	return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeRosterListener(IBeemRosterListener listen) throws RemoteException {
	if (listen != null)
	    mRemoteRosListeners.unregister(listen);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setContactName(String jid, String name) throws RemoteException {
	mAdaptee.getEntry(jid).setName(name);
    }

    @Override
    public PresenceAdapter getPresence(String jid) throws RemoteException {
	return new PresenceAdapter(mAdaptee.getPresence(jid));
    }

    @Override
    public void addContactToGroup(String groupName, String jid) throws RemoteException {
	createGroup(groupName);
	RosterGroup group = mAdaptee.getGroup(groupName);
	try {
	    group.addEntry(mAdaptee.getEntry(jid));
	} catch (XMPPException e) {
	    e.printStackTrace();
	}
    }

    @Override
    public void removeContactFromGroup(String groupName, String jid) throws RemoteException {
	RosterGroup group = mAdaptee.getGroup(groupName);
	try {
	    group.removeEntry(mAdaptee.getEntry(jid));
	} catch (XMPPException e) {
	    e.printStackTrace();
	}
    }

    /**
     * Get a contact from a RosterEntry.
     * @param entry a roster entry containing information for the contact.
     * @return a contact for this entry.
     */
    private Contact getContactFromRosterEntry(RosterEntry entry) {
	String user = entry.getUser();
	Contact c = new Contact(user);
	Presence p = mAdaptee.getPresence(user);

	if (p.getStatus() == null || p.getStatus().equals(""))
	    p.setStatus(mDefaultStatusMessages.get(Status.getStatusFromPresence(p)));
	c.setStatus(p);
	try {
	    c.setGroups(entry.getGroups());
	} catch (NullPointerException e) {
	    Log.d(TAG, "Group list not ready");
	}
	c.setName(entry.getName());
	return c;
    }

    /**
     * Create a map which contains default status messages.
     * @return
     */
    private Map<Integer, String> createDefaultStatusMessagesMap(Context context) {
	Map<Integer, String> defaultStatusMessages = new HashMap<Integer, String>();
	defaultStatusMessages.put(Status.CONTACT_STATUS_AVAILABLE, context
	    .getString(R.string.contact_status_msg_available));
	defaultStatusMessages.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT, context
	    .getString(R.string.contact_status_msg_available_chat));
	defaultStatusMessages.put(Status.CONTACT_STATUS_AWAY, context.getString(R.string.contact_status_msg_away));
	defaultStatusMessages.put(Status.CONTACT_STATUS_BUSY, context.getString(R.string.contact_status_msg_dnd));
	defaultStatusMessages.put(Status.CONTACT_STATUS_DISCONNECT, context
	    .getString(R.string.contact_status_msg_offline));
	defaultStatusMessages.put(Status.CONTACT_STATUS_UNAVAILABLE, context.getString(R.string.contact_status_msg_xa));

	return (defaultStatusMessages);
    }

    /**
     * Listener for the roster events. It will call the remote listeners registered.
     * @author darisk
     */
    private class RosterListenerAdapter implements RosterListener {

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

	/**
	 * Event which is fired when an entry is deleted for a group.
	 * @param group the group the entry was.
	 * @param jid the jid of the entry which is deleted.
	 */
	@SuppressWarnings("unused")
	public void onEntryDeleteFromGroup(final String group, final String jid) {
	    // Log.i(TAG, "entry delete listener");
	    final int n = mRemoteRosListeners.beginBroadcast();
	    for (int i = 0; i < n; i++) {
		IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i);
		try {
		    listener.onEntryDeleteFromGroup(group, jid);
		} catch (RemoteException e) {
		    Log.w(TAG, "entry delete listener", e);
		}
	    }
	    mRemoteRosListeners.finishBroadcast();

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void entriesAdded(Collection<String> addresses) {
	    // Log.i(TAG, "Ajout de l'entry " + addresses.size() + " " +
	    // addresses.toArray()[0]);
	    final int n = mRemoteRosListeners.beginBroadcast();

	    List<String> tab = new ArrayList<String>();
	    tab.addAll(addresses);
	    for (int i = 0; i < n; i++) {
		IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i);
		try {
		    listener.onEntriesAdded(tab);
		} catch (RemoteException e) {
		    Log.w(TAG, "Error while adding roster entries", e);
		}
	    }
	    mRemoteRosListeners.finishBroadcast();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void entriesDeleted(Collection<String> addresses) {
	    // Log.i(TAG, "Suppression de l'entry");
	    final int n = mRemoteRosListeners.beginBroadcast();

	    List<String> tab = new ArrayList<String>();
	    tab.addAll(addresses);
	    for (int i = 0; i < n; i++) {
		IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i);
		try {
		    listener.onEntriesDeleted(tab);
		} catch (RemoteException e) {
		    Log.w(TAG, "Error while deleting roster entries", e);
		}
	    }
	    mRemoteRosListeners.finishBroadcast();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void entriesUpdated(Collection<String> addresses) {
	    // Log.i(TAG, "Update de l'entry " + addresses.size() + " " +
	    // addresses.toArray()[0]);
	    final int n = mRemoteRosListeners.beginBroadcast();

	    List<String> tab = new ArrayList<String>();
	    tab.addAll(addresses);
	    for (int i = 0; i < n; i++) {
		IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i);
		try {
		    listener.onEntriesUpdated(tab);
		} catch (RemoteException e) {
		    Log.w(TAG, "Error while updating roster entries", e);
		}
	    }
	    mRemoteRosListeners.finishBroadcast();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void presenceChanged(Presence presence) {
	    // Log.i(TAG, "presence Changed");
	    /* redispatch vers les IBeemRosterListener */
	    final int n = mRemoteRosListeners.beginBroadcast();

	    for (int i = 0; i < n; i++) {
		IBeemRosterListener listener = mRemoteRosListeners.getBroadcastItem(i);
		try {
		    Log.d(TAG, ">>> Presence changed.");
		    if (presence.getStatus() == null || presence.getStatus().equals("")) {
			presence.setStatus(mDefaultStatusMessages.get(Status.getStatusFromPresence(presence)));
			Log.d(TAG, ">>> Default status added.");
		    }
		    listener.onPresenceChanged(new PresenceAdapter(presence));
		} catch (RemoteException e) {
		    // The RemoteCallbackList will take care of removing the
		    // dead listeners.
		    Log.w(TAG, "Error while updating roster presence entries", e);
		}
	    }
	    mRemoteRosListeners.finishBroadcast();
	}
    }
}
