package org.limewire.xmpp.client.impl;

import java.util.concurrent.Callable;

import org.jivesoftware.smack.roster.Roster;
import org.limewire.concurrent.ExecutorsHelper;
import org.limewire.concurrent.ListeningExecutorService;
import org.limewire.concurrent.ListeningFuture;
import org.limewire.friend.api.FriendConnection;
import org.limewire.friend.api.FriendConnectionConfiguration;
import org.limewire.friend.api.FriendConnectionEvent;
import org.limewire.friend.api.FriendConnectionFactory;
import org.limewire.friend.api.FriendConnectionFactoryRegistry;
import org.limewire.friend.api.FriendException;
import org.limewire.friend.api.FriendPresence;
import org.limewire.friend.api.Network;
import org.limewire.inject.EagerSingleton;
import org.limewire.lifecycle.Asynchronous;
import org.limewire.lifecycle.Service;
import org.limewire.listener.EventBean;
import org.limewire.listener.EventMulticaster;
import org.limewire.listener.EventUtils;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.xmpp.api.client.JabberSettings;

import com.google.inject.Inject;


@EagerSingleton
public class XMPPConnectionFactoryImpl implements Service, FriendConnectionFactory {

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

    private final XMPPConnectionImplFactory connectionImplFactory;
    private final JabberSettings jabberSettings;
    private final ListeningExecutorService executorService;

    private final EventBean<FriendConnectionEvent> friendConnectionEventBean;

    @Inject
    public XMPPConnectionFactoryImpl(XMPPConnectionImplFactory connectionImplFactory,
            EventMulticaster<FriendConnectionEvent> connectionBroadcaster,
            EventBean<FriendConnectionEvent> friendConnectionEventBean,
            JabberSettings jabberSettings) {
        this.connectionImplFactory = connectionImplFactory;
        this.friendConnectionEventBean = friendConnectionEventBean;
        this.jabberSettings = jabberSettings;

        connectionBroadcaster.addListener(new ReconnectionManager(this));
        // We'll install our own subscription listeners
        Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
        executorService = ExecutorsHelper.newSingleThreadExecutor(ExecutorsHelper.daemonThreadFactory("XMPPServiceImpl"));
    }

    @Inject
    void register(org.limewire.lifecycle.ServiceRegistry registry) {
        registry.register(this);
    }

    @Override
    public void initialize() {
    }

    @Override
    public void start() {
    }

    /**
     * Logs out all existing connections.
     */
    @Asynchronous
    @Override
    public void stop() {
        XMPPFriendConnectionImpl connection = getActiveConnection();
        if (connection != null) {
            connection.logoutImpl(null);
        }
    }

    @Override
    public String getServiceName() {
        return "XMPP";
    }

    @Override
    public ListeningFuture<FriendConnection> login(final FriendConnectionConfiguration configuration) {
        return executorService.submit(new Callable<FriendConnection>() {
            @Override
            public FriendConnection call() throws Exception {
                return loginImpl(configuration);
            }
        }); 
    }

    @Override
    @Inject
    public void register(FriendConnectionFactoryRegistry registry) {
        registry.register(Network.Type.XMPP, this);
    }

    FriendConnection loginImpl(FriendConnectionConfiguration configuration) throws FriendException {
        return loginImpl(configuration, false);
    }

    FriendConnection loginImpl(FriendConnectionConfiguration configuration, boolean isReconnect) throws FriendException {
        synchronized (this) {
            XMPPFriendConnectionImpl activeConnection = getActiveConnection();
            if(isReconnect) {
                if(activeConnection != null) {
                    return activeConnection;
                }
            } else {
                if(activeConnection != null) {
                    if (activeConnection.getConfiguration().equals(configuration)) {
                        return activeConnection;
                    } else {
                        // logout synchronously
                        activeConnection.logoutImpl(null);
                    }
                }    
            }                
            
            XMPPFriendConnectionImpl connection = connectionImplFactory.createConnection(
                    configuration, executorService);
            try {
                connection.loginImpl();
                //maintain the last set login state available or do not disturb
                connection.setModeImpl(jabberSettings.isDoNotDisturbSet() ? FriendPresence.Mode.dnd : FriendPresence.Mode.available);
                return connection;
            } catch(FriendException e) {
                LOG.debug(e.getMessage(), e);
                throw new FriendException(e);
            }
        }
    }

    XMPPFriendConnectionImpl getActiveConnection() {
        FriendConnection friendConnection = EventUtils.getSource(friendConnectionEventBean);
        if (friendConnection instanceof XMPPFriendConnectionImpl && friendConnection.isLoggedIn()) {
            return (XMPPFriendConnectionImpl)friendConnection;
        }
        return null;
    }

    @SuppressWarnings("unused")
    private void setModeImpl(FriendPresence.Mode mode) throws FriendException {
        XMPPFriendConnectionImpl connection = getActiveConnection();
        if (connection != null) {
            connection.setModeImpl(mode);
        }
    }

    @Override
    public ListeningFuture<String> requestLoginUrl(FriendConnectionConfiguration configuration) {
        return null;
    }

    
}
