package sample.webflux.websocket.netty.handler;

import java.nio.channels.ClosedChannelException;

import org.springframework.web.reactive.socket.WebSocketSession;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.ReplayProcessor;
import reactor.core.scheduler.Schedulers;
import reactor.ipc.netty.channel.AbortedException;

public class WebSocketSessionHandler 
{
	private final ReplayProcessor<String> receiveProcessor;
	private final MonoProcessor<WebSocketSession> connectedProcessor;
	private final MonoProcessor<WebSocketSession> disconnectedProcessor;
	
	private boolean webSocketConnected;
	private WebSocketSession session;	
	
	public WebSocketSessionHandler()
	{
		this(50);
	}
	
	public WebSocketSessionHandler(int historySize)
	{
		receiveProcessor = ReplayProcessor.create(historySize);
		connectedProcessor = MonoProcessor.create();
		disconnectedProcessor = MonoProcessor.create();
		
		webSocketConnected = false;
	}
	
	protected Mono<Void> handle(WebSocketSession session)
	{
		this.session = session;
		
		Flux<String> receive =
			session
				.receive()
				.map(message -> message.getPayloadAsText())
				.doOnNext(textMessage -> receiveProcessor.onNext(textMessage))
				.doOnComplete(() -> receiveProcessor.onComplete());
		
		Mono<Object> connected =
				Mono
					.fromRunnable(() -> 
					{
						webSocketConnected = true;
						connectedProcessor.onNext(session);
					});

		Mono<Object> disconnected =
				Mono
					.fromRunnable(() -> 
					{
						webSocketConnected = false;
						disconnectedProcessor.onNext(session);
					})
					.doOnNext(value -> receiveProcessor.onComplete());
			
		return connected.thenMany(receive).then(disconnected).then();
	}
	
	public Mono<WebSocketSession> connected()
	{
		return connectedProcessor;
	}
	
	public Mono<WebSocketSession> disconnected()
	{
		return disconnectedProcessor;
	}
	
	public boolean isConnected()
	{
		return webSocketConnected;
	}
	
	public Flux<String> receive()
	{
		return receiveProcessor;
	}
	
	public void send(String message)
	{
		if (webSocketConnected)
		{
			session
				.send(Mono.just(session.textMessage(message)))
				.doOnError(ClosedChannelException.class, t -> connectionClosed())
				.doOnError(AbortedException.class, t -> connectionClosed())
				.onErrorResume(ClosedChannelException.class, t -> Mono.empty())
				.onErrorResume(AbortedException.class, t -> Mono.empty())
				.subscribe();
		}	
	}
	
	private void connectionClosed()
	{
		if (webSocketConnected)
		{
			webSocketConnected = false;
			disconnectedProcessor.onNext(session);
		}
	}
}