/*
 * Copyright (c) 2021 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.api;

import org.eclipse.milo.opcua.sdk.server.Lifecycle;
import org.eclipse.milo.opcua.sdk.server.LifecycleManager;
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
import org.eclipse.milo.opcua.sdk.server.UaNodeManager;

/**
 * A {@link ManagedAddressSpace} fragment that has a {@link Lifecycle} associated with it.
 * <p>
 * This fragment will be registered with the provided composite and its {@link UaNodeManager} will
 * be registered with the server on startup and unregistered on shutdown.
 * <p>
 * Subclasses can register additional startup/shutdown tasks with the {@link LifecycleManager}
 * obtained from {@link #getLifecycleManager()}.
 */
public abstract class ManagedAddressSpaceFragmentWithLifecycle
    extends ManagedAddressSpaceFragment implements Lifecycle {

    private final LifecycleManager lifecycleManager = new LifecycleManager();

    private final AddressSpaceComposite composite;

    /**
     * Create a managed fragment using the server's {@link AddressSpaceManager} as the composite.
     *
     * @param server the {@link OpcUaServer} instance.
     */
    public ManagedAddressSpaceFragmentWithLifecycle(OpcUaServer server) {
        this(server, server.getAddressSpaceManager());
    }

    /**
     * Create a managed fragment using {@code composite}.
     *
     * @param server    the {@link OpcUaServer} instance.
     * @param composite the {@link AddressSpaceComposite} this fragment is part of.
     */
    public ManagedAddressSpaceFragmentWithLifecycle(OpcUaServer server, AddressSpaceComposite composite) {
        super(server);

        this.composite = composite;

        getLifecycleManager().addLifecycle(new Lifecycle() {
            @Override
            public void startup() {
                registerWithComposite(composite);
                registerNodeManager(getNodeManager());
            }

            @Override
            public void shutdown() {
                composite.unregister(ManagedAddressSpaceFragmentWithLifecycle.this);
                unregisterNodeManager(getNodeManager());
            }
        });
    }

    /**
     * Create a managed fragment using the server's {@link AddressSpaceManager} as the composite.
     *
     * @param server      the {@link OpcUaServer} instance.
     * @param nodeManager the {@link UaNodeManager} to manage nodes with.
     */
    public ManagedAddressSpaceFragmentWithLifecycle(OpcUaServer server, UaNodeManager nodeManager) {
        this(server, nodeManager, server.getAddressSpaceManager());
    }

    /**
     * Create a managed fragment using {@code composite}.
     *
     * @param server      the {@link OpcUaServer} instance.
     * @param nodeManager the {@link UaNodeManager} to manage nodes with.
     * @param composite   the {@link AddressSpaceComposite} this fragment is part of.
     */
    public ManagedAddressSpaceFragmentWithLifecycle(
        OpcUaServer server,
        UaNodeManager nodeManager,
        AddressSpaceComposite composite
    ) {

        super(server, nodeManager);

        this.composite = composite;

        getLifecycleManager().addLifecycle(new Lifecycle() {
            @Override
            public void startup() {
                registerWithComposite(composite);
                registerNodeManager(getNodeManager());
            }

            @Override
            public void shutdown() {
                composite.unregister(ManagedAddressSpaceFragmentWithLifecycle.this);
                unregisterNodeManager(getNodeManager());
            }
        });
    }

    @Override
    public final void startup() {
        lifecycleManager.startup();
    }

    @Override
    public final void shutdown() {
        lifecycleManager.shutdown();
    }

    /**
     * Get the {@link AddressSpaceComposite} this fragment registers with.
     *
     * @return the {@link AddressSpaceComposite} this fragment registers with.
     */
    public AddressSpaceComposite getComposite() {
        return composite;
    }

    /**
     * Get the {@link LifecycleManager} for this {@link ManagedAddressSpaceFragmentWithLifecycle}.
     *
     * @return the {@link LifecycleManager} for this {@link ManagedAddressSpaceFragmentWithLifecycle}.
     */
    protected LifecycleManager getLifecycleManager() {
        return lifecycleManager;
    }

    /**
     * Register this {@link AddressSpaceFragment} with {@code composite}.
     * <p>
     * Can be overridden if e.g. this fragment should use
     * {@link AddressSpaceComposite#registerFirst(AddressSpaceFragment)} instead of
     * {@link AddressSpaceComposite#register(AddressSpaceFragment)}.
     *
     * @param composite the {@link AddressSpaceComposite} to register with.
     */
    protected void registerWithComposite(AddressSpaceComposite composite) {
        composite.register(ManagedAddressSpaceFragmentWithLifecycle.this);
    }

    /**
     * Register this AddressSpace's {@link UaNodeManager} with its managing entity.
     * <p>
     * The default implementation registers it with the server's {@link AddressSpaceManager}.
     *
     * @param nodeManager the {@link UaNodeManager} to register.
     */
    protected void registerNodeManager(UaNodeManager nodeManager) {
        getServer().getAddressSpaceManager().register(nodeManager);
    }

    /**
     * Unregister this AddressSpace's {@link UaNodeManager} with its managing entity.
     * <p>
     * The default implementation unregisters it with the server's {@link AddressSpaceManager}.
     *
     * @param nodeManager the {@link UaNodeManager} to unregister.
     */
    protected void unregisterNodeManager(UaNodeManager nodeManager) {
        getServer().getAddressSpaceManager().unregister(nodeManager);
    }

}
