/*
 * Copyright 2006-2010 Daniel Henninger.  All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package net.sf.kraken;

import net.sf.kraken.roster.TransportBuddy;
import net.sf.kraken.session.cluster.TransportSessionRouter;
import net.sf.kraken.type.TransportType;

import org.apache.log4j.Logger;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.xmpp.component.ComponentManager;

import java.util.Map;

/**
 * Transport Instance
 *
 * Represents all information that needs to be tracked about a gateway instance.
 *
 * @author Daniel Henninger
 */
public class TransportInstance<B extends TransportBuddy> implements PropertyEventListener {

    final static Logger Log = Logger.getLogger(TransportInstance.class);

    private final ComponentManager componentManager;
    private final String description;
    private final String nameOfClass;
    public BaseTransport<B> transport = null;
    private final TransportType type;
    private boolean enabled = false;
    private boolean running = false;
    private String subDomain;
    private final TransportSessionRouter sessionRouter;

    /**
     *  Creates a new transport instance.
     *
     *  @param type Type of transport.
     *  @param description Short description of transport.
     *  @param classname Full name/path of class associated with instance.
     *  @param componentManager Component manager managing this instance.
     *  @param sessionRouter Session router managing this instance.
     */
    public TransportInstance(TransportType type, String description, String classname, ComponentManager componentManager, TransportSessionRouter sessionRouter) {
        this.description = description;
        this.type = type;
        this.nameOfClass = classname;
        this.componentManager = componentManager;
        this.sessionRouter = sessionRouter;
        enabled = JiveGlobals.getBooleanProperty("plugin.gateway."+this.type.toString()+".enabled", false);
        subDomain = JiveGlobals.getProperty("plugin.gateway."+this.type.toString()+".subdomain", this.type.toString());
    }

    /**
     *  Retrieves the name of the service (aka, subdomain)
     *
     *  @return name of the service
     */
    public String getName() {
        return this.type.toString();
    }

    /**
     *  Returns whether this transport instance is enabled.
     *
     *  @return true or false if instance is enabled
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     *  Returns whether this transport instance is currently running.
     *
     *  @return true or false if instance is currently running
     */
    public boolean isRunning() {
        return running;
    }

    /**
     *  Enables the transport instance and starts it if it's not already running.
     */
    public void enable() {
        enabled = true;
        JiveGlobals.setProperty("plugin.gateway."+this.type.toString()+".enabled", "true");
        if (!running) {
            startInstance();
        }
    }

    /**
     *  Disables the transport instance and stops it if it's running.
     */
    public void disable() {
        enabled = false;
        JiveGlobals.setProperty("plugin.gateway."+this.type.toString()+".enabled", "false");
        if (running) {
            stopInstance();
        }
    }

    /**
     *  Starts the transport instance if it's enabled and not already running.
     */
    public void startInstance() {
        if (!enabled || running) {
            return;
        }

        Log.info("Starting transport service: "+type.toString());

        transport = null;

        //Log.debug("Loading class "+nameOfClass);

        try {
            transport = (BaseTransport)Class.forName(nameOfClass).newInstance();
            transport.setup(this.type, this.description, sessionRouter);
        }
        catch (ClassNotFoundException e) {
            Log.error("Unable to find class: "+nameOfClass);
            return;
        }
        catch (InstantiationException e) {
            Log.error("Unable to instantiate class: "+nameOfClass);
            return;
        }
        catch (IllegalAccessException e) {
            Log.error("Unable to access class: "+nameOfClass);
            return;
        }

        // Automatically kill any current s2s connections with the JID we want to use.
        SessionManager sessionManager = SessionManager.getInstance();
        String fullJID = this.subDomain+"."+XMPPServer.getInstance().getServerInfo().getXMPPDomain();
        boolean pause = false;
        try {
            for (Session sess : sessionManager.getIncomingServerSessions(fullJID)) {
                sess.close();
                pause = true;
            }
        }
        catch (Exception ignored) {
            // Session might have disappeared on its own
        }

        try {
            Session sess = sessionManager.getOutgoingServerSession(fullJID);
            if (sess != null) {
                sess.close();
                pause = true;
            }
        }
        catch (Exception ignored) {
            // Session might have disappeared on its own
        }

        try {
            // Wait one second if we closed something.
            if (pause) {
                Thread.sleep(1000L);
            }
        }
        catch (Exception ignored) {
            // Hrm, interrupted?  That's odd.
        }

        try {
            componentManager.addComponent(this.subDomain, transport);
            PropertyEventDispatcher.addListener(this);
            running = true;
        }
        catch (Exception e) {
            Log.error("Error while adding component "+this.subDomain+": ", e);
        }
    }

    /**
     *  Stops the transport instance if it's running.
     */
    public void stopInstance() {
        if (!running) {
            return;
        }

        Log.info("Stopping transport service: "+type.toString());

        transport.shutdown();
        PropertyEventDispatcher.removeListener(this);
        try {
            componentManager.removeComponent(this.subDomain);
        }
        catch (Exception e) {
            Log.error("Error while removing component "+this.subDomain+": ", e);
        }
        transport = null;
        running = false;
    }

    /**
     * Retrieves actual transport associated with this instance.
     *
     * @return Transport that the instance is associated with.
     */
    public BaseTransport<B> getTransport() {
        return transport;
    }

    @SuppressWarnings("unchecked")
    public void propertySet(String property, Map params) {
        if (property.startsWith("plugin.gateway.")) {
            if (property.equals("plugin.gateway."+this.type.toString()+".enabled")) {
                enabled = Boolean.parseBoolean((String)params.get("value"));
                if (enabled) {
                    if (!running) {
                        startInstance();
                    }
                }
                else {
                    if (running) {
                        stopInstance();
                    }
                }
            }
            else if (property.equals("plugin.gateway."+this.type.toString()+".subdomain")) {
                String newSubDomain = (String)params.get("value");
                if (!newSubDomain.equals(this.subDomain)) {
                    if (running) {
                        stopInstance();
                        this.subDomain = newSubDomain;
                        startInstance();
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void propertyDeleted(String property, Map params) {
        if (property.startsWith("plugin.gateway.")) {
            if (property.equals("plugin.gateway."+this.type.toString()+".enabled")) {
                if (running) {
                    stopInstance();
                }
            }
            else if (property.equals("plugin.gateway."+this.type.toString()+".subdomain")) {
                String newSubDomain = this.type.toString();
                if (!newSubDomain.equals(this.subDomain)) {
                    if (running) {
                        stopInstance();
                        this.subDomain = newSubDomain;
                        startInstance();
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void xmlPropertySet(String property, Map params) {
        propertySet(property, params);
    }

    @SuppressWarnings("unchecked")
    public void xmlPropertyDeleted(String property, Map params) {
        propertyDeleted(property, params);
    }

}
