/*
 * Copyright (c) 2013, 2017 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.tyrus.core;

import java.lang.reflect.Method;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.websocket.WebSocketContainer;

/**
 * Base WebSocket container.
 * <p>
 * Client and Server containers extend this to provide additional functionality.
 *
 * @author Jitendra Kotamraju
 */
public abstract class BaseContainer extends ExecutorServiceProvider implements WebSocketContainer {

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

    private final ExecutorService managedExecutorService;
    private final ScheduledExecutorService managedScheduledExecutorService;
    private final ThreadFactory threadFactory;
    /**
     * This lock ensures that only one instance of each type of executors will be created and it also prevents a
     * situation
     * when a client is given an executor that is just about to be shut down.
     */
    private final Object EXECUTORS_CLEAN_UP_LOCK = new Object();

    private volatile ExecutorService executorService = null;
    private volatile ScheduledExecutorService scheduledExecutorService = null;

    public BaseContainer() {
        this.managedExecutorService = lookupManagedExecutorService();
        this.managedScheduledExecutorService = lookupManagedScheduledExecutorService();

        if (managedExecutorService == null || managedScheduledExecutorService == null) {
            // at least one of the managed executor services is null, a local one will be created instead
            threadFactory = new DaemonThreadFactory();
        } else {
            // only managed executor services will be used, the thread factory won't be needed.
            threadFactory = null;
        }
    }

    /**
     * Returns a container-managed {@link java.util.concurrent.ExecutorService} registered under
     * {@code java:comp/DefaultManagedExecutorService} or if the lookup has failed, it returns a
     * {@link java.util.concurrent.ExecutorService} created and managed by this instance of
     * {@link org.glassfish.tyrus.core.BaseContainer}.
     *
     * @return executor service.
     */
    @Override
    public ExecutorService getExecutorService() {
        if (managedExecutorService != null) {
            return managedExecutorService;
        }

        if (executorService == null) {
            synchronized (EXECUTORS_CLEAN_UP_LOCK) {
                if (executorService == null) {
                    executorService = Executors.newCachedThreadPool(threadFactory);
                }
            }
        }

        return executorService;
    }

    /**
     * Returns a container-managed {@link java.util.concurrent.ScheduledExecutorService} registered under
     * {@code java:comp/DefaultManagedScheduledExecutorService} or if the lookup has failed it returns a
     * {@link java.util.concurrent.ScheduledExecutorService} created and managed by this instance of
     * {@link org.glassfish.tyrus.core.BaseContainer}.
     *
     * @return scheduled executor service.
     */
    @Override
    public ScheduledExecutorService getScheduledExecutorService() {
        if (managedScheduledExecutorService != null) {
            return managedScheduledExecutorService;
        }

        if (scheduledExecutorService == null) {
            synchronized (EXECUTORS_CLEAN_UP_LOCK) {
                if (scheduledExecutorService == null) {
                    scheduledExecutorService = Executors.newScheduledThreadPool(10, threadFactory);
                }
            }
        }

        return scheduledExecutorService;
    }

    /**
     * Release executor services managed by this instance. Executor services obtained via JNDI lookup won't be
     * shut down.
     */
    public void shutdown() {
        if (executorService != null) {
            executorService.shutdown();
            executorService = null;
        }

        if (scheduledExecutorService != null) {
            scheduledExecutorService.shutdownNow();
            scheduledExecutorService = null;
        }
    }

    /**
     * Release executor services managed by this instance if the condition passed in the parameter is fulfilled.
     * Executor services obtained via JNDI lookup won't be shut down.
     *
     * @param shutDownCondition condition that will be evaluated before executor services are released and they will be
     *                          released only if the condition is evaluated to {@code true}. The condition will be
     *                          evaluated in a synchronized block in order to make the process of its evaluation
     *                          and executor services release an atomic operation.
     */
    protected void shutdown(ShutDownCondition shutDownCondition) {
        synchronized (EXECUTORS_CLEAN_UP_LOCK) {
            if (shutDownCondition.evaluate()) {
                shutdown();
            }
        }
    }

    private ExecutorService lookupManagedExecutorService() {
        // Get the default ManagedExecutorService, if available
        try {
            // TYRUS-256: Tyrus client on Android
            final Class<?> aClass = Class.forName("javax.naming.InitialContext");
            final Object o = aClass.newInstance();

            final Method lookupMethod = aClass.getMethod("lookup", String.class);
            return (ExecutorService) lookupMethod.invoke(o, "java:comp/DefaultManagedExecutorService");
        } catch (Exception e) {
            // ignore
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
            }
        } catch (LinkageError error) {
            // ignore - JDK8 compact2 profile - http://openjdk.java.net/jeps/161
        }

        return null;
    }

    private ScheduledExecutorService lookupManagedScheduledExecutorService() {
        try {
            // TYRUS-256: Tyrus client on Android
            final Class<?> aClass = Class.forName("javax.naming.InitialContext");
            final Object o = aClass.newInstance();

            final Method lookupMethod = aClass.getMethod("lookup", String.class);
            return (ScheduledExecutorService) lookupMethod
                    .invoke(o, "java:comp/DefaultManagedScheduledExecutorService");
        } catch (Exception e) {
            // ignore
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getMessage(), e);
            }
        } catch (LinkageError error) {
            // ignore - JDK8 compact2 profile - http://openjdk.java.net/jeps/161
        }

        return null;
    }

    private static class DaemonThreadFactory implements ThreadFactory {
        static final AtomicInteger poolNumber = new AtomicInteger(1);
        final AtomicInteger threadNumber = new AtomicInteger(1);
        final String namePrefix;

        DaemonThreadFactory() {
            namePrefix = "tyrus-" + poolNumber.getAndIncrement() + "-thread-";
        }

        @Override
        public Thread newThread(@SuppressWarnings("NullableProblems") Runnable r) {
            Thread t = new Thread(null, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (!t.isDaemon()) {
                t.setDaemon(true);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

    protected static interface ShutDownCondition {

        boolean evaluate();
    }
}
