/*
 * 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.atomic.AtomicReference;

public abstract class AbstractLifecycle implements Lifecycle {

    private final AtomicReference<LifecycleState> state = new AtomicReference<>(LifecycleState.NEW);

    /**
     * {@inheritDoc}
     * <p>
     * Subsequent invocations throw {@link IllegalStateException}.
     *
     * @throws IllegalStateException on subsequent invocations.
     */
    @Override
    public final void startup() throws IllegalStateException {
        LifecycleState previous =
            state.getAndUpdate(prev -> prev == LifecycleState.NEW ? LifecycleState.RUNNING : prev);

        if (previous == LifecycleState.NEW) {
            this.onStartup();
        } else {
            throw new IllegalStateException("cannot call startup when state=" + previous);
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * If {@link #startup()} hasn't been called yet {@link IllegalStateException} is thrown.
     * <p>
     * Subsequent invocations have no effect.
     *
     * @throws IllegalStateException if not started yet.
     */
    @Override
    public final void shutdown() throws IllegalStateException {
        LifecycleState previous =
            state.getAndUpdate(prev -> prev == LifecycleState.RUNNING ? LifecycleState.STOPPED : prev);

        if (previous == LifecycleState.RUNNING) {
            this.onShutdown();
        } else if (previous == LifecycleState.NEW) {
            throw new IllegalStateException("Cannot call shutdown, never started.");
        }
    }

    /**
     * True if this lifecycle is running, i.e. after {@link #startup()} is called and before {@link #shutdown()} is
     * called.
     *
     * @return {@code true} if this lifecycle is running.
     */
    public final boolean isRunning() {
        return this.state.get() == LifecycleState.RUNNING;
    }

    /**
     * True if this lifecycle is <b>not</b> running, i.e. before {@link #startup()} is called or after
     * {@link #shutdown()}} is called.
     *
     * @return {@code true} if this lifecycle is <b>not</b> running.
     */
    public final boolean isNotRunning() {
        return !isRunning();
    }

    /**
     * Execute startup logic, if there is any.
     */
    protected abstract void onStartup();

    /**
     * Execute shutdown logic, if there is any.
     */
    protected abstract void onShutdown();

    private enum LifecycleState {
        NEW, RUNNING, STOPPED
    }

}
