package com.bekwam.examples.javafx.nettyinaction.ch2;

import java.net.InetSocketAddress;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;

public class EchoClientController {

	private Logger logger = LoggerFactory.getLogger( EchoClient.class );

	@FXML
	TextField tfSend;
	
	@FXML
	TextField tfReceive;
	
	@FXML
	TextField tfHost;
	
	@FXML
	TextField tfPort;
	
	@FXML
	Button btnConnect;
	
	@FXML
	Button btnSend;
	
	@FXML
	Button btnDisconnect;
	
	@FXML
	HBox hboxStatus;
	
	@FXML
	ProgressIndicator piStatus;
	
	@FXML
	Label lblStatus;
	
	private BooleanProperty connected = new SimpleBooleanProperty(false);
	private StringProperty receivingMessageModel = new SimpleStringProperty("");
	private Channel channel;
	private EventLoopGroup group;
	
	@FXML
	public void initialize() {
		
		hboxStatus.setVisible(false);
		
		btnConnect.disableProperty().bind( connected );
		tfHost.disableProperty().bind( connected );
		tfPort.disableProperty().bind( connected );
		tfSend.disableProperty().bind( connected.not() );
		btnDisconnect.disableProperty().bind( connected.not() );
		btnSend.disableProperty().bind( connected.not() );

		tfReceive.textProperty().bind(receivingMessageModel);
	}

	@FXML
	public void send() {
		if( logger.isDebugEnabled() ) {
			logger.debug("[SEND]");
		}
		
		if( !connected.get() ) {
			if( logger.isWarnEnabled() ) {
				logger.warn("client not connected; skipping write");
			}
			return;
		}
		
		final String toSend = tfSend.getText();
		
		Task<Void> task = new Task<Void>() {

			@Override
			protected Void call() throws Exception {
				
				ChannelFuture f = channel.writeAndFlush( Unpooled.copiedBuffer(toSend, CharsetUtil.UTF_8) );
				f.sync();

				return null;
			}
			
			@Override
			protected void failed() {
				
				Throwable exc = getException();
				logger.error( "client send error", exc );
				Alert alert = new Alert(AlertType.ERROR);
				alert.setTitle("Client");
				alert.setHeaderText( exc.getClass().getName() );
				alert.setContentText( exc.getMessage() );
				alert.showAndWait();
				
				connected.set(false);
			}

		};
		
		hboxStatus.visibleProperty().bind( task.runningProperty() );
		lblStatus.textProperty().bind( task.messageProperty() );
		piStatus.progressProperty().bind(task.progressProperty());
		
		new Thread(task).start();
	}
	
	@FXML
	public void connect() {
		
		if( connected.get() ) {
			if( logger.isWarnEnabled() ) {
				logger.warn("client already connected; skipping connect");
			}
			return;  // already connected; should be prevented with disabled
		}
		
		String host = tfHost.getText();
		int port = Integer.parseInt(tfPort.getText());

		group = new NioEventLoopGroup();
		
		Task<Channel> task = new Task<Channel>() {

			@Override
			protected Channel call() throws Exception {
				
				updateMessage("Bootstrapping");
				updateProgress(0.1d, 1.0d);
				
				Bootstrap b = new Bootstrap();
				b
					.group(group)
					.channel(NioSocketChannel.class)
					.remoteAddress( new InetSocketAddress(host, port) )
					.handler( new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new EchoClientHandler(receivingMessageModel));
						}
					});
				
				ChannelFuture f = b.connect();
				Channel chn = f.channel();
				
				updateMessage("Connecting");
				updateProgress(0.2d, 1.0d);

				f.sync();

				return chn;
			}

			@Override
			protected void succeeded() {
				
				channel = getValue();
				connected.set(true);
			}

			@Override
			protected void failed() {
				
				Throwable exc = getException();
				logger.error( "client connect error", exc );
				Alert alert = new Alert(AlertType.ERROR);
				alert.setTitle("Client");
				alert.setHeaderText( exc.getClass().getName() );
				alert.setContentText( exc.getMessage() );
				alert.showAndWait();
				
				connected.set(false);
			}
		};
		
		hboxStatus.visibleProperty().bind( task.runningProperty() );
		lblStatus.textProperty().bind( task.messageProperty() );
		piStatus.progressProperty().bind(task.progressProperty());
		
		new Thread(task).start();
	}
	
	@FXML
	public void disconnect() {
		
		if( !connected.get() ) {
			if( logger.isWarnEnabled() ) {
				logger.warn("client not connected; skipping disconnect");
			}
			return;
		}
		
		if( logger.isDebugEnabled() ) {
			logger.debug("[DISCONNECT]");
		}
		
		Task<Void> task = new Task<Void>() {

			@Override
			protected Void call() throws Exception {
				
				updateMessage("Disconnecting");
				updateProgress(0.1d, 1.0d);
				
				channel.close().sync();					

				updateMessage("Closing group");
				updateProgress(0.5d, 1.0d);
				group.shutdownGracefully().sync();

				return null;
			}

			@Override
			protected void succeeded() {
				
				connected.set(false);
			}

			@Override
			protected void failed() {
				
				connected.set(false);

				Throwable t = getException();
				logger.error( "client disconnect error", t );
				Alert alert = new Alert(AlertType.ERROR);
				alert.setTitle("Client");
				alert.setHeaderText( t.getClass().getName() );
				alert.setContentText( t.getMessage() );
				alert.showAndWait();

			}
			
		};
		
		hboxStatus.visibleProperty().bind( task.runningProperty() );
		lblStatus.textProperty().bind( task.messageProperty() );
		piStatus.progressProperty().bind(task.progressProperty());

		new Thread(task).start();
	}
}