package com.beem.project.beem.service;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.PrivacyListManager;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.ChatStateManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.JingleManager;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.beem.project.beem.BeemService;
import com.beem.project.beem.R;
import com.beem.project.beem.service.aidl.IBeemConnectionListener;
import com.beem.project.beem.service.aidl.IChatManager;
import com.beem.project.beem.service.aidl.IRoster;
import com.beem.project.beem.service.aidl.IXmppConnection;
import com.beem.project.beem.ui.Subscription;
import com.beem.project.beem.utils.BeemBroadcastReceiver;

/**
 * This class implements an adapter for XMPPConnection.
 * 
 * @author darisk
 */
public class XmppConnectionAdapter extends IXmppConnection.Stub {

	/**
	 * Beem connection closed Intent name.
	 */

	private static final String TAG = "XMPPConnectionAdapter";
	private static final int PRESENCE_PRIORITY = 128;

	private final XMPPConnection mAdaptee;
	private IChatManager mChatManager;
	private final String mLogin;
	private final String mPassword;
	private RosterAdapter mRoster;
	private PrivacyListManagerAdapter mPrivacyList;
	private final BeemService mService;
	private final RemoteCallbackList<IBeemConnectionListener> mRemoteConnListeners = new RemoteCallbackList<IBeemConnectionListener>();

	private final ConnexionListenerAdapter mConListener = new ConnexionListenerAdapter();

	/**
	 * Constructor.
	 * 
	 * @param config
	 *            Configuration to use in order to connect
	 * @param login
	 *            login to use on connect
	 * @param password
	 *            password to use on connect
	 * @param service
	 *            the background service associated with the connection.
	 */
	public XmppConnectionAdapter(final ConnectionConfiguration config, final String login, final String password,
			final BeemService service) {
		this(new XMPPConnection(config), login, password, service);
	}

	/**
	 * Constructor.
	 * 
	 * @param serviceName
	 *            name of the service to connect to
	 * @param login
	 *            login to use on connect
	 * @param password
	 *            password to use on connect
	 * @param service
	 *            the background service associated with the connection.
	 */
	public XmppConnectionAdapter(final String serviceName, final String login, final String password,
			final BeemService service) {
		this(new XMPPConnection(serviceName), login, password, service);
	}

	/**
	 * Constructor.
	 * 
	 * @param con
	 *            The connection to adapt
	 * @param login
	 *            The login to use
	 * @param password
	 *            The password to use
	 * @param service
	 *            the background service associated with the connection.
	 */
	public XmppConnectionAdapter(final XMPPConnection con, final String login, final String password,
			final BeemService service) {
		mAdaptee = con;
		PrivacyListManager.getInstanceFor(mAdaptee);
		mLogin = login;
		mPassword = password;
		mService = service;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addConnectionListener(IBeemConnectionListener listen) throws RemoteException {
		if (listen != null)
			mRemoteConnListeners.register(listen);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void connectAsync() throws RemoteException {
		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				try {
					connectSync();
				} catch (RemoteException e) {
					Log.e(TAG, "Error while connecting asynchronously", e);
				}
			}
		});
		t.start();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean connectSync() throws RemoteException {
		try {
			mAdaptee.connect();
			mAdaptee.addConnectionListener(mConListener);
			mAdaptee.login(mLogin, mPassword, "BEEM");

			mChatManager = new BeemChatManager(mAdaptee.getChatManager(), mService);
			mPrivacyList = new PrivacyListManagerAdapter(mAdaptee);

			this.initFeatures(); // pour declarer les features xmpp qu'on
			// supporte
			ChatStateManager.getInstance(mAdaptee);

			triggerAsynchronousConnectEvent();
			// Priority between -128 and 128
			Presence p = new Presence(Presence.Type.available, "Beem : http://www.beem-project.com", PRESENCE_PRIORITY,
					Presence.Mode.available);
			mAdaptee.sendPacket(p);
			return true;
		} catch (XMPPException e) {
			Log.d(TAG, "Error while connecting", e);
			if (e.getXMPPError() != null && e.getXMPPError().getMessage() != null)
				mConListener.connectionFailed(e.getXMPPError().getMessage());
			else if (e.getMessage() != null)
				mConListener.connectionFailed(e.getMessage());
			else
				mConListener.connectionFailed("Error On Connection");
		} catch (IllegalStateException e) {
			mConListener.connectionFailed(e.getMessage());
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean disconnect() {
		if (mAdaptee != null && mAdaptee.isConnected())
			mAdaptee.disconnect();
		return true;
	}

	/**
	 * Get the Smack XmppConnection.
	 * 
	 * @return Smack XmppConnection
	 */
	public XMPPConnection getAdaptee() {
		return mAdaptee;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IChatManager getChatManager() throws RemoteException {
		return mChatManager;
	}

	/**
	 * Get the context of the adapter.
	 * 
	 * @return The context of the adapter
	 */
	public BeemService getContext() {
		return mService;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public IRoster getRoster() throws RemoteException {
		if (mRoster != null)
			return mRoster;
		Roster adap = mAdaptee.getRoster();
		if (adap == null)
			return null;
		mRoster = new RosterAdapter(adap, mService.getApplicationContext());
		return mRoster;
	}

	/**
	 * enregistre les features dispo dans notre version Liste de features que
	 * Telepathy supporte.
	 */
	private void initFeatures() {
		ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
		if (sdm == null)
			sdm = new ServiceDiscoveryManager(mAdaptee);
		sdm.addFeature("http://jabber.org/protocol/disco#info");
		JingleManager.setJingleServiceEnabled();
		sdm.addFeature("jabber:iq:privacy");
	}

	/**
	 * Returns true if currently authenticated by successfully calling the login
	 * method.
	 * 
	 * @return true when successfully authenticated
	 */
	public boolean isAuthentificated() {
		return mAdaptee.isAuthenticated();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeConnectionListener(IBeemConnectionListener listen) throws RemoteException {
		if (listen != null)
			mRemoteConnListeners.unregister(listen);
	}

	/**
	 * Trigger Connection event.
	 */
	private void triggerAsynchronousConnectEvent() {
		mConListener.onConnect();
	}

	/**
	 * Set the privacy list to use.
	 * 
	 * @param privacyList
	 *            the mPrivacyList to set
	 */
	public void setPrivacyList(PrivacyListManagerAdapter privacyList) {
		this.mPrivacyList = privacyList;
	}

	/**
	 * Get the privacy list in use.
	 * 
	 * @return the mPrivacyList
	 */
	public PrivacyListManagerAdapter getPrivacyList() {
		return mPrivacyList;
	}

	/**
	 * Listener for XMPP connection events. It will calls the remote listeners
	 * for connection events.
	 * 
	 * @author darisk
	 */
	private class ConnexionListenerAdapter implements ConnectionListener {

		/**
		 * Defaut constructor.
		 */
		public ConnexionListenerAdapter() {
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void connectionClosed() {
			Log.d(TAG, "closing connection");
			mRoster = null;
			Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED);
			intent.putExtra("message", mService.getString(R.string.BeemBroadcastReceiverDisconnect));
			intent.putExtra("normally", true);
			mService.sendBroadcast(intent);
			mService.stopSelf();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void connectionClosedOnError(Exception exception) {
			Log.d(TAG, "connectionClosedOnError");
			mRoster = null;
			Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED);
			intent.putExtra("message", exception.getMessage());
			mService.sendBroadcast(intent);
			mService.stopSelf();
		}

		/**
		 * Connection failed callback.
		 * 
		 * @param errorMsg
		 *            smack failure message
		 */
		public void connectionFailed(String errorMsg) {
			Log.d(TAG, "Connection Failed");
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					if (listener != null)
						listener.connectionFailed(errorMsg);
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, "Error while triggering remote connection listeners", e);
				}
			}
			mRemoteConnListeners.finishBroadcast();
			mService.stopSelf();
		}

		/**
		 * Method to execute when a connection event occurs.
		 */
		public void onConnect() {
			PacketFilter filter = new PacketFilter() {

				@Override
				public boolean accept(Packet packet) {
					if (packet instanceof Presence) {
						Presence pres = (Presence) packet;
						if (pres.getType() == Presence.Type.subscribe)
							return true;
					}
					return false;
				}
			};

			mAdaptee.addPacketListener(new PacketListener() {

				@Override
				public void processPacket(Packet packet) {
					String from = packet.getFrom();
					Notification notif = new Notification(com.beem.project.beem.R.drawable.signal, mService
							.getString(R.string.AcceptContactRequest), System.currentTimeMillis());
					notif.defaults = Notification.DEFAULT_ALL;
					notif.flags = Notification.FLAG_AUTO_CANCEL;
					Intent intent = new Intent(mService, Subscription.class);
					intent.putExtra("from", from);
					notif.setLatestEventInfo(mService, from, mService.getString(R.string.AcceptContactRequestFrom)
							+ from, PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_ONE_SHOT));
					int id = packet.hashCode();
					mService.sendNotification(id, notif);
				}
			}, filter);

			mService.resetStatus();
			mService.initJingle(mAdaptee);

			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					if (listener != null)
						listener.onConnect();
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, "Error while triggering remote connection listeners", e);
				}
			}
			mRemoteConnListeners.finishBroadcast();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectingIn(int arg0) {
			Log.d(TAG, "reconnectingIn");
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					if (listener != null)
						listener.reconnectingIn(arg0);
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, "Error while triggering remote connection listeners", e);
				}
			}
			mRemoteConnListeners.finishBroadcast();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectionFailed(Exception arg0) {
			Log.d(TAG, "reconnectionFailed");
			final int r = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < r; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					if (listener != null)
						listener.reconnectionFailed();
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, "Error while triggering remote connection listeners", e);
				}
			}
			mRemoteConnListeners.finishBroadcast();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void reconnectionSuccessful() {
			Log.d(TAG, "reconnectionSuccessful");
			PacketFilter filter = new PacketFilter() {

				@Override
				public boolean accept(Packet packet) {
					if (packet instanceof Presence) {
						Presence pres = (Presence) packet;
						if (pres.getType() == Presence.Type.subscribe)
							return true;
					}
					return false;
				}
			};

			mAdaptee.addPacketListener(new PacketListener() {

				@Override
				public void processPacket(Packet packet) {
					String from = packet.getFrom();
					Notification notif = new Notification(com.beem.project.beem.R.drawable.signal, mService
							.getString(R.string.AcceptContactRequest), System.currentTimeMillis());
					notif.defaults = Notification.DEFAULT_ALL;
					notif.flags = Notification.FLAG_AUTO_CANCEL;
					Intent intent = new Intent(mService, Subscription.class);
					intent.putExtra("from", from);
					notif.setLatestEventInfo(mService, from, mService.getString(R.string.AcceptContactRequestFrom)
							+ from, PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_ONE_SHOT));
					int id = packet.hashCode();
					mService.sendNotification(id, notif);
				}
			}, filter);

			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					if (listener != null)
						listener.reconnectionSuccessful();
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
					Log.w(TAG, "Error while triggering remote connection listeners", e);
				}
			}
			mRemoteConnListeners.finishBroadcast();
		}
	}

}
