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.ExpandableListActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.util.Linkify;
import android.util.Log;
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.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.ExpandableListAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.beem.project.beem.BeemService;
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.utils.Status;

public class ContactList extends ExpandableListActivity {

	private static final String TAG = "CONTACTLIST_ACT";
	private static final String DEFAULT_GROUP = "Default";
	private MyExpandableListAdapter mAdapter;
	private IRoster mRoster;
	private Map<String, List<Contact>> groupMap;
	private List<String> groupName;
	private List<Contact> mListContact;
	private Handler mHandler;
	private IXmppFacade xmppFacade = null;
	private final ServiceConnection mServConn = new BeemServiceConnection();

	/**
	 * 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:
			startActivity(new Intent(this, EditSettings.class));
			return true;
		case R.id.contact_list_menu_add_contact:
			startActivity(new Intent(ContactList.this, AddContact.class));
			return true;
		default:
			return false;
		}
	}

	@Override
	protected void onCreate(Bundle saveBundle) {
		super.onCreate(saveBundle);
		bindService(new Intent(this, BeemService.class), mServConn, BIND_AUTO_CREATE);
		mHandler = new Handler();
		groupMap = new HashMap<String, List<Contact>>();
		groupName = new ArrayList<String>();
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		unbindService(mServConn);
	}

	class ComparatorContactListByName<T> implements Comparator<T> {
		@Override
		public int compare(T c1, T c2) {
			return ((Contact) c1).getName().compareToIgnoreCase(((Contact) c2).getName());
		}
	}

	class ComparatorContactListByStatusAndName<T> implements Comparator<T> {
		@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());
		}
	}

	private void buildContactList(List<Contact> listContact) {
		mListContact = listContact;
		Collections.sort(mListContact, new ComparatorContactListByStatusAndName<Contact>());
		for (Contact contact : mListContact) {
			for (String group : contact.getGroups()) {
				if (!groupMap.containsKey(group)) {
					groupMap.put(group, new ArrayList<Contact>());
					Collections.sort(groupMap.get(group), new ComparatorContactListByStatusAndName<Contact>());
					groupName.add(group);
				}
				try {
					if (!groupMap.get(group).contains(contact))
						groupMap.get(group).add(contact);
				} catch (NullPointerException e) {
					Log.e(TAG, "Failed to find group in groupMap", e);
				}
			}
			if (contact.getGroups().isEmpty()) {
				if (!groupMap.containsKey(DEFAULT_GROUP)) {
					groupMap.put(DEFAULT_GROUP, new ArrayList<Contact>());
					groupName.add(DEFAULT_GROUP);
				}
				groupMap.get(DEFAULT_GROUP).add(contact);
			}
		}
		mAdapter = new MyExpandableListAdapter();
		setListAdapter(mAdapter);
	}

	private class BeemRosterListener extends IBeemRosterListener.Stub {

		@Override
		public void onEntriesAdded(List<String> addresses) throws RemoteException {
			for (String str : addresses) {
				Contact curContact = mRoster.getContact(str);
				for (String group : curContact.getGroups()) {
					if (!groupMap.containsKey(group)) {
						groupMap.put(group, new ArrayList<Contact>());
						groupName.add(group);
					}
					try {
						groupMap.get(group).add(curContact);
					} catch (NullPointerException e) {
						Log.e(TAG, "Failed to find group in groupMap", e);
					}
				}
			}
			mHandler.post(new RunnableChange());
		}

		@Override
		public void onEntriesDeleted(List<String> addresses) throws RemoteException {
			for (List<Contact> cList : groupMap.values()) {
				for (Contact curContact : cList) {
					for (String addr : addresses) {
						if (addr.equals(curContact.getJID())) {
							cList.remove(curContact);
							if (cList.isEmpty()) {
								groupMap.values().remove(cList);
							}
						}
					}
				}
			}
			mHandler.post(new RunnableChange());
		}

		@Override
		public void onEntriesUpdated(List<String> addresses) throws RemoteException {
			for (String str : addresses) {
				Contact curContact = mRoster.getContact(str);
				for (String group : curContact.getGroups()) {
					if (!groupMap.containsKey(group)) {
						groupMap.put(group, new ArrayList<Contact>());
						groupName.add(group);
						groupMap.get(group).add(curContact);
					} else {
						boolean found = false;
						for (Contact tempContact : groupMap.get(group)) {
							if (tempContact.getJID().equals(str)) {
								found = true;
								break;
							}
						}
						if (!found) {
							groupMap.get(group).add(curContact);
						}
					}
				}
			}
			mHandler.post(new RunnableChange());
		}

		@Override
		public void onPresenceChanged(PresenceAdapter presence) throws RemoteException {
			for (Contact curContact : mListContact) {
				if (curContact.getJID().equals(StringUtils.parseBareAddress(presence.getFrom()))) {
					curContact.setStatus(mRoster.getPresence(StringUtils.parseBareAddress(presence.getFrom())));
					mHandler.post(new RunnableChange());
					return;
				}
			}
		}

		private class RunnableChange implements Runnable {
			@Override
			public void run() {
				mAdapter.changed();
			}
		}

	}

	private class MyExpandableListAdapter implements ExpandableListAdapter {

		class MyOnClickListener implements OnClickListener {

			private final Contact mContact;

			public MyOnClickListener(Contact contact) {
				mContact = contact;
			}

			@Override
			public void onClick(View v) {
				Intent i = new Intent(ContactList.this, SendIM.class);
				i.setData(mContact.toUri());
				startActivity(i);
			}

		}

		class MyOnLongClickListener implements OnLongClickListener {

			private final Contact mContact;

			public MyOnLongClickListener(Contact contact) {
				mContact = contact;
			}

			@Override
			public boolean onLongClick(View v) {
				createDialog(mContact);
				return true;
			}
		}

		private final List<DataSetObserver> observers;

		public MyExpandableListAdapter() {
			observers = new ArrayList<DataSetObserver>();
		}
		

		@Override
		public boolean areAllItemsEnabled() {
			return true;
		}

		private void bindView(View view, Contact curContact) {

			if (curContact != null) {
				ImageView imgV = (ImageView) view.findViewById(R.id.contactliststatus);
				Drawable imageDrawable = null;
				switch (curContact.getStatus()) {
				case Status.CONTACT_STATUS_AVAILABLE:
					imageDrawable = getResources().getDrawable(R.drawable.online);
					break;
				case Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT:
					imageDrawable = getResources().getDrawable(R.drawable.chat);
					break;
				case Status.CONTACT_STATUS_AWAY:
					imageDrawable = getResources().getDrawable(R.drawable.away);
					break;
				case Status.CONTACT_STATUS_BUSY:
					imageDrawable = getResources().getDrawable(R.drawable.dnd);
					break;
				case Status.CONTACT_STATUS_DISCONNECT:
					imageDrawable = getResources().getDrawable(R.drawable.offline);
					break;
				case Status.CONTACT_STATUS_UNAVAILABLE:
					imageDrawable = getResources().getDrawable(R.drawable.requested);
					break;
				default:
					imageDrawable = getResources().getDrawable(R.drawable.error);
				break;
				}
				imgV.setImageDrawable(imageDrawable);

				TextView v = (TextView) view.findViewById(R.id.contactlistpseudo);
				if (v != null) {
					v.setText(curContact.getName());
				}

				v = (TextView) view.findViewById(R.id.contactlistmsgperso);
				if (v != null) {
					v.setText(curContact.getMsgState());
					Linkify.addLinks(v, Linkify.WEB_URLS);
				}

				// TODO: Rajouter l'avatar du contact getAvatar() dans la classe
				imgV = (ImageView) view.findViewById(R.id.contactlistavatar);
				if (imgV != null) {
					imageDrawable = getResources().getDrawable(R.drawable.avatar);
					imgV.setImageDrawable(imageDrawable);
				}
			}
		}

		public void changed() {
			for (String name : groupName) {
				Collections.sort(groupMap.get(name), new ComparatorContactListByStatusAndName<Contact>());
			}
			for (DataSetObserver obs : observers) {
				obs.onChanged();
			}			
		}

		void createDialog(Contact contact) {
			ContactDialog dialogContact = new ContactDialog(ContactList.this, contact);
			dialogContact.setOwnerActivity(ContactList.this);
			dialogContact.show();
		}

		@Override
		public Object getChild(int groupPosition, int childPosition) {
			try {
				return groupMap.get(groupName.get(groupPosition)).get(childPosition);
			} catch (NullPointerException e) {
				Log.e(TAG, "Child not found", e);
				return null;
			}
		}

		@Override
		public long getChildId(int groupPosition, int childPosition) {
			try {
				groupMap.get(groupName.get(groupPosition)).get(childPosition);
			} catch (NullPointerException e) {
				Log.e(TAG, "Child not found", e);
				return 0;
			}
			return childPosition;
		}

		@Override
		public int getChildrenCount(int groupPosition) {
			try {
				return groupMap.get(groupName.get(groupPosition)).size();
			} catch (NullPointerException e) {
				Log.e(TAG, "Child not found", e);
				return 0;
			}
		}

		@Override
		public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView,
				ViewGroup parent) {
			View v;
			if (convertView == null) {
				v = LayoutInflater.from(ContactList.this).inflate(R.layout.contactlistcontact, null);
			} else {
				v = convertView;
			}
			Contact contact = groupMap.get(groupName.get(groupPosition)).get(childPosition);
			bindView(v, contact);

			v.setOnLongClickListener(new MyOnLongClickListener(contact));
			v.setOnClickListener(new MyOnClickListener(contact));
			return v;
		}

		@Override
		public long getCombinedChildId(long groupId, long childId) {
			return 1000 * groupId + childId;
		}

		@Override
		public long getCombinedGroupId(long groupId) {
			return 1000 * groupId;
		}

		@Override
		public Object getGroup(int groupPosition) {
			try {
				return groupMap.get(groupName.get(groupPosition));
			} catch (NullPointerException e) {
				Log.e(TAG, "Group not found", e);
				return null;
			}
		}

		@Override
		public int getGroupCount() {
			return groupMap.size();
		}

		@Override
		public long getGroupId(int groupPosition) {
			return groupPosition;
		}

		@Override
		public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
			if (convertView == null) {
				convertView = LayoutInflater.from(ContactList.this).inflate(R.layout.contactlistgroup, null);
			}
			TextView groupTextView = (TextView) convertView.findViewById(R.id.textgroup);
			groupTextView.setText(groupName.get(groupPosition));
			return convertView;
		}

		@Override
		public boolean hasStableIds() {
			return false;
		}

		@Override
		public boolean isChildSelectable(int groupPosition, int childPosition) {
			return true;
		}

		@Override
		public boolean isEmpty() {
			return groupMap.isEmpty();
		}

		@Override
		public void onGroupCollapsed(int groupPosition) {
		}

		@Override
		public void onGroupExpanded(int groupPosition) {
		}

		@Override
		public void registerDataSetObserver(DataSetObserver observer) {
			observers.add(observer);
		}

		@Override
		public void unregisterDataSetObserver(DataSetObserver observer) {
			observers.remove(observer);
		}
	}

	private class BeemServiceConnection implements ServiceConnection {

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			xmppFacade = IXmppFacade.Stub.asInterface(service);
			try {
				mRoster = xmppFacade.getRoster();
				if (mRoster != null) {
					mRoster.addRosterListener(new BeemRosterListener());
					buildContactList(mRoster.getContactList());
				}
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			xmppFacade = null;
			mRoster = null;
		}
	}
}
