/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.wildfly.extension.messaging.activemq;

import static org.jboss.as.controller.SimpleAttributeDefinitionBuilder.create;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.START;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STOP;
import static org.wildfly.extension.messaging.activemq.CommonAttributes.NAME;
import static org.jboss.dmr.ModelType.BOOLEAN;
import static org.wildfly.extension.messaging.activemq._private.MessagingLogger.ROOT_LOGGER;

import org.apache.activemq.artemis.api.core.management.ActiveMQComponentControl;
import org.jboss.as.controller.AbstractRuntimeOnlyHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationDefinition;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.descriptions.ResourceDescriptionResolver;
import org.jboss.as.controller.operations.validation.ParametersValidator;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.wildfly.extension.messaging.activemq._private.MessagingLogger;

/**
 * Base class for {@link org.jboss.as.controller.OperationStepHandler} implementations for handlers that interact
 * with an implementation of a {@link ActiveMQComponentControl} subinterface to perform their function.  This base class
 * handles a "start" and "stop" operation as well as a "read-attribute" call reading runtime attribute "started".
 *
 * @author Brian Stansberry (c) 2011 Red Hat Inc.
 */
public abstract class AbstractActiveMQComponentControlHandler<T extends ActiveMQComponentControl> extends AbstractRuntimeOnlyHandler {

    private static final SimpleAttributeDefinition STARTED = create(CommonAttributes.STARTED, BOOLEAN)
            .setFlags(AttributeAccess.Flag.STORAGE_RUNTIME)
            .build();

    private ParametersValidator readAttributeValidator = new ParametersValidator();

    protected AbstractActiveMQComponentControlHandler() {
        readAttributeValidator.registerValidator(NAME, new StringLengthValidator(1));
    }

    @Override
    protected void executeRuntimeStep(OperationContext context, ModelNode operation) throws OperationFailedException {

        final String operationName = operation.require(OP).asString();

        if (READ_ATTRIBUTE_OPERATION.equals(operationName)) {
            if (ActiveMQActivationService.ignoreOperationIfServerNotActive(context, operation)) {
                return;
            }
            readAttributeValidator.validate(operation);
            final String name = operation.require(NAME).asString();
            if (STARTED.getName().equals(name)) {
                ActiveMQComponentControl control = getActiveMQComponentControl(context, operation, false);
                if(control != null) {
                    context.getResult().set(control.isStarted());
                }
            } else {
                handleReadAttribute(name, context, operation);
            }
            return;
        }

        if (ActiveMQActivationService.rollbackOperationIfServerNotActive(context, operation)) {
            return;
        }

        ActiveMQComponentControl control = null;
        boolean appliedToRuntime = false;
        Object handback = null;
        if (START.equals(operationName)) {
            control = getActiveMQComponentControl(context, operation, true);
            try {
                control.start();
                appliedToRuntime = true;
                context.getResult();
            } catch (Exception e) {
                context.getFailureDescription().set(e.getLocalizedMessage());
            }

        } else if (STOP.equals(operationName)) {
            control = getActiveMQComponentControl(context, operation, true);
            try {
                control.stop();
                appliedToRuntime = true;
                context.getResult();
            } catch (Exception e) {
                context.getFailureDescription().set(e.getLocalizedMessage());
            }
        } else {
            handback = handleOperation(operationName, context, operation);
            appliedToRuntime = handback != null;
        }


        OperationContext.RollbackHandler rh;

        if (appliedToRuntime)  {
            final ActiveMQComponentControl rhControl = control;
            final Object rhHandback = handback;
            rh = new OperationContext.RollbackHandler() {
                @Override
                public void handleRollback(OperationContext context, ModelNode operation) {
                    try {
                        if (START.equals(operationName)) {
                            rhControl.stop();
                        } else if (STOP.equals(operationName)) {
                            rhControl.start();
                        } else {
                            handleRevertOperation(operationName, context, operation, rhHandback);
                        }
                    } catch (Exception e) {
                        ROOT_LOGGER.revertOperationFailed(e, getClass().getSimpleName(),
                                operation.require(ModelDescriptionConstants.OP).asString(),
                                PathAddress.pathAddress(operation.require(ModelDescriptionConstants.OP_ADDR)));
                    }
                }
            };
        } else {
            rh = OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER;
        }

        context.completeStep(rh);
    }

    public void registerAttributes(final ManagementResourceRegistration registry) {
        registry.registerReadOnlyAttribute(STARTED, this);
    }

    public void registerOperations(final ManagementResourceRegistration registry, final ResourceDescriptionResolver resolver) {
        final OperationDefinition startOp = new SimpleOperationDefinitionBuilder(START, resolver)
                .setRuntimeOnly()
                .build();
        registry.registerOperationHandler(startOp, this);
        final OperationDefinition stopOp = new SimpleOperationDefinitionBuilder(STOP, resolver)
                .setRuntimeOnly()
                .build();
        registry.registerOperationHandler(stopOp, this);
    }

    /**
     * Gets the {@link ActiveMQComponentControl} implementation used by this handler.
     *
     * @param activeMQBroker the ActiveMQ server installed in the runtime
     * @param address the address being invoked
     * @return the runtime ActiveMQ control object associated with the given address
     */
    protected abstract T getActiveMQComponentControl(ActiveMQBroker activeMQBroker, PathAddress address);

    protected abstract String getDescriptionPrefix();

    /**
     * Hook to allow subclasses to handle read-attribute requests for attributes other than {@link CommonAttributes#STARTED}.
     * Implementations must not call any of the
     * {@link OperationContext#completeStep(OperationContext.ResultHandler) context.completeStep variants}.
     * <p>
     * This default implementation just throws the exception returned by {@link #unsupportedAttribute(String)}.
     * </p>
     *
     *
     * @param attributeName the name of the attribute
     * @param context the operation context
     * @param operation
     * @throws OperationFailedException
     */
    protected void handleReadAttribute(String attributeName, OperationContext context, ModelNode operation) throws OperationFailedException {
        unsupportedAttribute(attributeName);
    }

    /**
     * Hook to allow subclasses to handle operations other than {@code read-attribute}, {@code start} and
     * {@code stop}. Implementations must not call any of the
     * {@link OperationContext#completeStep(OperationContext.ResultHandler) context.completeStep variants}.
     * <p>
     * This default implementation just throws the exception returned by {@link #unsupportedOperation(String)}.
     * </p>
     *
     *
     * @param operationName the name of the operation
     * @param context the operation context
     * @param operation the operation
     *
     * @return an object that can be passed back in {@link #handleRevertOperation(String, OperationContext, ModelNode, Object)}
     *         if the operation should be reverted. A value of {@code null} is an indication that no reversible
     *         modification was made
     * @throws OperationFailedException
     */
    protected Object handleOperation(String operationName, OperationContext context, ModelNode operation) throws OperationFailedException {
        unsupportedOperation(operationName);
        throw MessagingLogger.ROOT_LOGGER.unsupportedOperation(operationName);
    }

    /**
     * Hook to allow subclasses to handle revert changes made in
     * {@link #handleOperation(String, OperationContext, ModelNode)}.
     * <p>
     * This default implementation does nothing.
     * </p>
     *
     *
     * @param operationName the name of the operation
     * @param context the operation context
     * @param operation the operation
     */
    protected void handleRevertOperation(String operationName, OperationContext context, ModelNode operation, Object handback) {
    }

    /**
     * Return an ISE with a message saying support for the attribute was not properly implemented. This handler should
     * only be called if for a "read-attribute" operation if {@link #registerOperations(ManagementResourceRegistration, ResourceDescriptionResolver)}
     * registers the attribute, so a handler then not recognizing the attribute name would be a bug and this method
     * returns an exception highlighting that bug.
     *
     * @param attributeName the name of the attribute
     * @throws IllegalStateException an exception with a message indicating a bug in this handler
     */
    protected final void unsupportedAttribute(final String attributeName) {
        // Bug
        throw MessagingLogger.ROOT_LOGGER.unsupportedAttribute(attributeName);
    }

    /**
     * Return an ISE with a message saying support for the operation was not properly implemented. This handler should
     * only be called if for a n operation if {@link #registerOperations(ManagementResourceRegistration, ResourceDescriptionResolver)}
     * registers it as a handler, so a handler then not recognizing the operation name would be a bug and this method
     * returns an exception highlighting that bug.
     *
     * @param operationName the name of the attribute
     * @throws IllegalStateException an exception with a message indicating a bug in this handler
     */
    protected final void unsupportedOperation(final String operationName) {
        // Bug
        throw MessagingLogger.ROOT_LOGGER.unsupportedOperation(operationName);
    }

    /**
     * Gets the runtime ActiveMQ control object that can help service this request.
     *
     * @param context  the operation context
     * @param operation the operation
     * @param forWrite {@code true} if this operation will modify the runtime; {@code false} if not.
     * @return the control object
     * @throws OperationFailedException
     */
    protected final T getActiveMQComponentControl(final OperationContext context, final ModelNode operation, final boolean forWrite) throws OperationFailedException {
        final ServiceName artemisServiceName = MessagingServices.getActiveMQServiceName(PathAddress.pathAddress(operation.get(ModelDescriptionConstants.OP_ADDR)));
        ServiceController<?> artemisService = context.getServiceRegistry(forWrite).getService(artemisServiceName);
        ActiveMQBroker server = ActiveMQBroker.class.cast(artemisService.getValue());
        PathAddress address = PathAddress.pathAddress(operation.require(OP_ADDR));
        return getActiveMQComponentControl(server, address);
    }
}
