/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.openejb.server;

import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.monitoring.LocalMBeanServer;
import org.apache.openejb.monitoring.ManagedMBean;
import org.apache.openejb.monitoring.ObjectNameBuilder;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.xbean.finder.ResourceFinder;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @version $Rev$ $Date$
 * @org.apache.xbean.XBean element="simpleServiceManager"
 */
public class SimpleServiceManager extends ServiceManager {

    private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_SERVER, SimpleServiceManager.class);

    private static ObjectName objectName = null;

    private ServerService[] daemons;
    private volatile boolean stopped = false;
    private final ServiceFinder serviceFinder;

    public SimpleServiceManager() {
        this(new SimpleServiceFinder("META-INF/"));
    }

    public SimpleServiceManager(final ServiceFinder serviceFinder) {
        this.serviceFinder = serviceFinder;
    }

    // Have properties files (like xinet.d) that specifies what daemons to
    // Look into the xinet.d file structure again
    // conf/server.d/
    //    admin.properties
    //    ejbd.properties
    //    webadmin.properties
    //    telnet.properties
    //    corba.properties
    //    soap.properties
    //    xmlrpc.properties
    //    httpejb.properties
    //    webejb.properties
    //    xmlejb.properties
    // Each contains the class name of the daemon implamentation
    // The port to use
    // whether it's turned on
    // May be reusable elsewhere, move if another use occurs
    public static class SimpleServiceFinder implements ServiceFinder {

        private final ResourceFinder resourceFinder;
        private final ClassLoader classLoader;

        public SimpleServiceFinder(final String basePath) {
            this(basePath, Thread.currentThread().getContextClassLoader());
        }

        public SimpleServiceFinder(final String basePath, final ClassLoader classLoader) {
            this.resourceFinder = new ResourceFinder(basePath, classLoader);
            this.classLoader = classLoader;
        }

        @Override
        public Map<String, Properties> mapAvailableServices(final Class interfase) throws IOException, ClassNotFoundException {
            final Map<String, Properties> service = resourceFinder.mapAvailableProperties(ServerService.class.getName());

            for (final Map.Entry<String, Properties> entry : service.entrySet()) {

                final String name = entry.getKey();
                final Properties properties = entry.getValue();

                String className = properties.getProperty("className");
                if (className == null) {
                    className = properties.getProperty("classname");
                    if (className == null) {
                        className = properties.getProperty("server");
                    }
                }

                final Class impl = classLoader.loadClass(className);

                properties.put(interfase, impl);
                final String rawProperties = resourceFinder.findString(interfase.getName() + "/" + name);
                properties.put(Properties.class, rawProperties);

            }
            return service;
        }
    }

    private static ObjectName getDiscoveryRegistryObjectName() {
        if (null == objectName) {
            final ObjectNameBuilder jmxName = new ObjectNameBuilder("openejb");
            jmxName.set("type", "Server");
            jmxName.set("name", "DiscoveryRegistry");
            objectName = jmxName.build();
        }
        return objectName;
    }

    @Override
    public void init() throws Exception {
        try {
            ServiceLogger.MDCput("SERVER", "main");
            final InetAddress localhost = InetAddress.getLocalHost();
            ServiceLogger.MDCput("HOST", localhost.getHostName());
        } catch (Throwable e) {
            //Ignore
        }

        final DiscoveryRegistry registry = new DiscoveryRegistry();

        // register the mbean
        try {
            LocalMBeanServer.get().registerMBean(new ManagedMBean(registry), getDiscoveryRegistryObjectName());
        } catch (Throwable e) {
            logger.error("Failed to register 'openejb' MBean", e);
        }

        SystemInstance.get().setComponent(DiscoveryRegistry.class, registry);

        final Map<String, Properties> availableServices = this.serviceFinder.mapAvailableServices(ServerService.class);

        final List<ServerService> enabledServers = initServers(availableServices);

        daemons = enabledServers.toArray(new ServerService[enabledServers.size()]);
        stopped = false;
    }

    @Override
    public synchronized void start(final boolean block) throws ServiceException {

        if (stopped) {
            throw new ServiceException("Stop has already been called on ServiceManager");
        }

        final boolean display = SystemInstance.get().getOptions().get("openejb.nobanner", (String) null) == null;

        // starting then displaying to get a more relevant log

        final Exception[] errors = new Exception[daemons.length];
        for (int i = 0; i < daemons.length; i++) {
            final ServerService d = daemons[i];

            try {
                d.start();
                errors[i] = null;
            } catch (Exception e) {
                errors[i] = e;
                LOGGER.info("Can't start service " + d.getName(), e);
            }
        }

        if (display) {
            LOGGER.info("  ** Bound Services **");
            printRow("NAME", "IP", "PORT");
        }
        for (int i = 0; i < daemons.length; i++) {
            final ServerService d = daemons[i];
            if (errors[i] == null) {
                if (display && d.getPort() != -1) {
                    printRow(d.getName(), d.getIP(), d.getPort() + "");
                }
            } else {
                logger.fatal("Service Start Failed: " + d.getName() + " " + d.getIP() + " " + d.getPort() + ": " + errors[i].getMessage());
                if (display) {
                    printRow(d.getName(), "----", "FAILED");
                }
            }
        }

        if (display) {
            LOGGER.info("-------");
            LOGGER.info("Ready!");
        }

        if (!block) {
            return;
        }

        /*
         * This will cause the user thread (the thread that keeps the
         *  vm alive) to go into a state of constant waiting.
         *  Each time the thread is woken up, it checks to see if
         *  it should continue waiting.
         *
         *  To stop the thread (and the VM), just call the stop method
         *  which will set 'stop' to true and notify the user thread.
         */
        try {
            while (!stopped) {

                this.wait(Long.MAX_VALUE);
            }
        } catch (Throwable t) {
            logger.fatal("Unable to keep the server thread alive. Received exception: " + t.getClass().getName() + " : " + t.getMessage());
        }
        logger.info("Stopping Remote Server");
    }

    @Override
    public synchronized void stop() throws ServiceException {
        logger.info("Stopping server services");
        stopped = true;

        final ServerService[] services = daemons.clone();

        final MBeanServer server = LocalMBeanServer.get();
        for (final ServerService service : services) {
            if (LocalMBeanServer.isJMXActive()) {
                final ObjectName on = getObjectName(service.getName());
                if (server.isRegistered(on)) {
                    try {
                        server.unregisterMBean(on);
                    } catch (Exception ignored) {
                        // no-op
                    }
                }
            }

            try {
                service.stop();
            } catch (ServiceException e) {
                logger.fatal("Service Shutdown Failed: " + service.getName() + ".  Exception: " + e.getMessage(), e);
            }
        }

        // De-register the mbean
        try {
            final ObjectName objectName = getDiscoveryRegistryObjectName();

            if (server.isRegistered(objectName)) {
                server.unregisterMBean(objectName);
            }

        } catch (Throwable e) {
            logger.warning("Failed to de-register the 'openejb' mbean", e);
        }

        setServiceManager(null);
        notifyAll();
    }

    private void printRow(String col1, String col2, String col3) {

        col1 += "                    ";
        col1 = col1.substring(0, 20);

        col2 += "                    ";
        col2 = col2.substring(0, 15);

        col3 += "                    ";
        col3 = col3.substring(0, 6);

        final String sb = "  " + col1 + " " + col2 + " " + col3;

        LOGGER.info(sb);
    }

    public ServerService[] getDaemons() {
        return daemons;
    }
}
