/*
    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

*/
package com.beem.project.beem.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jivesoftware.smack.util.StringUtils;

import android.app.Activity;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.graphics.drawable.BitmapDrawable;

import com.beem.project.beem.R;
import com.beem.project.beem.service.Contact;
import com.beem.project.beem.service.PresenceAdapter;
import com.beem.project.beem.service.aidl.IBeemRosterListener;
import com.beem.project.beem.service.aidl.IRoster;
import com.beem.project.beem.service.aidl.IXmppFacade;
import com.beem.project.beem.ui.dialogs.builders.Alias;
import com.beem.project.beem.ui.dialogs.builders.DeleteContact;
import com.beem.project.beem.ui.dialogs.builders.ResendSubscription;
import com.beem.project.beem.utils.BeemBroadcastReceiver;
import com.beem.project.beem.utils.Status;

/**
 * The contact list activity displays the roster of the user. TODO: Dismiss dialogs on destroy to avoid window leak
 */
public class ContactList extends Activity {

    // private static final String TAG = "CONTACTLIST_ACT";
    private static final Intent SERVICE_INTENT = new Intent();
    static {
	SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService"));
    }

    private static final int REQUEST_CODE = 1;
    private BeemContactList mAdapterContactList;
    private BeemBanner mAdapterBanner;
    private IRoster mRoster;
    private List<Contact> mListContact = new ArrayList<Contact>();
    private final List<String> mListGroup = new ArrayList<String>();
    private final Map<String, List<Contact>> mContactOnGroup = new HashMap<String, List<Contact>>();
    private String mCurGroup;
    private Contact mContact;
    private Handler mHandler;
    private IXmppFacade mXmppFacade;
    private SharedPreferences mSettings;
    private final ServiceConnection mServConn = new BeemServiceConnection();
    private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver();
    private final Map<Integer, BitmapDrawable> mIconsMap = new HashMap<Integer, BitmapDrawable>();
    private LayoutInflater mInflater;
    private ComparatorContactListByStatusAndName<Contact> mComparator = new ComparatorContactListByStatusAndName<Contact>();
    private final BeemRosterListener mBeemRosterListener = new BeemRosterListener();

    /**
     * Constructor.
     */
    public ContactList() {

    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onCreate(Bundle saveBundle) {
	super.onCreate(saveBundle);
	mSettings = PreferenceManager.getDefaultSharedPreferences(this);
	setContentView(R.layout.contactlist);
	mAdapterContactList = new BeemContactList(this);
	mAdapterBanner = new BeemBanner(this);
	mHandler = new Handler();

	this.registerReceiver(mReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED));

	mInflater = getLayoutInflater();
	prepareIconsStatus();
    }

    /**
     * Callback for menu creation.
     * @param menu the menu created
     * @return true on success, false otherwise
     */
    @Override
    public final boolean onCreateOptionsMenu(Menu menu) {
	super.onCreateOptionsMenu(menu);
	MenuInflater inflater = getMenuInflater();
	inflater.inflate(R.menu.contact_list, menu);
	return true;
    }

    /**
     * Callback for menu item selected.
     * @param item the item selected
     * @return true on success, false otherwise
     */
    @Override
    public final boolean onOptionsItemSelected(MenuItem item) {
	switch (item.getItemId()) {
	    case R.id.contact_list_menu_settings:
		startActivityForResult(new Intent(this, Settings.class), REQUEST_CODE);
		return true;
	    case R.id.contact_list_menu_add_contact:
		startActivity(new Intent(ContactList.this, AddContact.class));
		return true;
	    default:
		return false;
	}
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
	super.onCreateContextMenu(menu, v, menuInfo);
	MenuInflater inflater = getMenuInflater();
	inflater.inflate(R.menu.contactlist_context, menu);
	AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
	Contact c = mListContact.get(info.position);
	try {
	    mContact = mRoster.getContact(c.getJID());
	} catch (RemoteException e) {
	    e.printStackTrace();
	}
	menu.setHeaderTitle(mContact.getJID());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean onContextItemSelected(MenuItem item) {
	Intent in;
	if (mContact != null) {
	    switch (item.getItemId()) {
		case R.id.contact_list_context_menu_chat_item:
		    in = new Intent(this, Chat.class);
		    in.setData(mContact.toUri());
		    startActivity(in);
		    return true;
		case R.id.contact_list_context_menu_call_item:
		    try {
			mXmppFacade.call(mContact.getJID() + "/psi");
			return true;
		    } catch (RemoteException e) {
			e.printStackTrace();
		    }
		    return true;
		case R.id.contact_list_context_menu_user_info:
		    item.getSubMenu().setHeaderTitle(mContact.getJID());
		    return true;
		case R.id.contact_list_context_menu_userinfo_alias:
		    Dialog alias = new Alias(ContactList.this, mRoster, mContact).create();
		    alias.show();
		    return true;
		case R.id.contact_list_context_menu_userinfo_group:
		    in = new Intent(this, GroupList.class);
		    in.putExtra("contact", mContact);
		    startActivity(in);
		    return true;
		case R.id.contact_list_context_menu_userinfo_subscription:
		    Dialog subscription = new ResendSubscription(ContactList.this, mXmppFacade, mContact).create();
		    subscription.show();
		    return true;
		case R.id.contact_list_context_menu_userinfo_block:
		    return true;
		case R.id.contact_list_context_menu_userinfo_delete:
		    Dialog delete = new DeleteContact(ContactList.this, mRoster, mContact).create();
		    delete.show();
		    return true;
		default:
		    return super.onContextItemSelected(item);
	    }
	}
	return super.onContextItemSelected(item);
    }

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);
	if (requestCode == REQUEST_CODE) {
	    if (resultCode == RESULT_OK) {
		mListContact.clear();
		stopService(SERVICE_INTENT);
		finish();
	    }
	}
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onStart() {
	super.onStart();
	if (mXmppFacade == null)
	    bindService(SERVICE_INTENT, mServConn, BIND_AUTO_CREATE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onStop() {
	super.onStop();
	unbindService(mServConn);
	mXmppFacade = null;
    }

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onDestroy()
     */
    @Override
    protected void onDestroy() {
	super.onDestroy();
	try {
	    if (mRoster != null)
		mRoster.removeRosterListener(mBeemRosterListener);
	} catch (RemoteException e) {
	    Log.d("ContactList", "Remode exception", e);
	}
	this.unregisterReceiver(mReceiver);
	mRoster = null;
	Log.e("CONTACTLIST", "onDestroy activity");
    }

    /**
     * Comparator Contact by Name.
     */
    @SuppressWarnings("unused")
    private class ComparatorContactListByName<T> implements Comparator<T> {
	/**
	 * Constructor.
	 */
	public ComparatorContactListByName() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int compare(T c1, T c2) {
	    return ((Contact) c1).getName().compareToIgnoreCase(((Contact) c2).getName());
	}
    }

    /**
     * Comparator Contact by status and name.
     */
    private class ComparatorContactListByStatusAndName<T> implements Comparator<T> {
	/**
	 * Constructor.
	 */
	public ComparatorContactListByStatusAndName() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int compare(T c1, T c2) {
	    if (((Contact) c1).getStatus() < ((Contact) c2).getStatus()) {
		return 1;
	    } else if (((Contact) c1).getStatus() > ((Contact) c2).getStatus()) {
		return -1;
	    } else
		return ((Contact) c1).getName().compareToIgnoreCase(((Contact) c2).getName());
	}
    }

    /**
     * Contact List construction.
     */
    private void buildContactList() {
	if (mCurGroup != null) {
	    mListContact = mContactOnGroup.get(mCurGroup);
	}
	sortBeemContactList();
	ListView listView = (ListView) findViewById(R.id.contactlist);
	listView.setOnItemClickListener(new BeemContactListOnClick());
	registerForContextMenu(listView);
	listView.setAdapter(mAdapterContactList);
    }

    /**
     * buildBanner.
     */
    private void buildBanner() {
	Gallery g = (Gallery) findViewById(R.id.contactlist_banner);
	g.setOnItemClickListener(new OnItemClickGroupName());
	g.setAdapter(mAdapterBanner);
    }

    /**
     * Event simple click on item of the contact list.
     */
    private class BeemContactListOnClick implements OnItemClickListener {
	/**
	 * Constructor.
	 */
	public BeemContactListOnClick() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onItemClick(AdapterView<?> arg0, View v, int pos, long lpos) {
	    Contact c = mListContact.get(pos);
	    Intent i = new Intent(ContactList.this, Chat.class);
	    i.setData(c.toUri());
	    startActivity(i);
	}
    }

    /**
     * Event simple click on middle groupe name.
     */
    private class OnItemClickGroupName implements OnItemClickListener {

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

	@Override
	public void onItemClick(AdapterView<?> arg0, View v, int i, long l) {
	    mCurGroup = mListGroup.get(i);
	    buildContactList();
	}
    }

    /**
     * Prepare Bitmap Map.
     */
    private void prepareIconsStatus() {
	mIconsMap.put(Status.CONTACT_STATUS_AVAILABLE, new BitmapDrawable(BitmapFactory.decodeResource(getResources(),
	    R.drawable.status_available)));
	mIconsMap.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT, new BitmapDrawable(BitmapFactory.decodeResource(getResources(),
	    R.drawable.status_available)));
	mIconsMap.put(Status.CONTACT_STATUS_AWAY, new BitmapDrawable(BitmapFactory.decodeResource(getResources(), R.drawable.status_away)));
	mIconsMap.put(Status.CONTACT_STATUS_BUSY, new BitmapDrawable(BitmapFactory.decodeResource(getResources(), R.drawable.status_dnd)));
	mIconsMap.put(Status.CONTACT_STATUS_DISCONNECT, new BitmapDrawable(BitmapFactory.decodeResource(getResources(),
	    R.drawable.status_offline)));
	mIconsMap.put(Status.CONTACT_STATUS_UNAVAILABLE, new BitmapDrawable(BitmapFactory.decodeResource(getResources(),
	    R.drawable.status_requested)));
    }

    /**
     * Sort the contact list.
     */
    private void sortBeemContactList() {
	Collections.sort(mListContact, mComparator);
    }

    /**
     * Listener on service event.
     */
    private class BeemRosterListener extends IBeemRosterListener.Stub {
	/**
	 * Constructor.
	 */
	public BeemRosterListener() {
	}

	/**
	 * Refresh the contact list.
	 */
	private class RunnableChange implements Runnable {
	    /**
	     * Constructor.
	     */
	    public RunnableChange() {

	    }

	    /**
	     * {@inheritDoc}
	     */
	    @Override
	    public void run() {
		sortBeemContactList();
		mAdapterContactList.notifyDataSetChanged();
		mAdapterBanner.notifyDataSetChanged();
	    }
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onEntriesAdded(List<String> addresses) throws RemoteException {
	    for (String newName : addresses) {
		Contact c = mRoster.getContact(newName);
		mContactOnGroup.get(getString(R.string.contact_list_all_contact)).add(c);
		if (c.getGroups().size() == 0)
		    mContactOnGroup.get(getString(R.string.contact_list_no_group)).add(c);
		else {
		    for (String group : c.getGroups()) {
			if (!mListGroup.contains(group)) {
			    mListGroup.add(mListGroup.size() - 1, group);
			    List<Contact> tmplist = new ArrayList<Contact>();
			    mContactOnGroup.put(group, tmplist);
			}
			mContactOnGroup.get(group).add(c);
			if (group.equals(mCurGroup) && !mListContact.contains(c))
			    mListContact.add(c);
		    }
		}
	    }
	    mHandler.post(new RunnableChange());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onEntriesDeleted(List<String> addresses) throws RemoteException {
	    for (String cToDelete : addresses) {
		for (Contact c : mListContact) {
		    if (c.getJID().equals(cToDelete)) {
			mListContact.remove(c);
			for (String group : mListGroup) {
			    mContactOnGroup.get(group).remove(c);
			    if (mContactOnGroup.get(group).size() == 0) {
				mListGroup.remove(group);
				mListContact = mContactOnGroup.get(getString(R.string.contact_list_all_contact));
			    }
			}
			break;
		    }
		}
	    }
	    mHandler.post(new RunnableChange());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onEntriesUpdated(List<String> addresses) throws RemoteException {
	    for (String adr : addresses) {
		Contact c = mRoster.getContact(adr);
		if (c.getGroups() != null) {
		    if (mContactOnGroup.get(getString(R.string.contact_list_no_group)).contains(c)) {
			mContactOnGroup.get(getString(R.string.contact_list_no_group)).remove(c);
		    }
		}
	    }
	    mHandler.post(new RunnableChange());
	}

	@Override
	public void onEntryDeleteFromGroup(String group, String jid) throws RemoteException {
	    List<Contact> tmpContactList = mContactOnGroup.get(group);
	    Contact curContact = null;
	    for (Contact c : tmpContactList) {
		if (jid.equals(c.getJID())) {
		    curContact = c;
		    tmpContactList.remove(c);
		    c.getGroups().remove(group);
		    if (mContactOnGroup.get(group).size() == 0) {
			mContactOnGroup.remove(group);
			mListGroup.remove(group);
			mListContact = mContactOnGroup.get(getString(R.string.contact_list_all_contact));
		    }
		    break;
		}
	    }
	    if (curContact != null && curContact.getGroups().size() == 0) {
		mContactOnGroup.get(getString(R.string.contact_list_no_group)).add(curContact);
	    }
	    mHandler.post(new RunnableChange());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onPresenceChanged(PresenceAdapter presence) throws RemoteException {
	    String from = presence.getFrom();
	    boolean resfound = false;
	    for (Contact curContact : mListContact) {
		if (curContact.getJID().equals(StringUtils.parseBareAddress(from))) {
		    String pres = StringUtils.parseResource(from);
		    for (String res : curContact.getMRes()) {
			if (res.equals(pres)) {
			    resfound = true;
			    break;
			}
		    }
		    curContact.setStatus(mRoster.getPresence(StringUtils.parseBareAddress(presence.getFrom())));
		    int status = presence.getStatus();
		    if (!resfound && !Status.statusOnline(status))
			curContact.addRes(pres);
		    else if (resfound && Status.statusOnline(status))
			curContact.delRes(pres);
		    mHandler.post(new RunnableChange());
		    return;
		}
	    }
	}
    }

    /**
     * Adapter contact list.
     */
    private class BeemContactList extends BaseAdapter {

	/**
	 * Constructor.
	 * @param context context activity.
	 */
	public BeemContactList(final Context context) {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getCount() {
	    if (mSettings.getBoolean("settings_key_hidden_contact", false)) {
		int res = 0;
		for (Contact c : mListContact) {
		    if (Status.statusOnline(c.getStatus()))
			res++;
		}
		return res;
	    } else {
		return mListContact.size();
	    }
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getItem(int position) {
	    Contact c = null;
	    if (mSettings.getBoolean("settings_key_hidden_contact", false)) {
		int res = 0;
		for (Contact cur : mListContact) {
		    if (res == position) {
			c = cur;
			break;
		    }
		    if (Status.statusOnline(cur.getStatus()))
			res++;
		}
	    } else
		c = mListContact.get(position);
	    return c;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long getItemId(int position) {
	    return position;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
	    View v = convertView;
	    if (convertView == null) {
		v = mInflater.inflate(R.layout.contactlistcontact, null);
	    }
	    Contact c = null;
	    if (mSettings.getBoolean("settings_key_hidden_contact", false)) {
		int res = 0;
		for (Contact cur : mListContact) {
		    if (res == position) {
			c = cur;
			break;
		    }
		    if (Status.statusOnline(cur.getStatus()))
			res++;
		}
	    } else
		c = mListContact.get(position);
	    if (mRoster != null) {
		try {
		    c = mRoster.getContact(c.getJID());
		} catch (RemoteException e) {
		    e.printStackTrace();
		}
	    }
	    if (mSettings.getBoolean("settings_key_hidden_contact", false) && !Status.statusOnline(c.getStatus())) {
		v.setVisibility(View.GONE);
	    } else {
		bindView(v, c);
	    }
	    return v;
	}

	/**
	 * Adapte curContact to the view.
	 * @param view the row view.
	 * @param curContact the current contact.
	 */
	private void bindView(View view, Contact curContact) {

	    if (curContact != null) {
		TextView v = (TextView) view.findViewById(R.id.contactlistpseudo);
		v.setCompoundDrawablesWithIntrinsicBounds(mIconsMap.get(curContact.getStatus()), null, null, null);
		v.setText(curContact.getName());
		v = (TextView) view.findViewById(R.id.contactlistmsgperso);
		v.setText(curContact.getMsgState());
	    }
	}
    }

    /**
     * Adapter banner list.
     */
    private class BeemBanner extends BaseAdapter {
	/**
	 * Constructor.
	 * @param c context activity.
	 */
	public BeemBanner(final Context c) {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getCount() {
	    return mListGroup.size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getItem(int position) {
	    return position;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public long getItemId(int position) {
	    return position;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
	    View v = convertView;
	    if (convertView == null) {
		v = mInflater.inflate(R.layout.contactlist_group, null);
	    }
	    ((TextView) v).setText(mListGroup.get(position));
	    return v;
	}

    }

    /**
     * Create dialog resend subscription.
     */

    /**
     * The service connection used to connect to the Beem service.
     */
    private class BeemServiceConnection implements ServiceConnection {

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

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
	    mXmppFacade = IXmppFacade.Stub.asInterface(service);
	    try {
		mRoster = mXmppFacade.getRoster();
		if (mRoster != null) {
		    mRoster.addRosterListener(mBeemRosterListener);
		    List<Contact> tmpContactList = mRoster.getContactList();
		    List<String> tmpGroupList = mRoster.getGroupsNames();
		    Collections.sort(tmpGroupList);
		    if (mListGroup.size() > 0)
			mListGroup.clear();
		    mListGroup.add(getString(R.string.contact_list_all_contact));
		    mListGroup.addAll(tmpGroupList);
		    mListGroup.add(getString(R.string.contact_list_no_group));
		    mContactOnGroup.clear();
		    if (tmpGroupList.size() > 0) {
			List<Contact> tmpNoGroup = new ArrayList<Contact>();
			for (String s : tmpGroupList) {
			    List<Contact> tmpList = new ArrayList<Contact>();
			    for (Contact c : tmpContactList) {
				if (c.getGroups().size() == 0 && !tmpNoGroup.contains(c))
				    tmpNoGroup.add(c);
				else if (c.getGroups().contains(s))
				    tmpList.add(c);
			    }
			    mContactOnGroup.put(s, tmpList);
			}
			mContactOnGroup.put(getString(R.string.contact_list_no_group), tmpNoGroup);
		    } else {
			mContactOnGroup.put(getString(R.string.contact_list_no_group), tmpContactList);
		    }
		    mContactOnGroup.put(getString(R.string.contact_list_all_contact), tmpContactList);
		    mCurGroup = getString(R.string.contact_list_all_contact);
		    buildBanner();
		    buildContactList();
		}
	    } catch (RemoteException e) {
		e.printStackTrace();
	    }
	}

	@Override
	public void onServiceDisconnected(ComponentName name) {
	    try {
		mRoster.removeRosterListener(mBeemRosterListener);
	    } catch (RemoteException e) {
		e.printStackTrace();
	    }
	    mXmppFacade = null;
	    mRoster = null;
	    mListContact.clear();
	    mListGroup.clear();
	    mContactOnGroup.clear();
	}
    }
}
