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

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.JingleManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import com.beem.project.beem.BeemException;
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;

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

	private static final String TAG = "XMPPConnectionAdapter";
	private XMPPConnection mAdaptee;
	private IChatManager mChatManager;
	private String mLogin;
	private String mPassword;
	private RosterAdapter mRoster;

	private RemoteCallbackList<IBeemConnectionListener> mRemoteConnListeners =
		new RemoteCallbackList<IBeemConnectionListener>();
	private ConnexionListenerAdapter mConListener = new ConnexionListenerAdapter();
	private Object mLastException;

	/**
	 * Constructor.
	 * @param con The connection to adapt
	 * @param login The login to use
	 * @param password The password to use
	 */
	public XMPPConnectionAdapter(final XMPPConnection con, final String login, final String password) {
		mAdaptee = con;
		mLogin = login;
		mPassword = password;
	}

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

	/**
	 * Constructor.
	 * @param config Configuration to use in order to connect
	 * @param login login to use on connect
	 * @param password password to use on connect
	 */
	public XMPPConnectionAdapter(final ConnectionConfiguration config, final String login, final String password) {
		this(new XMPPConnection(config), login, password);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean connectSync() throws RemoteException {
		try {
			mAdaptee.connect();
			mAdaptee.addConnectionListener(mConListener);
			mAdaptee.login(mLogin, mPassword, "BEEM");
			mChatManager = new BeemChatManager(mAdaptee.getChatManager());
			// TODO find why this cause a null pointer exception
			this.initFeatures(); // pour declarer les features xmpp qu'on supporte
			mLastException = null;
			triggerAsynchronousConnectEvent();
			return true;
		} catch (XMPPException e) {
			mLastException = new BeemException(e);
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean disconnect() {
		mAdaptee.disconnect();
		mLastException = null;
		return true;
	}

	/**
	 * {@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);
		return mRoster;
	}

	/**
	 * {@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", e);
				}
			}
		});
		t.start();
	}

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

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

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

	/**
	 * Trigger Connection event.
	 */
	private void triggerAsynchronousConnectEvent() {
		mConListener.onConnect();
	}
	
	/**
	 * enregistre les features dispo dans notre version Liste de features que Telepathy supporte.
	 */
	private void initFeatures() {
		JingleManager.setJingleServiceEnabled();
		ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
		sdm.addFeature("http://jabber.org/protocol/disco#info");
		// sdm.addFeature("http://jabber.org/protocol/nick");

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

		/**
		 * Defaut constructor.
		 */
		public ConnexionListenerAdapter() {
			// TODO Auto-generated constructor stub
		}

		/**
		 * Method to execute when a connection event occurs.
		 */
		public void onConnect() {
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					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 connectionClosed() {
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					listener.connectionClosed();
				} 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 connectionClosedOnError(Exception arg0) {
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					listener.connectionClosedOnError();
				} 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) {
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					listener.reconnectingIn(arg0);
				} catch (RemoteException e) {
					// The RemoteCallbackList will take care of removing the
					// dead listeners.
				}
			}
			mRemoteConnListeners.finishBroadcast();
		}

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

			for (int i = 0; i < r; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					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() {
			final int n = mRemoteConnListeners.beginBroadcast();

			for (int i = 0; i < n; i++) {
				IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
				try {
					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();
		}
	}
}
