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(android.R.drawable.stat_notify_more, mService
			.getString(R.string.AcceptContactRequest, from), 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(android.R.drawable.stat_notify_more, mService
			.getString(R.string.AcceptContactRequest, from), 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();
	}
    }

}
