package org.mercury_im.messenger.core.connection;

import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.ReconnectionManager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.iqversion.VersionManager;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.reactivex.subjects.BehaviorSubject;

public class MercuryConnection {

    public static final String TAG = "Mercury";

    private static final Logger LOGGER = Logger.getLogger(MercuryConnection.class.getName());

    private final AbstractXMPPConnection mConnection;
    private final long mAccountId;

    // Managers
    private final ReconnectionManager mReconnectionManager;
    private final Roster mRoster;
    private final ChatManager mChatManager;
    private final CarbonManager mCarbonManager;
    private final StableUniqueStanzaIdManager mStanzaIdManager;
    private final ServiceDiscoveryManager mServiceDiscoveryManager;
    private final MamManager mMamManager;
    private final VersionManager mVersionManager;

    private BehaviorSubject<ConnectionState> mState = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED);


    public MercuryConnection(AbstractXMPPConnection connection, long accountId) {
        mConnection = connection;
        mConnection.addConnectionListener(mConnectionListener);
        mAccountId = accountId;

        mReconnectionManager = ReconnectionManager.getInstanceFor(connection);
        mReconnectionManager.enableAutomaticReconnection();
        mReconnectionManager.abortPossiblyRunningReconnection();

        mRoster = Roster.getInstanceFor(connection);
        mRoster.setRosterLoadedAtLogin(true);

        mChatManager = ChatManager.getInstanceFor(connection);
        mCarbonManager = CarbonManager.getInstanceFor(connection);

        mStanzaIdManager = StableUniqueStanzaIdManager.getInstanceFor(connection);
        mStanzaIdManager.enable();

        mServiceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
        mServiceDiscoveryManager.setIdentity(new DiscoverInfo.Identity("client", "Mercury", "phone"));

        mVersionManager = VersionManager.getInstanceFor(connection);
        mVersionManager.setVersion("Mercury", "0.0.1-stealth", "Android");
        VersionManager.setAutoAppendSmackVersion(false);

        mMamManager = MamManager.getInstanceFor(connection);
    }

    public void connect() throws InterruptedException, XMPPException, SmackException, IOException {
        LOGGER.log(Level.INFO, "Connecting...");
        mState.onNext(ConnectionState.CONNECTING);
        AbstractXMPPConnection con = (AbstractXMPPConnection) getConnection();

        con.connect().login();
    }

    public void disconnect() throws SmackException.NotConnectedException {
        AbstractXMPPConnection con = (AbstractXMPPConnection) getConnection();
        mState.onNext(ConnectionState.DISCONNECTING);

        con.disconnect(new Presence(Presence.Type.unavailable));

        mState.onNext(ConnectionState.DISCONNECTED);
    }

    public XMPPConnection getConnection() {
        return mConnection;
    }

    public long getAccountId() {
        return mAccountId;
    }

    public Roster getRoster() {
        return mRoster;
    }


    private final ConnectionListener mConnectionListener = new ConnectionListener() {

        @Override
        public void connected(XMPPConnection connection) {
            mState.onNext(ConnectionState.CONNECTED);
        }

        @Override
        public void authenticated(XMPPConnection connection, boolean resumed) {
            mState.onNext(ConnectionState.AUTHENTICATED);
            LOGGER.info("Connection " + getAccountId() + " authenticated (" + (resumed ? "resumed" : "initially") + ")");
            if (!resumed) {
                LOGGER.info("Enabling carbons!");
                mCarbonManager.enableCarbonsAsync(exception -> {
                    LOGGER.severe("Could not enable carbons for connection " + mAccountId + ".");
                    exception.printStackTrace();
                });
            }
        }

        @Override
        public void connectionClosed() {
            mState.onNext(ConnectionState.DISCONNECTED);
        }

        @Override
        public void connectionClosedOnError(Exception e) {
            mState.onNext(ConnectionState.DISCONNECTED);
        }
    };

    public BehaviorSubject<ConnectionState> getState() {
        return mState;
    }
}
