/**
 * $RCSfile$
 * $Revision$
 * $Date$
 *
 * Copyright (C) 2004 Jive Software. 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 org.jivesoftware.messenger.spi;

import org.jivesoftware.messenger.*;
import org.jivesoftware.messenger.container.BasicModule;
import org.jivesoftware.messenger.net.*;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveGlobals;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ConnectionManagerImpl extends BasicModule implements ConnectionManager {

    private SocketAcceptThread socketThread;
    private SSLSocketAcceptThread sslSocketThread;
    private SocketAcceptThread componentSocketThread;
    private SocketAcceptThread serverSocketThread;
    private ArrayList<ServerPort> ports;

    private SessionManager sessionManager;
    private PacketDeliverer deliverer;
    private PacketRouter router;
    private String serverName;
    private XMPPServer server;

    public ConnectionManagerImpl() {
        super("Connection Manager");
        ports = new ArrayList<ServerPort>(4);
    }

    private void createSocket() {
        if (!isStarted || isSocketStarted || sessionManager == null || deliverer == null ||
                router == null ||
                serverName == null)
        {
            return;
        }
        isSocketStarted = true;

        // Setup port info
        String localIPAddress = null;
        try {
            localIPAddress = InetAddress.getLocalHost().getHostAddress();
        }
        catch (UnknownHostException e) {
            if (localIPAddress == null) {
                localIPAddress = "Unknown";
            }
        }
        // Start the port listener for s2s communication
        startServerListener(localIPAddress);
        // Start the port listener for external components
        startComponentListener(localIPAddress);
        // Start the port listener for clients
        startClientListeners(localIPAddress);
    }

    private void startServerListener(String localIPAddress) {
        // Start servers socket unless it's been disabled.
        if (JiveGlobals.getBooleanProperty("xmpp.server.socket.active", true)) {
            int port = JiveGlobals.getIntProperty("xmpp.server.socket.port",
                    SocketAcceptThread.DEFAULT_SERVER_PORT);
            String interfaceName = JiveGlobals.getProperty("xmpp.server.socket.interface");
            ServerPort serverPort = new ServerPort(port, interfaceName, serverName, localIPAddress,
                    false, null, ServerPort.Type.server);
            try {
                serverSocketThread = new SocketAcceptThread(this, serverPort);
                ports.add(serverPort);
                serverSocketThread.setDaemon(true);
                serverSocketThread.start();

                List params = new ArrayList();
                params.add(Integer.toString(serverSocketThread.getPort()));
                Log.info(LocaleUtils.getLocalizedString("startup.server", params));
            }
            catch (Exception e) {
                System.err.println("Error starting server listener on port " + port + ": " +
                        e.getMessage());
                Log.error(LocaleUtils.getLocalizedString("admin.error.socket-setup"), e);
            }
        }
    }

    private void startComponentListener(String localIPAddress) {
        // Start components socket unless it's been disabled.
        if (JiveGlobals.getBooleanProperty("xmpp.component.socket.active", true)) {
            int port = JiveGlobals.getIntProperty("xmpp.component.socket.port",
                    SocketAcceptThread.DEFAULT_COMPONENT_PORT);
            String interfaceName = JiveGlobals.getProperty("xmpp.component.socket.interface");
            ServerPort serverPort = new ServerPort(port, interfaceName, serverName, localIPAddress,
                    false, null, ServerPort.Type.component);
            try {
                componentSocketThread = new SocketAcceptThread(this, serverPort);
                ports.add(serverPort);
                componentSocketThread.setDaemon(true);
                componentSocketThread.start();

                List params = new ArrayList();
                params.add(Integer.toString(componentSocketThread.getPort()));
                Log.info(LocaleUtils.getLocalizedString("startup.component", params));
            }
            catch (Exception e) {
                System.err.println("Error starting component listener on port " + port + ": " +
                        e.getMessage());
                Log.error(LocaleUtils.getLocalizedString("admin.error.socket-setup"), e);
            }
        }
    }

    private void startClientListeners(String localIPAddress) {
        // Start clients plain socket unless it's been disabled.
        if (JiveGlobals.getBooleanProperty("xmpp.socket.plain.active", true)) {
            int port = JiveGlobals.getIntProperty("xmpp.socket.plain.port",
                    SocketAcceptThread.DEFAULT_PORT);
            String interfaceName = JiveGlobals.getProperty("xmpp.socket.plain.interface");
            ServerPort serverPort = new ServerPort(port, interfaceName, serverName, localIPAddress,
                    false, null, ServerPort.Type.client);
            try {
                socketThread = new SocketAcceptThread(this, serverPort);
                ports.add(serverPort);
                socketThread.setDaemon(true);
                socketThread.start();

                List params = new ArrayList();
                params.add(Integer.toString(socketThread.getPort()));
                Log.info(LocaleUtils.getLocalizedString("startup.plain", params));
            }
            catch (Exception e) {
                System.err.println("Error starting XMPP listener on port " + port + ": " +
                        e.getMessage());
                Log.error(LocaleUtils.getLocalizedString("admin.error.socket-setup"), e);
            }
        }
        // Start clients SSL unless it's been disabled.
        if (JiveGlobals.getBooleanProperty("xmpp.socket.ssl.active", true)) {
            int port = JiveGlobals.getIntProperty("xmpp.socket.ssl.port",
                    SSLSocketAcceptThread.DEFAULT_PORT);
            String interfaceName = JiveGlobals.getProperty("xmpp.socket.ssl.interface");
            String algorithm = JiveGlobals.getProperty("xmpp.socket.ssl.algorithm");
            if ("".equals(algorithm) || algorithm == null) {
                algorithm = "TLS";
            }
            ServerPort serverPort = new ServerPort(port, interfaceName, serverName, localIPAddress,
                    true, algorithm, ServerPort.Type.client);
            try {
                sslSocketThread = new SSLSocketAcceptThread(this, serverPort);
                ports.add(serverPort);
                sslSocketThread.setDaemon(true);
                sslSocketThread.start();

                List params = new ArrayList();
                params.add(Integer.toString(sslSocketThread.getPort()));
                Log.info(LocaleUtils.getLocalizedString("startup.ssl", params));
            }
            catch (Exception e) {
                System.err.println("Error starting SSL XMPP listener on port " + port + ": " +
                        e.getMessage());
                Log.error(LocaleUtils.getLocalizedString("admin.error.ssl"), e);
            }
        }
    }

    public Iterator<ServerPort> getPorts() {
        return ports.iterator();
    }

    public void addSocket(Socket sock, boolean isSecure, ServerPort serverPort)  {
        try {
            // the order of these calls is critical (stupid huh?)
            SocketConnection conn = new SocketConnection(deliverer, sock, isSecure);
            SocketReader reader = null;
            String threadName = null;
            if (serverPort.isClientPort()) {
                reader = new ClientSocketReader(router, serverName, sock, conn);
                threadName = "Client SR";
            }
            else if (serverPort.isComponentPort()) {
                reader = new ComponentSocketReader(router, serverName, sock, conn);
                threadName = "Component SR";
            }
            else {
                reader = new ServerSocketReader(router, serverName, sock, conn);
                threadName = "Server SR";
            }
            Thread thread = new Thread(reader, threadName);
            thread.setDaemon(true);
            thread.start();
        }
        catch (IOException e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
        }
    }

    public void initialize(XMPPServer server) {
        super.initialize(server);
        this.server = server;
        router = server.getPacketRouter();
        deliverer = server.getPacketDeliverer();
        sessionManager = server.getSessionManager();
    }

    // Used to know if the sockets can be started (the connection manager has been started)
    private boolean isStarted = false;
    // Used to know if the sockets have been started
    private boolean isSocketStarted = false;

    // #####################################################################
    // Module management
    // #####################################################################

    public void start() {
        super.start();
        isStarted = true;
        serverName = server.getServerInfo().getName();
        createSocket();
    }

    public void stop() {
        super.stop();
        if (socketThread != null) {
            socketThread.shutdown();
            socketThread = null;
        }
        if (sslSocketThread != null) {
            sslSocketThread.shutdown();
            sslSocketThread = null;
        }
        if (componentSocketThread != null) {
            componentSocketThread.shutdown();
            componentSocketThread = null;
        }
        if (serverSocketThread != null) {
            serverSocketThread.shutdown();
            serverSocketThread = null;
        }
        serverName = null;
    }
}
