/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.wildfly.extension.requestcontroller;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.jboss.as.server.logging.ServerLogger;
import org.jboss.as.server.suspend.ServerActivityCallback;
import java.util.concurrent.Executor;

/**
 * A representation of an entry point into the application server, represented as both a deployment
 * name and an entry point name.
 * <p/>
 * This should only be created using the top level name, as it does not generally make sense to shut
 * down individual parts of a deployment.
 * <p/>
 * Note that requests are tracked at two levels, both at the entry point level and the request controller level.
 * This allows for individual deployments/interfaces to be gracefully suspended, and also allows for the global
 * request controller to limit the total number of active requests.
 *
 * @author Stuart Douglas
 */
public class ControlPoint {

    private static final AtomicIntegerFieldUpdater<ControlPoint> activeRequestCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ControlPoint.class, "activeRequestCount");
    private static final AtomicReferenceFieldUpdater<ControlPoint, ServerActivityCallback> listenerUpdater = AtomicReferenceFieldUpdater.newUpdater(ControlPoint.class, ServerActivityCallback.class, "listener");

    private final RequestController controller;
    private final String deployment;
    private final String entryPoint;
    private final boolean trackIndividualControlPoints;

    /**
     * The number of active requests that are using this entry point
     */
    @SuppressWarnings("unused")
    private volatile int activeRequestCount = 0;

    /**
     * If this entry point is paused
     */
    private volatile boolean paused = false;

    @SuppressWarnings("unused")
    private volatile ServerActivityCallback listener = null;

    /**
     * The number of services that are using this entry point.
     * This is a deployment time measurement, not a runtime one
     */
    private int referenceCount = 0;

    ControlPoint(RequestController controller, String deployment, String entryPoint, boolean trackIndividualControlPoints) {
        this.controller = controller;
        this.deployment = deployment;
        this.entryPoint = entryPoint;
        this.trackIndividualControlPoints = trackIndividualControlPoints;
    }

    public String getEntryPoint() {
        return entryPoint;
    }

    public String getDeployment() {
        return deployment;
    }

    /**
     * Pause the current entry point, and invoke the provided listener when all current requests have finished.
     *
     * If individual control point tracking is not enabled then the listener will be invoked straight away
     *
     * @param requestCountListener The listener to invoke
     */
    public void pause(ServerActivityCallback requestCountListener) {
        if (paused) {
            throw ServerLogger.ROOT_LOGGER.serverAlreadyPaused();
        }
        this.paused = true;
        listenerUpdater.set(this, requestCountListener);
        if (activeRequestCountUpdater.get(this) == 0) {
            if (listenerUpdater.compareAndSet(this, requestCountListener, null)) {
                requestCountListener.done();
            }
        }
    }

    /**
     * Cancel the pause operation
     */
    public void resume() {
        this.paused = false;
        ServerActivityCallback listener = listenerUpdater.get(this);
        if (listener != null) {
            listenerUpdater.compareAndSet(this, listener, null);
        }
    }


    /**
     * All tasks entering the system via this entry point must call this method. If it returns REJECTED then the
     * task cannot be run, and its failure should be signaled back to the originator.
     * <p/>
     * If it returns {@code RUN} then the task should proceed as normal, and the {@link #requestComplete()} method
     * must be called once the task is complete, usually via a try/finally construct.
     */
    public RunResult beginRequest() {
        if (paused) {
            return RunResult.REJECTED;
        }
        if(trackIndividualControlPoints) {
            activeRequestCountUpdater.incrementAndGet(this);
        }
        RunResult runResult = controller.beginRequest(false);
        if (runResult == RunResult.REJECTED) {
            decreaseRequestCount();
        }
        return runResult;
    }



    /**
     * This task should only be called by a thread that has already been accepted from an entry point. It is used when
     * an existing running thread is about to offload to another thread, such as an executor service or async EJB.
     * <p>
     * Note that this can still be rejected if the global request limit has been hit.
     * <p/>
     * If it returns {@code RUN} then the task should proceed as normal, and the {@link #requestComplete()} method
     * must be called once the task is complete, usually via a try/finally construct.
     */
    public RunResult forceBeginRequest() {
        if(trackIndividualControlPoints) {
            activeRequestCountUpdater.incrementAndGet(this);
        }
        return controller.beginRequest(true);
    }

    /**
     * Called when a queued task is executed.
     */
    void beginExistingRequest() {
        if(trackIndividualControlPoints) {
            activeRequestCountUpdater.incrementAndGet(this);
        }
    }

    /**
     * Method that should be invoked once (and only once) to signify that a request has finished.
     * <p/>
     * This cannot be done automatically when the handleRequest method completes, as some
     */
    public void requestComplete() {
        decreaseRequestCount();
        controller.requestComplete();
    }

    private void decreaseRequestCount() {
        if (trackIndividualControlPoints) {
            int result = activeRequestCountUpdater.decrementAndGet(this);
            if (paused && result == 0) {
                ServerActivityCallback listener = listenerUpdater.get(this);
                if (listener != null) {
                    if (listenerUpdater.compareAndSet(this, listener, null)) {
                        listener.done();
                    }
                }
            }
        }
    }

    /**
     * Queues a task to run when the request controller allows it. There are two use cases for this:
     * <ol>
     * <li>This allows for requests to be queued instead of dropped when the request limit has been hit</li>
     * <li>Timed jobs that are supposed to execute while the container is suspended can be queued to execute
     * when it resumes</li>
     * </ol>
     * <p>
     * Note that the task will be run within the context of a {@link #beginRequest()} call, if the task
     * is executed there is no need to invoke on the control point again.
     * </p>
     *
     *
     * @param task            The task to run
     * @param timeout         The timeout in milliseconds, if this is larger than zero the task will be timed out after
     *                        this much time has elapsed
     * @param timeoutTask     The task that is run on timeout
     * @param rejectOnSuspend If the task should be rejected if the container is suspended, if this happens the timeout task is invoked immediately
     */
    public void queueTask(Runnable task, Executor taskExecutor, long timeout, Runnable timeoutTask, boolean rejectOnSuspend) {
        controller.queueTask(this, task, taskExecutor, timeout, timeoutTask, rejectOnSuspend, false);
    }

    /**
     * Queues a task to run when the request controller allows it. This allows tasks not to be dropped when the max request
     * limit has been hit. If the container has been suspended then this
     * <p/>
     * Note that the task will be run withing the context of a {@link #beginRequest()} call, if the task
     * is executed there is no need to invoke on the control point again.
     *
     *
     *
     * @param task            The task to run
     * @param taskExecutor    The executor to run the task in
     */
    public void forceQueueTask(Runnable task, Executor taskExecutor) {
        controller.queueTask(this, task, taskExecutor, -1, null, false, true);
    }

    public boolean isPaused() {
        return paused;
    }

    public int getActiveRequestCount() {
        return activeRequestCountUpdater.get(this);
    }

    synchronized int increaseReferenceCount() {
        return ++referenceCount;
    }

    synchronized int decreaseReferenceCount() {
        return --referenceCount;
    }
}
