/*
 * This file is part of Bisq.
 *
 * Bisq is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bisq 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 Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
 */

package bisq.network.p2p.peers.getdata;

import bisq.network.p2p.network.CloseConnectionReason;
import bisq.network.p2p.network.Connection;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.peers.getdata.messages.GetDataRequest;
import bisq.network.p2p.peers.getdata.messages.GetDataResponse;
import bisq.network.p2p.storage.P2PDataStorage;

import bisq.common.Timer;
import bisq.common.UserThread;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import lombok.extern.slf4j.Slf4j;

import org.jetbrains.annotations.NotNull;

@Slf4j
public class GetDataRequestHandler {
    private static final long TIMEOUT = 180;

    private static final int MAX_ENTRIES = 10000;

    ///////////////////////////////////////////////////////////////////////////////////////////
    // Listener
    ///////////////////////////////////////////////////////////////////////////////////////////

    public interface Listener {
        void onComplete();

        void onFault(String errorMessage, Connection connection);
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Class fields
    ///////////////////////////////////////////////////////////////////////////////////////////

    private final NetworkNode networkNode;
    private final P2PDataStorage dataStorage;
    private final Listener listener;
    private Timer timeoutTimer;
    private boolean stopped;


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Constructor
    ///////////////////////////////////////////////////////////////////////////////////////////

    public GetDataRequestHandler(NetworkNode networkNode, P2PDataStorage dataStorage, Listener listener) {
        this.networkNode = networkNode;
        this.dataStorage = dataStorage;
        this.listener = listener;
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // API
    ///////////////////////////////////////////////////////////////////////////////////////////

    public void handle(GetDataRequest getDataRequest, final Connection connection) {
        long ts = System.currentTimeMillis();
        String connectionInfo = "connectionInfo" + connection.getPeersNodeAddressOptional()
                .map(e -> "node address " + e.getFullAddress())
                .orElseGet(() -> "connection UID " + connection.getUid());

        AtomicBoolean outPersistableNetworkPayloadOutputTruncated = new AtomicBoolean(false);
        AtomicBoolean outProtectedStoragePayloadOutputTruncated = new AtomicBoolean(false);
        GetDataResponse getDataResponse = dataStorage.buildGetDataResponse(
                getDataRequest,
                MAX_ENTRIES,
                outPersistableNetworkPayloadOutputTruncated,
                outProtectedStoragePayloadOutputTruncated,
                connection.getCapabilities());

        if (outPersistableNetworkPayloadOutputTruncated.get()) {
            log.warn("The getData request from peer with {} caused too much PersistableNetworkPayload " +
                            "entries to get delivered. We limited the entries for the response to {} entries",
                    connectionInfo, MAX_ENTRIES);
        }

        if (outProtectedStoragePayloadOutputTruncated.get()) {
            log.warn("The getData request from peer with {} caused too much ProtectedStorageEntry " +
                            "entries to get delivered. We limited the entries for the response to {} entries",
                    connectionInfo, MAX_ENTRIES);
        }

        log.info("The getDataResponse to peer with {} contains {} ProtectedStorageEntries and {} PersistableNetworkPayloads",
                connectionInfo,
                getDataResponse.getDataSet().size(),
                getDataResponse.getPersistableNetworkPayloadSet().size());

        if (timeoutTimer == null) {
            timeoutTimer = UserThread.runAfter(() -> {  // setup before sending to avoid race conditions
                        String errorMessage = "A timeout occurred for getDataResponse " +
                                " on connection:" + connection;
                        handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
                    },
                    TIMEOUT, TimeUnit.SECONDS);
        }

        SettableFuture<Connection> future = networkNode.sendMessage(connection, getDataResponse);
        Futures.addCallback(future, new FutureCallback<>() {
            @Override
            public void onSuccess(Connection connection) {
                if (!stopped) {
                    log.trace("Send DataResponse to {} succeeded. getDataResponse={}",
                            connection.getPeersNodeAddressOptional(), getDataResponse);
                    cleanup();
                    listener.onComplete();
                } else {
                    log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call.");
                }
            }

            @Override
            public void onFailure(@NotNull Throwable throwable) {
                if (!stopped) {
                    String errorMessage = "Sending getDataRequest to " + connection +
                            " failed. That is expected if the peer is offline. getDataResponse=" + getDataResponse + "." +
                            "Exception: " + throwable.getMessage();
                    handleFault(errorMessage, CloseConnectionReason.SEND_MSG_FAILURE, connection);
                } else {
                    log.trace("We have stopped already. We ignore that networkNode.sendMessage.onFailure call.");
                }
            }
        }, MoreExecutors.directExecutor());
        log.info("handle GetDataRequest took {} ms", System.currentTimeMillis() - ts);
    }

    public void stop() {
        cleanup();
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Private
    ///////////////////////////////////////////////////////////////////////////////////////////

    private void handleFault(String errorMessage, CloseConnectionReason closeConnectionReason, Connection connection) {
        if (!stopped) {
            log.info(errorMessage + "\n\tcloseConnectionReason=" + closeConnectionReason);
            cleanup();
            listener.onFault(errorMessage, connection);
        } else {
            log.warn("We have already stopped (handleFault)");
        }
    }

    private void cleanup() {
        stopped = true;
        if (timeoutTimer != null) {
            timeoutTimer.stop();
            timeoutTimer = null;
        }
    }
}

@@ -3398,19 +3398,19 @@
 Boolean 
-out
+was
 Persista
@@ -3418,38 +3418,33 @@
 leNetworkPayload
-Output
+s
 Truncated = new 
@@ -3487,19 +3487,19 @@
 Boolean 
-out
+was
 Protecte
@@ -3502,37 +3502,31 @@
 ectedStorage
-PayloadOutput
+Entries
 Truncated = 
@@ -3704,19 +3704,19 @@
         
-out
+was
 Persista
@@ -3724,38 +3724,33 @@
 leNetworkPayload
-Output
+s
 Truncated,%0A     
@@ -3760,19 +3760,19 @@
         
-out
+was
 Protecte
@@ -3775,37 +3775,31 @@
 ectedStorage
-PayloadOutput
+Entries
 Truncated,%0A 
@@ -3857,19 +3857,19 @@
     if (
-out
+was
 Persista
@@ -3881,30 +3881,25 @@
 tworkPayload
-Output
+s
 Truncated.ge
@@ -4195,19 +4195,19 @@
     if (
-out
+was
 Protecte
@@ -4218,21 +4218,15 @@
 rage
-PayloadOutput
+Entries
 Trun

Rename variables