/**
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jivesoftware.sparkimpl.plugin.gateways.transports;

import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.IQReplyFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smackx.iqregister.packet.Registration;
import org.jivesoftware.smackx.iqprivate.PrivateDataManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.util.TaskEngine;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.gateways.GatewayPrivateData;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * Handles some basic handling of
 */
public class TransportUtils {

    private static Map<DomainBareJid, Transport> transports = new HashMap<>();
    private static GatewayPrivateData gatewayPreferences;

    private TransportUtils() {
    }

    static {
        PrivateDataManager.addPrivateDataProvider(GatewayPrivateData.ELEMENT, GatewayPrivateData.NAMESPACE, new GatewayPrivateData.ConferencePrivateDataProvider());

        final Runnable loadGateways = () -> {
            PrivateDataManager pdm = SparkManager.getSessionManager().getPersonalDataManager();
            gatewayPreferences = null;
            //Re: SPARK-1483 comment the loop as it causes Out Of Memory (infinite loop) if preferences not found
            //If really necessary to try more times, a Thread Pool may be used: java ScheduledThreadPoolExecutor for example
            //while (gatewayPreferences == null){
                try {
                    gatewayPreferences = (GatewayPrivateData)pdm.getPrivateData(GatewayPrivateData.ELEMENT, GatewayPrivateData.NAMESPACE);
                }
                catch (XMPPException | SmackException | InterruptedException e) {
                    Log.error("Unable to load private data for Gateways", e);
                }
            //}
        };

        TaskEngine.getInstance().submit(loadGateways);
    }

    public static boolean autoJoinService(DomainBareJid serviceName) {
        if (gatewayPreferences != null) {
        	return gatewayPreferences.autoLogin(serviceName);
        }else{
        	return false;
        }
    }

    public static void setAutoJoin(DomainBareJid serviceName, boolean autoJoin) {
    	if (gatewayPreferences != null) {
    		gatewayPreferences.addService(serviceName, autoJoin);
    		PrivateDataManager pdm = SparkManager.getSessionManager().getPersonalDataManager();
    		try {
    			pdm.setPrivateData(gatewayPreferences);
    		}
    		catch (XMPPException | SmackException | InterruptedException e) {
    			Log.error(e);
    		}
    	} else {
    		Log.warning("Cannot set privacy data as gatewayPreferences is NULL");
    	}
    }

    /**
     * 
     * @param serviceName
     * @return
     * @deprecated use {@link #getTransport(DomainBareJid)} instead.
     */
    @Deprecated
    public static Transport getTransport(String serviceName) {
        DomainBareJid transportAddress = JidCreate.domainBareFromOrThrowUnchecked(serviceName);
        return getTransport(transportAddress);
    }

    public static Transport getTransport(DomainBareJid transportAddress) {
        // Return transport.
        return transports.get(transportAddress);
    }

    /**
     * Returns true if the jid is from a gateway.
     * @param jid the jid.
     * @return true if the jid is from a gateway.
     */
    public static boolean isFromGateway(Jid jid) {
        DomainBareJid serviceName = jid.asDomainBareJid();
        return transports.containsKey(serviceName);
    }

    /**
     * 
     * @param serviceName
     * @param transport
     * @deprecated use {@link #addTransport(DomainBareJid, Transport)} instead.
     */
    @Deprecated
    public static void addTransport(String serviceName, Transport transport) {
        DomainBareJid transportAddress = JidCreate.domainBareFromOrThrowUnchecked(serviceName);
         addTransport(transportAddress, transport);
    }

    public static void addTransport(DomainBareJid transportAddress, Transport transport) {
        transports.put(transportAddress, transport);
    }

    public static Collection<Transport> getTransports() {
        return transports.values();
    }

    /**
     * Checks if the user is registered with a gateway.
     *
     * @param con       the XMPPConnection.
     * @param transport the transport.
     * @return true if the user is registered with the transport.
     */
    public static boolean isRegistered(XMPPConnection con, Transport transport) {
        if (!con.isConnected()) {
            return false;
        }

        ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(con);
        try {
            Jid jid = JidCreate.from(transport.getXMPPServiceDomain());
            DiscoverInfo info = discoveryManager.discoverInfo(jid);
            return info.containsFeature("jabber:iq:registered");
        }
        catch (XMPPException | SmackException | XmppStringprepException | InterruptedException e) {
            Log.error(e);
        }
        return false;
    }

    /**
     * Registers a user with a gateway.
     *
     * @param con           the XMPPConnection.
     * @param gatewayDomain the domain of the gateway (service name)
     * @param username      the username.
     * @param password      the password.
     * @param nickname      the nickname.
     * @throws InterruptedException 
     * @throws XMPPException thrown if there was an issue registering with the gateway.
     */
    public static void registerUser(XMPPConnection con, DomainBareJid gatewayDomain, String username, String password, String nickname, StanzaListener callback) throws SmackException.NotConnectedException, InterruptedException
    {
        Map<String, String> attributes = new HashMap<>();
        if (username != null) {
            attributes.put("username", username);
        }
        if (password != null) {
            attributes.put("password", password);
        }
        if (nickname != null) {
            attributes.put("nick", nickname);
        }
        Registration registration = new Registration( attributes );
        registration.setType(IQ.Type.set);
        registration.setTo(gatewayDomain);
        registration.addExtension(new GatewayRegisterExtension());

        con.sendStanzaWithResponseCallback( registration, new IQReplyFilter( registration, con ), callback);
    }

    /**
     * @param con           the XMPPConnection.
     * @param gatewayDomain the domain of the gateway (service name)
     * @throws InterruptedException 
     * @throws XMPPException thrown if there was an issue unregistering with the gateway.
     */
    public static void unregister(XMPPConnection con, DomainBareJid gatewayDomain) throws SmackException.NotConnectedException, InterruptedException
    {
        Map<String,String> map = new HashMap<>();
        map.put("remove", "");
        Registration registration = new Registration( map );
        registration.setType(IQ.Type.set);
        registration.setTo(gatewayDomain);

        con.sendStanzaWithResponseCallback( registration, new IQReplyFilter( registration, con ), stanza -> {
            IQ response = (IQ) stanza;
            if (response.getType() == IQ.Type.error ) {
                Log.warning( "Unable to unregister from gateway: " + stanza );
            }
        } );
    }


    static class GatewayRegisterExtension implements ExtensionElement {

        @Override
		public String getElementName() {
            return "x";
        }

        @Override
		public String getNamespace() {
            return "jabber:iq:gateway:register";
        }

        @Override
		public String toXML(String enclosingNamespace) {
            String builder = "<" + getElementName() + " xmlns=\"" + getNamespace() +
                    "\"/>";
            return builder;
        }
    }

}