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

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PrivacyListManager;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smackx.ChatStateManager;
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.BeemService;
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 PrivacyListManagerAdapter mPrivacyList;
    private BeemService mService;
    private RemoteCallbackList<IBeemConnectionListener> mRemoteConnListeners =
	new RemoteCallbackList<IBeemConnectionListener>();

    private 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", 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",
		    128, Presence.Mode.available);
	    mAdaptee.sendPacket(p);
	    return true;
	} catch (XMPPException e) {
	    Log.e(TAG, "Error while connecting", e);
	    if (e.getXMPPError() != null && e.getXMPPError().getMessage() != null)
		mConListener.connectionFailed(e.getXMPPError().getMessage());
	    else
		mConListener.connectionFailed("Error On Connection");
	}
	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);
	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 mPrivacyList 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 connexion events.
     * @author darisk
     */
    private class ConnexionListenerAdapter implements ConnectionListener {

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void connectionClosed() {
	    mRoster = null;

	    final int n = mRemoteConnListeners.beginBroadcast();

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

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

	public void connectionFailed(String errorMsg) {
	    Log.i(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();
	}

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

}
