package org.limewire.xmpp.client.impl.features;


import org.jivesoftware.smack.XMPPConnection;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.FriendPresenceEvent;
import org.limewire.friend.api.RosterEvent;
import org.limewire.friend.api.feature.FeatureInitializer;
import org.limewire.friend.api.feature.FeatureRegistry;
import org.limewire.friend.impl.feature.NoSaveFeature;
import org.limewire.listener.BlockingEvent;
import org.limewire.listener.EventListener;
import org.limewire.listener.ListenerSupport;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.xmpp.client.impl.XMPPFriendConnectionImpl;
import org.limewire.xmpp.client.impl.messages.nosave.NoSaveIQ;
import org.limewire.xmpp.client.impl.messages.nosave.NoSaveIQListener;

/**
 * Initializer for {@link NoSaveFeature}.
 */
public class NoSaveFeatureInitializer implements FeatureInitializer {

    private static final Log LOG = LogFactory.getLog(NoSaveFeatureInitializer.class);

    private final XMPPFriendConnectionImpl connection;
    private final XMPPConnection jabberConnection;
    private final ListenerSupport<RosterEvent> rosterSupport;
    private final ListenerSupport<FriendPresenceEvent> friendPresenceSupport;
    private RosterListener rosterListener;
    private NoSaveIQListener noSaveListener;

    // The feature can only be fully initialized (meaning that the NoSaveIQ
    // packet is sent, and the feature added to the xmpp connection)
    // after both of the following preconditions are met:
    //
    // 1. Roster received           (RosterEvent is used to determine this)
    // 2. NoSaveFeature  supported  (We know it is when initializeFeature is called)
    //
    private boolean isRosterReceived;
    private boolean isFeatureSupported;

    public NoSaveFeatureInitializer(XMPPConnection jabberConnection, XMPPFriendConnectionImpl limeConnection,
                                    ListenerSupport<RosterEvent> rosterSupport,
                                    ListenerSupport<FriendPresenceEvent> friendPresenceSupport) {
        this.connection = limeConnection;
        this.jabberConnection = jabberConnection;
        this.rosterSupport = rosterSupport;
        this.friendPresenceSupport = friendPresenceSupport;
        this.rosterListener = new RosterListener();
        this.noSaveListener = null;
    }

    @Override
    public void register(FeatureRegistry registry) {
        rosterSupport.addListener(rosterListener);
        registry.registerPrivateInitializer(NoSaveFeature.ID, this);
    }

    @Override
    public void initializeFeature(FriendPresence notUsedInFeatureInit) {
        setFeatureSupported(true);
        sendNoSaveRequest();
    }

    @Override
    public void removeFeature(FriendPresence friendPresence) {
        friendPresence.removeFeature(NoSaveFeature.ID);
    }

    public void cleanup() {
        rosterSupport.removeListener(rosterListener);

        if (noSaveListener != null) {
            jabberConnection.removeAsyncStanzaListener(noSaveListener);
            noSaveListener.cleanup();
        }
    }

    /**
     * Only initialize the feature if both pre-conditions have been met
     * <pre>
     * 1. Roster has been received
     * 2. The feature {@link NoSaveFeature} is supported
     * </pre>
     */
    private void sendNoSaveRequest() {

        if (canSendNoSaveRequest()) {
            // send message to entity server explicitly requesting nosave state
            // make sure noSaveIQListener is ready for the reply messages
            if (noSaveListener == null) {
                noSaveListener = NoSaveIQListener.createNoSaveIQListener(connection, friendPresenceSupport);
                jabberConnection.addAsyncStanzaListener(noSaveListener, noSaveListener.getStanzaFilter());
            }
            NoSaveIQ requestNoSaveSettingsPacket = NoSaveIQ.getNoSaveStatesMessage();

            try {
                connection.sendPacket(requestNoSaveSettingsPacket);
            } catch (FriendException e) {
                LOG.warn("couldn't request google:nosave settings", e);
            }
        }
    }

    private synchronized void setFeatureSupported(boolean isFeatureSupported) {
        this.isFeatureSupported = isFeatureSupported;
    }

    private synchronized void setRosterReceived(boolean isRosterReceived) {
        this.isRosterReceived = isRosterReceived;
    }

    private synchronized boolean canSendNoSaveRequest() {
        return isFeatureSupported && isRosterReceived;
    }

    /** Every time roster changes, (re)send nosave request packet.
     *  Event handler code is annotated @BlockingEvent because we are
     *  potentially over the network.
     **/
    private class RosterListener implements EventListener<RosterEvent> {
        @BlockingEvent
        @Override
        public void handleEvent(RosterEvent event) {
            setRosterReceived(true);
            sendNoSaveRequest();
        }
    }
}
