/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.communication.routing.internal;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.rcenvironment.core.communication.common.InstanceNodeSessionId;
import de.rcenvironment.core.communication.model.NetworkRequest;
import de.rcenvironment.core.communication.model.NetworkResponse;
import de.rcenvironment.core.communication.model.NetworkResponseHandler;
import de.rcenvironment.core.communication.protocol.NetworkResponseFactory;
import de.rcenvironment.core.utils.common.LogUtils;
import de.rcenvironment.core.utils.incubator.DebugSettings;

/**
 * A helper class for waiting asynchronously for a single {@link NetworkResponse}. Used for connecting callback- and Future-based call
 * variants.
 * 
 * @author Robert Mischke
 */
public class WaitForResponseBlocker implements NetworkResponseHandler {

    private static final boolean VERBOSE_LOGGING = DebugSettings.getVerboseLoggingEnabled(WaitForResponseBlocker.class);

    // this class is instantiated frequently, so save the overhead of logger fetching with a static instance - misc_ro
    private static final Log sharedLog = LogFactory.getLog(WaitForResponseBlocker.class);

    private final NetworkRequest request;

    private final InstanceNodeSessionId eventNodeId;

    private final CountDownLatch responseReceivedLatch;

    private NetworkResponse response;

    private volatile String logMarker = null;

    public WaitForResponseBlocker(NetworkRequest request, InstanceNodeSessionId localNodeId) {
        this.request = request;
        this.eventNodeId = localNodeId;
        this.responseReceivedLatch = new CountDownLatch(1);
    }

    @Override
    public void onResponseAvailable(NetworkResponse receivedResponse) {
        this.response = receivedResponse;
        responseReceivedLatch.countDown();
    }

    /**
     * Waits until a {@link NetworkResponse} was received via {@link #onResponseAvailable(NetworkResponse)}, or until the given timeout has
     * elapsed. In both cases, a {@link NetworkResponse} is returned; there are no timeout exceptions.
     * 
     * @param requestTimeoutMsec the maximum time to wait, in msec
     * @return the result wrapper; either the actual response, or a timeout representation
     */
    public NetworkResponse await(long requestTimeoutMsec) {
        if (VERBOSE_LOGGING) {
            sharedLog.debug("Waiting for response callback (" + logMarker + ")");
        }
        try {
            if (responseReceivedLatch.await(requestTimeoutMsec, TimeUnit.MILLISECONDS)) {
                if (VERBOSE_LOGGING) {
                    sharedLog.debug("Received response callback (" + logMarker + ")");
                }
                return response;
            } else {
                sharedLog.debug("Timeout reached while waiting for a network response to request " + request.getRequestId());
                // TODO test/verify that a "null" cause does not trigger downstream NPEs
                return NetworkResponseFactory.generateResponseForTimeoutWaitingForResponse(request, eventNodeId);
            }
        } catch (InterruptedException e) {
            String errorId = LogUtils.logErrorAndAssignUniqueMarker(sharedLog,
                "Interrupted while waiting for a network response: " + e.toString()); // stacktrace irrelevant
            return NetworkResponseFactory.generateResponseForErrorDuringDelivery(request, eventNodeId, errorId);
        }
    }

    public void setLogMarker(String logMarker) {
        this.logMarker = logMarker;
    }
}
