package com.beem.project.beem.ui;

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

import org.jivesoftware.smack.util.StringUtils;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Typeface;
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.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnKeyListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
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.Message;
import com.beem.project.beem.service.PresenceAdapter;
import com.beem.project.beem.service.aidl.IBeemRosterListener;
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.service.aidl.IRoster;
import com.beem.project.beem.service.aidl.IXmppFacade;
import com.beem.project.beem.utils.BeemBroadcastReceiver;
import com.beem.project.beem.utils.Status;

public class Chat extends Activity implements OnKeyListener {

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

	private IRoster mRoster;
	private Contact mContact;

	private TextView mContactNameTextView;
	private TextView mContactStatusMsgTextView;
	private ImageView mContactStatusIcon;
	private ListView mMessagesListView;
	private EditText mInputField;
	private Map<Integer, Bitmap> mStatusIconsMap = new HashMap<Integer, Bitmap>();

	private List<MessageText> mListMessages = new ArrayList<MessageText>();

	private IChat mChat;
	private IChatManager mChatManager;
	private IChatManagerListener mChatManagerListener;
	private IMessageListener mMessageListener;
	private MessagesListAdapter mMessagesListAdapter;

	private final ServiceConnection mConn = new BeemServiceConnection();
	private BeemBroadcastReceiver mBroadcastReceiver;

	/**
	 * Constructor.
	 */
	public Chat() {
		super();
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onCreate(Bundle savedBundle) {
		super.onCreate(savedBundle);

		Log.v(TAG, "BEGIN onCreate.");
		setContentView(R.layout.chat);
		mMessagesListAdapter = new MessagesListAdapter(this);

		mHandler = new Handler();

		// Listeners
		mMessageListener = new OnMessageListener();
		mChatManagerListener = new OnChatManagerListener();

		mBroadcastReceiver = new BeemBroadcastReceiver(mConn);
		this.registerReceiver(mBroadcastReceiver, new IntentFilter(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED));

		// UI
		mContactNameTextView = (TextView) findViewById(R.id.chat_contact_name);
		mContactStatusMsgTextView = (TextView) findViewById(R.id.chat_contact_status_msg);
		mContactStatusIcon = (ImageView) findViewById(R.id.chat_contact_status_icon);
		mMessagesListView = (ListView) findViewById(R.id.chat_messages);
		mMessagesListView.setAdapter(mMessagesListAdapter);
		mInputField = (EditText) findViewById(R.id.chat_input);
		mInputField.setOnKeyListener(this);

		prepareIconsStatus();

		Log.v(TAG, "END onCreate.");
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onDestroy() {
		super.onDestroy();

		Log.v(TAG, "BEGIN onDestroy.");
		this.unregisterReceiver(mBroadcastReceiver);
		if (mChatManager != null) {
			try {
				mChatManager.removeChatCreationListener(mChatManagerListener);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
		}
		Log.v(TAG, "END onDestroy.");
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onStart() {
		super.onStart();

		Log.v(TAG, "BEGIN onStart.");
		bindService(new Intent(this, BeemService.class), mConn, BIND_AUTO_CREATE);
		Log.v(TAG, "END onStart.");
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onStop() {
		super.onStop();

		Log.v(TAG, "BEGIN onStop.");
		if (mChat != null) {
			try {
				mChat.setOpen(false);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
		}
		if (mBroadcastReceiver.isBinded()) {
			unbindService(mConn);
		}
		Log.v(TAG, "END onStop.");
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onResume() {
		super.onResume();
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onPause() {
		super.onPause();
	}

	/**
	 * {@inheritDoc}.
	 */
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);

		Log.v(TAG, "BEGIN onNewIntent.");
		try {
			changeCurrentChat((mContact = new Contact(intent.getData())));
		} catch (RemoteException e) {
			Log.e(TAG, e.getMessage());
		}
		Log.v(TAG, "END onNewIntent.");
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onSaveInstanceState(Bundle savedInstanceState) {
		// TODO
		super.onSaveInstanceState(savedInstanceState);
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
		// TODO
	}

	/**
	 * Change the displayed chat.
	 * 
	 * @param contact
	 * @throws RemoteException
	 */
	private void changeCurrentChat(Contact contact) throws RemoteException {
		Log.v(TAG, "BEGIN changeCurrentChat.");
		if (mChat != null)
			mChat.setOpen(false);
		mChat = mChatManager.createChat(contact, mMessageListener);
		mChat.setOpen(true);

		mChatManager.deleteChatNotification(mChat);

		mContact = mRoster.getContact(contact.getJID());
		updateContactInformations();
		updateContactStatusIcon();

		playRegisteredTranscript();
		Log.v(TAG, "END changeCurrentChat.");
	}

	/**
	 * Get all messages from the current chat and refresh the activity with
	 * them.
	 * 
	 * @throws RemoteException
	 */
	private void playRegisteredTranscript() throws RemoteException {
		String fromBareJid = null;
		String fromName = null;
		List<Message> chatMessages = mChat.getMessages();

		Log.v(TAG, "BEGIN playRegisteredTranscript.");
		mListMessages.clear();
		if (chatMessages.size() > 0) {
			MessageText lastMessage = null;
			for (Message m : chatMessages) {
				fromBareJid = StringUtils.parseBareAddress(m.getFrom());
				fromName = mContact.getName();

				if (fromBareJid == null) {
					fromBareJid = getString(R.string.chat_self);
					fromName = getString(R.string.chat_self);
				}

				if (lastMessage == null) {
					lastMessage = new MessageText(fromBareJid, fromName, m.getBody());
					continue;
				}

				if (!lastMessage.getBareJid().equals(fromBareJid)) {
					mListMessages.add(lastMessage);
					lastMessage = new MessageText(fromBareJid, fromName, m.getBody());
				} else
					lastMessage.setMessage(lastMessage.getMessage().concat("\n" + m.getBody()));
			}
			mListMessages.add(lastMessage);
		}
		mMessagesListAdapter.notifyDataSetChanged();
		Log.v(TAG, "END playRegisteredTranscript.");
	}

	/**
	 * 
	 * @author Jamu
	 * 
	 */
	private final class BeemServiceConnection implements ServiceConnection {

		private IXmppFacade mXmppFacade;
		private final BeemRosterListener mBeemRosterListener = new BeemRosterListener();

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			Log.v(TAG, "BEGIN onServiceConnected.");
			mXmppFacade = IXmppFacade.Stub.asInterface(service);
			mBroadcastReceiver.setBinded(true);
			try {
				mChatManager = mXmppFacade.getChatManager();
				mRoster = mXmppFacade.getRoster();
				mRoster.addRosterListener(mBeemRosterListener);
				changeCurrentChat((mContact = new Contact(getIntent().getData())));
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
			Log.v(TAG, "END onServiceConnected.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onServiceDisconnected(ComponentName name) {
			Log.v(TAG, "BEGIN onServiceDisconnected.");
			mXmppFacade = null;
			mBroadcastReceiver.setBinded(false);
			try {
				mRoster.removeRosterListener(mBeemRosterListener);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
			Log.v(TAG, "END onServiceDisconnected.");
		}
	}

	/**
	 * 
	 * @author Jamu
	 * 
	 */
	private class BeemRosterListener extends IBeemRosterListener.Stub {

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesAdded(List<String> addresses) throws RemoteException {
			Log.v(TAG, "BEGIN onEntriesAdded.");
			Log.v(TAG, "END onEntriesAdded.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesDeleted(List<String> addresses) throws RemoteException {
			Log.v(TAG, "BEGIN onEntriesDeleted.");
			Log.v(TAG, "END onEntriesDeleted.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesUpdated(List<String> addresses) throws RemoteException {
			Log.v(TAG, "BEGIN onEntriesUpdated.");
			Log.v(TAG, "END onEntriesUpdated.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntryDeleteFromGroup(String group, String jid) throws RemoteException {
			Log.v(TAG, "BEGIN onEntryDeleteFromGroup.");
			Log.v(TAG, "END onEntryDeleteFromGroup.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onPresenceChanged(final PresenceAdapter presence) throws RemoteException {
			Log.v(TAG, "BEGIN onPresenceChanged.");
			if (mContact.getJID().equals(StringUtils.parseBareAddress(presence.getFrom()))) {
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						mContact.setStatus(presence.getStatus());
						mContact.setMsgState(presence.getStatusText());

						updateContactInformations();
						updateContactStatusIcon();
					}
				});
			}
			Log.v(TAG, "END onPresenceChanged.");
		}
	}

	/**
	 * 
	 * @author Jamu
	 * 
	 */
	private class OnMessageListener extends IMessageListener.Stub {

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void processMessage(IChat chat, final Message msg) throws RemoteException {
			Log.v(TAG, "BEGIN processMessage.");

			final String fromBareJid = StringUtils.parseBareAddress(msg.getFrom());

			if (mContact.getJID().equals(fromBareJid)) {
				mHandler.post(new Runnable() {

					/**
					 * {@inheritDoc}.
					 */
					@Override
					public void run() {
						if (msg.getBody() != null && msg.getType() != Message.MSG_TYPE_ERROR) {
							MessageText lastMessage = mListMessages.size() != 0 ? mListMessages.get(mListMessages
									.size() - 1) : null;

							if (lastMessage != null && lastMessage.getBareJid().equals(fromBareJid)) {
								lastMessage.setMessage(lastMessage.getMessage().concat("\n" + msg.getBody()));
								mListMessages.set(mListMessages.size() - 1, lastMessage);
							} else
								mListMessages.add(new MessageText(fromBareJid, mContact.getName(), msg.getBody()));
							mMessagesListAdapter.notifyDataSetChanged();
						}
					}
				});
			}
			Log.v(TAG, "END processMessage.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void stateChanged(IChat chat) throws RemoteException {
			Log.v(TAG, "BEGIN stateChanged.");
			mHandler.post(new Runnable() {

				/**
				 * {@inheritDoc}.
				 */
				@Override
				public void run() {
				}
			});
			Log.v(TAG, "END stateChanged.");
		}
	}

	/**
	 * 
	 * @author Jamu
	 * 
	 */
	private class OnChatManagerListener extends IChatManagerListener.Stub {
		/**
		 * Constructor.
		 */
		public OnChatManagerListener() {
			Log.v(TAG, "OnChatManagerListener constructor.");
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void chatCreated(IChat chat, boolean locally) throws RemoteException {
			Log.i(TAG, "Chat has been created.");
		}
	}

	/**
	 * Update the contact informations.
	 * 
	 */
	private void updateContactInformations() {
		Log.v(TAG, "BEGIN updateContactInformations.");
		// Check for a contact name update
		if (!(mContactNameTextView.getText().toString().equals(mContact.getName())))
			mContactNameTextView.setText(mContact.getName());

		// Check for a contact status message update
		if (!(mContactStatusMsgTextView.getText().toString().equals(mContact.getMsgState()))) {
			Log.d(TAG, "Setting status message - " + mContact.getMsgState());
			mContactStatusMsgTextView.setText(mContact.getMsgState());
			Linkify.addLinks(mContactStatusMsgTextView, Linkify.WEB_URLS);
		}
		Log.v(TAG, "END updateContactInformations.");
	}

	/**
	 * Update the contact status icon.
	 */
	private void updateContactStatusIcon() {
		Log.v(TAG, "BEGIN updateContactStatusIcon.");
		mContactStatusIcon.setImageBitmap(mStatusIconsMap.get(mContact.getStatus()));
		Log.v(TAG, "END updateContactStatusIcon.");
	}

	/**
	 * Prepare the status icons map.
	 */
	private void prepareIconsStatus() {
		mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_available));
		mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_available));
		mStatusIconsMap.put(Status.CONTACT_STATUS_AWAY, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_away));
		mStatusIconsMap.put(Status.CONTACT_STATUS_BUSY, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_dnd));
		mStatusIconsMap.put(Status.CONTACT_STATUS_DISCONNECT, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_offline));
		mStatusIconsMap.put(Status.CONTACT_STATUS_UNAVAILABLE, BitmapFactory.decodeResource(getResources(),
				R.drawable.status_requested));
	}

	private class MessagesListAdapter extends BaseAdapter {

		private Context mContext;

		public MessagesListAdapter(Context context) {
			mContext = context;
		}

		public int getCount() {
			return mListMessages.size();
		}

		public Object getItem(int position) {
			return position;
		}

		public long getItemId(int position) {
			return position;
		}

		public View getView(int position, View convertView, ViewGroup parent) {
			MessageView sv;
			if (convertView == null) {
				sv = new MessageView(mContext, mListMessages.get(position).getName(), mListMessages.get(position)
						.getMessage());
			} else {
				sv = (MessageView) convertView;
				sv.setName(mListMessages.get(position).getName());
				sv.setMessage(mListMessages.get(position).getMessage());
			}

			sv.setPadding(2, 2, 2, 4);

			sv.mName.setTextSize(16);
			sv.mName.setTextColor(Color.WHITE);
			sv.mName.setTypeface(Typeface.DEFAULT_BOLD);

			sv.mMessage.setLinkTextColor(Color.WHITE);
			sv.mMessage.setPadding(0, 4, 0, 4);
			Linkify.addLinks(sv.mMessage, Linkify.WEB_URLS);

			return sv;
		}
	}

	private class MessageText {
		private String mBareJid;
		private String mName;
		private String mMessage;

		public MessageText(String bareJid, String name, String message) {
			mBareJid = bareJid;
			mName = name;
			mMessage = message;
		}

		public String getBareJid() {
			return mBareJid;
		}

		public String getName() {
			return mName;
		}

		public String getMessage() {
			return mMessage;
		}

		@SuppressWarnings("unused")
		public void setBareJid(String bareJid) {
			mBareJid = bareJid;
		}

		@SuppressWarnings("unused")
		public void setName(String name) {
			mName = name;
		}

		public void setMessage(String message) {
			mMessage = message;
		}
	}

	/**
	 * We will use a MessageView to display each message.
	 */
	private class MessageView extends LinearLayout {
		private TextView mName;
		private TextView mMessage;

		/**
		 * Constructor.
		 * 
		 * @param context
		 * @param name
		 * @param message
		 */
		public MessageView(Context context, String name, String message) {
			super(context);

			this.setOrientation(VERTICAL);

			mName = new TextView(context);
			mName.setText(name);
			addView(mName, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));

			mMessage = new TextView(context);
			mMessage.setText(message);
			addView(mMessage, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
		}

		/**
		 * Convenience method to set the title of a MessageView
		 */
		public void setName(String name) {
			mName.setText(name);
		}

		/**
		 * Convenience method to set the dialogue of a MessageView
		 */
		public void setMessage(String message) {
			mMessage.setText(message);
		}
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	public boolean onKey(View v, int keyCode, KeyEvent event) {
		Log.d(TAG, "KeyEvent = " + event.getAction());
		if (event.getAction() == KeyEvent.ACTION_DOWN) {
			switch (keyCode) {
			case KeyEvent.KEYCODE_ENTER:
				sendMessage();
				return true;
			default:
				return false;
			}
		}
		return false;
	}

	/**
	 * Send an xmpp message.
	 */
	private void sendMessage() {
		final String inputContent = mInputField.getText().toString();

		Log.v(TAG, "BEGIN sendMessage.");
		if (!inputContent.equals("")) {
			Message msgToSend = new Message(mContact.getJID(), Message.MSG_TYPE_CHAT);
			msgToSend.setBody(inputContent);

			try {
				mChat.sendMessage(msgToSend);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}

			final String self = getString(R.string.chat_self);
			MessageText lastMessage = mListMessages.size() != 0 ? mListMessages.get(mListMessages.size() - 1) : null;

			if (lastMessage != null && lastMessage.getName().equals(self)) {
				lastMessage.setMessage(lastMessage.getMessage().concat("\n" + inputContent));
				mListMessages.set(mListMessages.size() - 1, lastMessage);
			} else
				mListMessages.add(new MessageText(self, self, inputContent));
			mMessagesListAdapter.notifyDataSetChanged();
			mInputField.setText(null);
		}
		Log.v(TAG, "END sendMessage.");
	}
}
