/*
    BEEM is a videoconference application on the Android Platform.

    Copyright (C) 2009 by Frederic-Charles Barthelery,
                          Jean-Manuel Da Silva,
                          Nikita Kozlov,
                          Philippe Lago,
                          Jean Baptiste Vergely,
                          Vincent Veronis.

    This file is part of BEEM.

    BEEM is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    BEEM is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with BEEM.  If not, see <http://www.gnu.org/licenses/>.

    Please send bug reports with examples or suggestions to
    contact@beem-project.com or http://dev.beem-project.com/

    Epitech, hereby disclaims all copyright interest in the program "Beem"
    written by Frederic-Charles Barthelery,
               Jean-Manuel Da Silva,
               Nikita Kozlov,
               Philippe Lago,
               Jean Baptiste Vergely,
               Vincent Veronis.

    Nicolas Sadirac, November 26, 2009
    President of Epitech.

    Flavien Astraud, November 26, 2009
    Head of the EIP Laboratory.

*/
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.
 */
public class RosterAdapter extends com.beem.project.beem.service.aidl.IRoster.Stub {

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

    /**
     * Constructor.
     * @param roster The roster to adapt.
     * @param context The context of the RosterAdapter.
     */
    public RosterAdapter(final Roster roster, final 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.v(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 {
	boolean add = true;
	List<Contact> coList = new ArrayList<Contact>(mAdaptee.getEntries().size());
	for (RosterEntry entry : mAdaptee.getEntries()) {
	    for (Contact c : coList) {
		if (c.getJID().equals(entry.getUser())) {
		    add = false;
		    break;
		}
	    }
	    // Because getEntries return duplicated user.
	    if (add)
		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);
    }

    /* (non-Javadoc)
     * @see com.beem.project.beem.service.aidl.IRoster#getPresence(java.lang.String)
     */
    @Override
    public PresenceAdapter getPresence(String jid) throws RemoteException {
	return new PresenceAdapter(mAdaptee.getPresence(jid));
    }

    /* (non-Javadoc)
     * @see com.beem.project.beem.service.aidl.IRoster#addContactToGroup(java.lang.String, java.lang.String)
     */
    @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();
	}
    }

    /* (non-Javadoc)
     * @see com.beem.project.beem.service.aidl.IRoster#removeContactFromGroup(java.lang.String, java.lang.String)
     */
    @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 || "".equals(p.getStatus()))
	    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.
     * @param context The context of the roster adapter.
     * @return A Map<Integer, String> which assigns a status to a message.
     */
    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() {
	}

	/**
	 * {@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 || "".equals(presence.getStatus())) {
			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();
	}
    }
}
