package io.muoncore.channel.impl;

import io.muoncore.channel.Channel;
import io.muoncore.channel.ChannelConnection;
import io.muoncore.channel.support.Scheduler;
import io.muoncore.exception.MuonException;
import io.muoncore.message.MuonInboundMessage;
import io.muoncore.message.MuonMessage;
import io.muoncore.message.MuonMessageBuilder;
import io.muoncore.message.MuonOutboundMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Dispatcher;

import java.util.concurrent.TimeUnit;

import static io.muoncore.transport.TransportEvents.CONNECTION_FAILURE;

/**
 * Implements keep alive behaviour.
 *
 * Assumes that there is another KeepAliveChannel down the pipe somewhere.
 *
 * If no data moves right within the keep alive period, sends a dedicated keep alive message
 *
 * If no data moves left within the keep alive period, will terminate the channel as failed.
 */
public class KeepAliveChannel implements Channel<MuonOutboundMessage, MuonInboundMessage> {

    public final static String KEEP_ALIVE_STEP = "keep-alive";

    private ChannelConnection<MuonOutboundMessage, MuonInboundMessage> left;
    private ChannelConnection<MuonInboundMessage, MuonOutboundMessage> right;

    private ChannelConnection.ChannelFunction<MuonOutboundMessage> leftFunction;
    private ChannelConnection.ChannelFunction<MuonInboundMessage> rightFunction;

    private static final long KEEP_ALIVE_TIMEOUT = 5500;
    private static final long KEEP_ALIVE_PERIOD = 1000;
    private final Scheduler scheduler;
    private Scheduler.TimerControl timeoutTimerControl;
    private Scheduler.TimerControl keepAliveTimerControl;
    private boolean channelFailed = false;
    private String protocol;
    private Logger logger = LoggerFactory.getLogger(getClass());
    private long lastMsg;

    public KeepAliveChannel(Dispatcher dispatcher, String protocol, Scheduler scheduler) {
        this.protocol = protocol;
        this.scheduler = scheduler;
        String leftname = "left";
        String rightname = "right";

        resetKeepAlivePing();
        resetTimeout();

        left = new ChannelConnection<MuonOutboundMessage, MuonInboundMessage>() {
            @Override
            public void receive(ChannelFunction<MuonInboundMessage> function) {
                rightFunction = function;
            }

            @Override
            public void send(MuonOutboundMessage message) {
                if (message == null) {
                    shutdown();
                    return;
                }
                resetKeepAlivePing();
                if (leftFunction == null) {
                    throw new MuonException("Other side of the channel [" + rightname + "] is not connected to receive data");
                }
                dispatcher.dispatch(message, msg -> {
                    if (StandardAsyncChannel.echoOut) System.out.println("KeepAliveChannel[" + leftname + " >>>>> " + rightname + "]: Sending " + msg + " to " + leftFunction);
                    leftFunction.apply(message); }
                        ,  Throwable::printStackTrace);
                if (message.getChannelOperation() == MuonMessage.ChannelOperation.closed) {
                    shutdown();
                }
            }

            @Override
            public void shutdown() {
                timeoutTimerControl.cancel();
                keepAliveTimerControl.cancel();
                leftFunction.apply(null);
            }
        };

        right = new ChannelConnection<MuonInboundMessage, MuonOutboundMessage>() {
            @Override
            public void receive(ChannelFunction<MuonOutboundMessage> function) {
                leftFunction = function;
            }

            @Override
            public void send(MuonInboundMessage message) {
                if (rightFunction == null) {
                    throw new MuonException("Other side of the channel [" + rightname + "] is not connected to receive data");
                }
                if (message == null) {
                    rightFunction.apply(null);
                    return;
                }
                resetTimeout();
                if(!KEEP_ALIVE_STEP.equals(message.getStep())) {
                    dispatcher.dispatch(message, msg -> {
                        if (StandardAsyncChannel.echoOut)
                            System.out.println("KeepAliveChannel[" + leftname + " <<<< " + rightname + "]: " + msg + " to " + rightFunction);
                        rightFunction.apply(message);
                    }, Throwable::printStackTrace);
                }
                if (message == null || message.getChannelOperation() == MuonMessage.ChannelOperation.closed) {
                    shutdown();
                }
            }

            @Override
            public void shutdown() {
                timeoutTimerControl.cancel();
                keepAliveTimerControl.cancel();
            }
        };
    }

    @Override
    public ChannelConnection<MuonInboundMessage, MuonOutboundMessage> right() {
        return right;
    }

    @Override
    public ChannelConnection<MuonOutboundMessage, MuonInboundMessage> left() {
        return left;
    }

    private void resetKeepAlivePing() {
        if (channelFailed) return;
        if (keepAliveTimerControl != null) {
            keepAliveTimerControl.cancel();
        }
        keepAliveTimerControl = scheduler.executeIn(KEEP_ALIVE_PERIOD, TimeUnit.MILLISECONDS, () -> {
            logger.trace("Sending keep alive ping");
            left.send(MuonMessageBuilder.fromService("local")
                            .protocol(protocol)
                            .step(KEEP_ALIVE_STEP).build());
        });
    }

    private void resetTimeout() {
        lastMsg = System.currentTimeMillis();
        if (channelFailed) return;
        if (timeoutTimerControl != null) {
            timeoutTimerControl.cancel();
        }
        timeoutTimerControl = scheduler.executeIn(KEEP_ALIVE_TIMEOUT, TimeUnit.MILLISECONDS, () -> {
            keepAliveTimerControl.cancel();
            logger.debug("Connection has failed to stay alive, last message was received " + (System.currentTimeMillis() - this.lastMsg) + "ms ago, sending failure to protocol level: " + protocol);
            channelFailed = true;
            right().send(
                    MuonMessageBuilder.fromService("local")
                            .protocol(protocol)
                            .operation(MuonMessage.ChannelOperation.closed)
                            .step(CONNECTION_FAILURE).buildInbound());
            left().send(MuonMessageBuilder.fromService("local")
                    .protocol(protocol)
                    .operation(MuonMessage.ChannelOperation.closed)
                    .step(CONNECTION_FAILURE).build());
        });
    }
}

@@ -5482,61 +5482,8 @@
 %3E %7B%0A
-            logger.trace(%22Sending keep alive ping%22);%0A
     

Remove logging.