package com.its.pis.xnettcp.vds;

import com.its.app.AppUtils;
import com.its.pis.ui.MainUI;
import com.its.pis.config.ControllerCommConfig;
import com.its.pis.entity.TbVdsCtlr;
import com.its.pis.xnettcp.vds.codec.VdsTcpClientDecoder;
import com.its.pis.xnettcp.vds.codec.VdsTcpClientEncoder;
import com.its.pis.xnettcp.vds.handler.VdsTcpClientIdleHandler;
import com.its.pis.xnettcp.vds.handler.VdsTcpClientInboundHandler;
import com.its.pis.xnettcp.vds.process.VdsDataProcess;
import com.its.pis.xnetudp.service.CenterCommResponseService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

@Slf4j
@Getter
@Setter
@RequiredArgsConstructor
public class VdsTcpClient implements Callable<Object> {

    private final TbVdsCtlr controller;
    private final ControllerCommConfig commConfig;
    private final VdsDataProcess vdsDataProcess;
    private final VdsTcpClientBootstrapFactory bootstrapFactory;

    private Bootstrap bootstrap = null;
    private ChannelFuture channelFuture = null;
    private String ipAddress;
    private int port;

    @Override
    public Object call() {

        this.ipAddress = this.controller.getVDS_CTLR_IP();
        this.port = this.controller.getVDS_CTLR_PORT();

        //log.info("[{}]. VdsTcpClient start: {}, {}", this.controller.getVDS_CTLR_ID(), this.ipAddress, this.port);
        if (this.bootstrap == null) {
            this.bootstrap = this.bootstrapFactory.createBootstrap();
            this.bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.commConfig.getConnectTimeout() * 1000);
            this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                // VDS 전송 요청을 먼저하고 데이터를 수신한다.
                // 핸들러가 실행되는 순서는 추가된 순서에 의해 결정된다.(Inbound: head=>tail, Outbound: tail=>head, name2ctx)
                @Override
                public void initChannel(SocketChannel ch) {
                    if (commConfig.isCommLogging()) {
                        //io.netty.handler.logging.LoggingHandler
                        //ch.pipeline().addLast(new LoggingHandler(LogLevel.WARN));
                    }
                    else {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.ERROR));
                    }
                    ch.pipeline().addLast("vdsClientIdleHandler",    new VdsTcpClientIdleHandler(commConfig.getReaderIdleTime(), commConfig.getWriterIdleTime(), commConfig.getAllIdleTime(), TimeUnit.SECONDS));
                    ch.pipeline().addLast("vdsClientDecoder",        new VdsTcpClientDecoder());            // Decoding handler
                    ch.pipeline().addLast("vdsClientInboundHandler", new VdsTcpClientInboundHandler(vdsDataProcess));     // Packet Inbound handler
                    ch.pipeline().addLast("vdsClientEncoder",        new VdsTcpClientEncoder());            // Encoding handler
                }
            });
        }

        log.info("[{}]. VDS Controller try connect: {}, {}", this.controller.getVDS_CTLR_ID(), this.ipAddress, this.port);
        if (this.channelFuture != null && this.channelFuture.channel() != null) {
            this.channelFuture.channel().close();
            this.channelFuture = null;
        }
        this.channelFuture = this.bootstrap.connect(new InetSocketAddress(this.ipAddress, this.port));

        // 연결 리스너 추가
        this.channelFuture.addListener(new ChannelFutureListener() {
            @Override public void operationComplete(ChannelFuture future) {
                if (future.isSuccess()) {
                    //log.info("[{}]. VDS Controller Channel open");
                    channelOpen(future.channel());
                } else {
                    log.error("[{}]. VDS Controller connect Failed: {}", controller.getVDS_CTLR_ID(), future.cause().getMessage());
                }
            }
        });

        // 연결 종료 리스너 추가
        this.channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {
            @Override public void operationComplete(ChannelFuture future) {
                channelClosed(future.channel());
            }
        });

        return null;
    }

    /**
     * 연결 성공시 처리 이벤트
     * @param channel
     */
    protected void channelOpen(Channel channel) {
        log.info("[{}]. VDS Controller connected: channel {}", this.controller.getVDS_CTLR_ID(), channel);
        TbVdsCtlr obj = this.controller;
        obj.channelOpen(channel);

        // 온도정보 요청
        ByteBuffer sendBuff;
        sendBuff = obj.getReqTemperature().getByteBuffer();
        obj.sendData(sendBuff, 0, "vds_Temperature");
        MainUI mainUI = MainUI.getInstance();
        if (mainUI != null) {
            mainUI.updateCtlrStts(obj);
        }
    }

    /**
     * 연결 종료시 처리 이벤트
     * @param channel
     */
    protected synchronized void channelClosed(Channel channel) {
        TbVdsCtlr obj = this.controller;
        if (obj.getChannel() != null) {
            log.error("[{}]. VDS Controller closed: channel {}", this.controller.getVDS_CTLR_ID(), channel);
            // 연결 되어 있다가 종료 된 경우임....
            // 클라이언트 연결 종료정보를 운영단말로 전송한다.
            CenterCommResponseService centerCommResponseService = (CenterCommResponseService) AppUtils.getBean(CenterCommResponseService.class);
            centerCommResponseService.notifyStatus(obj, false);
            MainUI mainUI = MainUI.getInstance();
            if (mainUI != null) {
                mainUI.updateCtlrStts(obj);
            }
        }
        obj.channelClosed();

        channel.close();
        channel.eventLoop().schedule(this, this.commConfig.getRetrySeconds(), TimeUnit.SECONDS);
    }

}
