/**
 *
 */
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 BeemException mLastException;
    private String mLogin;
    private String mPassword;
    private RosterAdapter mRoster;

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

    /**
     * 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();
    }

    /**
     * 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();
	}

    }

    /**
     * enregistre les features dispo dans notre version Liste de features que Telepathy supporte.
     * "http://www.google.com/xmpp/protocol/session" "http://www.google.com/transport/p2p"
     * "http://jabber.org/protocol/jingle" "http://jabber.org/protocol/chatstates" "http://jabber.org/protocol/nick"
     * "http://jabber.org/protocol/nick+notify" "http://jabber.org/protocol/si" "ttp://jabber.org/protocol/ibb"
     * "ttp://telepathy.freedesktop.org/xmpp/tubes" "http://www.google.com/xmpp/protocol/voice/v1"
     * "http://jabber.org/protocol/jingle/description/audio" "http://jabber.org/protocol/jingle/description/video" Liste
     * de features que pidgin `supporte' (on notera la cradence de l'annonce): "jabber:iq:last" "jabber:iq:oob"
     * "jabber:iq:time" "jabber:iq:version" "jabber:x:conference" "urn:xmpp:attention:0" "urn:xmpp:bob" "urn:xmpp:ping"
     * "xmpp:urn:time" "http://jabber.org/protocol/bytestreams" "http://jabber.org/protocol/disco#info"
     * "http://jabber.org/protocol/disco#items" "http://jabber.org/protocol/ibb" "http://jabber.org/protocol/muc"
     * "http://jabber.org/protocol/muc#user" "http://jabber.org/protocol/si"
     * "http://jabber.org/protocol/si/profile/file-transfer" "http://jabber.org/protocol/xhtml-im"
     * "http://www.xmpp.org/extensions/xep-0199.html#ns" "http://jabber.org/protocol/mood"
     * "http://jabber.org/protocol/mood+notify" "http://jabber.org/protocol/nick"
     * "http://jabber.org/protocol/nick+notify" "http://jabber.org/protocol/tune"
     * "http://jabber.org/protocol/tune+notify" "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
     * "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
     * "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata+notify"
     * "http://www.xmpp.org/extensions/xep-0167.html#ns" << Jingle RTP Sessions
     */
    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");

    }

    @Override
    public boolean isAuthentificated() throws RemoteException {
	return mAdaptee.isAuthenticated();
    }

}
