/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.sdk.server;

import java.util.concurrent.CopyOnWriteArrayList;

import com.google.common.collect.Lists;

/**
 * A {@link Lifecycle} instance that manages the startup and shutdown of a collection of sub-Lifecycles.
 * <p>
 * Registered Lifecycles are started when this {@link LifecycleManager} is started and shutdown when this
 * LifecycleManager is shutdown.
 * <p>
 * The order that sub-Lifecycles are shutdown can be controlled with the optional {@link ShutdownOrder} parameter
 * provided in {@link LifecycleManager#LifecycleManager(ShutdownOrder)}.
 */
public final class LifecycleManager extends AbstractLifecycle {

    private final CopyOnWriteArrayList<Lifecycle> lifecycles = new CopyOnWriteArrayList<>();
    private final ShutdownOrder shutdownOrder;

    /**
     * Create a {@link LifecycleManager} that shuts down sub-{@link Lifecycle}s in {@link ShutdownOrder#LINEAR} order.
     */
    public LifecycleManager() {
        this(ShutdownOrder.LINEAR);
    }

    /**
     * Create a {@link LifecycleManager} that shuts down sub-{@link Lifecycle}s in the specified {@link ShutdownOrder}.
     *
     * @param shutdownOrder the {@link ShutdownOrder} to shutdown sub-Lifecycles in.
     */
    public LifecycleManager(ShutdownOrder shutdownOrder) {
        this.shutdownOrder = shutdownOrder;
    }

    @Override
    protected void onStartup() {
        lifecycles.forEach(Lifecycle::startup);
    }

    @Override
    protected void onShutdown() {
        if (shutdownOrder == ShutdownOrder.LINEAR) {
            lifecycles.forEach(Lifecycle::shutdown);
        } else {
            Lists.reverse(lifecycles).forEach(Lifecycle::shutdown);
        }
    }

    /**
     * Add a {@link Lifecycle} to be managed.
     * <p>
     * This Lifecycle will have {@link Lifecycle#startup()} called when this manager is started and
     * {@link Lifecycle#shutdown()} called when this manager is shutdown.
     *
     * @param lifecycle the {@link Lifecycle} to add.
     */
    public void addLifecycle(Lifecycle lifecycle) {
        lifecycles.add(lifecycle);
    }

    /**
     * Remove a previously added {@link Lifecycle}.
     *
     * @param lifecycle the {@link Lifecycle} to remove.
     */
    public void removeLifecycle(Lifecycle lifecycle) {
        lifecycles.remove(lifecycle);
    }

    /**
     * Add logic to run at startup.
     * <p>
     * This is a convenience method for {@link #addLifecycle(Lifecycle)} where {@link Lifecycle#shutdown()} is implied
     * to be a no-op.
     *
     * @param startupTask logic to run at startup.
     * @return a {@link Lifecycle} instance that can be used with {@link #removeLifecycle(Lifecycle)} if needed.
     */
    public Lifecycle addStartupTask(Runnable startupTask) {
        Lifecycle lifecycle = new Lifecycle() {
            @Override
            public void startup() {
                startupTask.run();
            }

            @Override
            public void shutdown() {}
        };

        addLifecycle(lifecycle);

        return lifecycle;
    }

    /**
     * Add logic to run at shutdown.
     * <p>
     * This is a convenience method for {@link #addLifecycle(Lifecycle)} where {@link Lifecycle#startup()} is implied
     * to be a no-op.
     *
     * @param shutdownTask logic to run at shutdown.
     * @return a {@link Lifecycle} instance that can be used with {@link #removeLifecycle(Lifecycle)} if needed.
     */
    public Lifecycle addShutdownTask(Runnable shutdownTask) {
        Lifecycle lifecycle = new Lifecycle() {
            @Override
            public void startup() {}

            @Override
            public void shutdown() {
                shutdownTask.run();
            }
        };

        addLifecycle(lifecycle);

        return lifecycle;
    }

    /**
     * Modes that control the order in which registered {@link Lifecycle}s are shutdown.
     */
    public enum ShutdownOrder {
        /**
         * Lifecycles are shutdown in the same order they were added.
         */
        LINEAR,

        /**
         * Lifecycles are shutdown in the reverse order they were added.
         */
        INVERSE
    }

}
