package com.pwt.netty.client;

import cn.hutool.core.thread.NamedThreadFactory;
import com.pwt.Globals;
import com.pwt.StationService;
import com.pwt.netty.MessageSender;
import com.pwt.netty.common.constants.Constants;
import com.pwt.netty.common.constants.ExchangeWorkState;
import com.pwt.netty.message2.Assist;
import com.pwt.netty.message2.RfidInfo;
import com.pwt.web.domain.PwtVehicle;
import com.pwt.web.service.IPwtVehicleService;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static com.pwt.netty.MessageSender.MSG_ASSIST;
import static com.pwt.netty.MessageSender.MSG_RFID;

/***
 * RFID客户端，用户接收UDP消息
 */
@Slf4j
public class RFIDClient {


    /**
     * RFID有效数据起始位置
     * </br>
     * 列如报文为：5A 00 01 12 00 00 18 00 0C E2 80 69 95 00 00 40 0E 7A 25 D9 9E 30 00 01 01 05 08 00 0D FB 1A E2 AC
     * 实际的RFID为E28069950000400E7A25D99E，从第10(索引为9）个字节开始，长度为12个字节
     */
    private static final int RFID_DATA_START_INDEX = 9;

    /**
     * RFID有效数据长度
     */
    private static final int RFID_DATA_LENGTH = 12;

    private static final int RFID_CLIENT_PORT = 3001;


    private final String host;
    private final int port;

    private final AtomicBoolean shouldStart = new AtomicBoolean(false);

    private final AtomicBoolean shouldRetry = new AtomicBoolean(false);

    private final AtomicInteger currentState = new AtomicInteger(ClientConstants.CLIENT_STATUS_OFF);

    private final AtomicInteger failConnectedTime = new AtomicInteger(0);

    private NioEventLoopGroup eventLoopGroup;
    private Channel channel;

    private Bootstrap bootstrap;

    private StationService stationService;

    private IPwtVehicleService vehicleService;

    @Autowired
    public void setVehicleService(IPwtVehicleService vehicleService) {
        this.vehicleService = vehicleService;
    }

    @Autowired
    public void setStationService(StationService stationService) {
        this.stationService = stationService;
    }

    public RFIDClient(String hostname, int port) {
        this.host = hostname;
        this.port = port;
    }

    public void start() throws Exception {
        if (shouldStart.compareAndSet(false, true)) {
            shouldRetry.set(true);
            startInternal();
        }
    }

    private void startInternal() {
        initBootStrap();
        connect();
    }

    private final AtomicBoolean isReady = new AtomicBoolean(false);

    private void initBootStrap() {
        bootstrap = new Bootstrap();
        eventLoopGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("RfidClientWorker"));

//        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);

        bootstrap.group(eventLoopGroup)
                .channel(NioDatagramChannel.class)
                .localAddress(RFID_CLIENT_PORT)
                .handler(new ChannelInitializer<DatagramChannel>() {
                    @Override
                    protected void initChannel(DatagramChannel datagramChannel) {
                        datagramChannel.pipeline()
//                        .addLast("rfid-logging", loggingHandler)
                                .addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS))
                                .addLast("rfid-clientHandler", new SimpleChannelInboundHandler<DatagramPacket>() {

                                    @Override
                                    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                        if (log.isInfoEnabled()) {
                                            log.info("[RFID-ClientHandler] Client handler inactive, remote address: {}", ctx.channel().remoteAddress());
                                        }
                                    }

                                    @Override
                                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                        if (log.isWarnEnabled()) {
                                            log.warn("[RFID-ClientHandler] Client exception caught", cause);
                                        }
                                    }

                                    @Override
                                    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
                                        if (log.isInfoEnabled()) {
                                            log.info("[RFID-ClientHandler] Client channel unregistered, remote address: {}", ctx.channel().remoteAddress());
                                        }
                                        currentState.set(ClientConstants.CLIENT_STATUS_OFF);

                                        disconnectCallback.run();
                                        if (isReady.compareAndSet(true, false)) {
                                            RfidInfo rfidInfo = new RfidInfo();
                                            rfidInfo.setState(Constants.NET_CONNECTION_STATE_OFFLINE);
                                            Globals.uiData.setRfid(rfidInfo);
                                            MessageSender.send(MSG_RFID, rfidInfo);
                                        }
                                    }

                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        currentState.set(ClientConstants.CLIENT_STATUS_STARTED);
                                        if (isReady.compareAndSet(false, true)) {
                                            RfidInfo rfidInfo = new RfidInfo();
                                            rfidInfo.setState(Constants.NET_CONNECTION_STATE_ONLINE);
                                            Globals.uiData.setRfid(rfidInfo);
                                            MessageSender.send(MSG_RFID, rfidInfo);
                                        }
                                    }


                                    private String lastRfid = null;

                                    @Override
                                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket msg) throws Exception {
                                        if (isReady.compareAndSet(false, true)) {
                                            RfidInfo rfidInfo = new RfidInfo();
                                            rfidInfo.setState(Constants.NET_CONNECTION_STATE_ONLINE);
                                            Globals.uiData.setRfid(rfidInfo);
                                            MessageSender.send(MSG_RFID, rfidInfo);
                                        }
                                        ByteBuf content = msg.content();
                                        int length = content.readableBytes();

                                        // 如果数据包的大小小于33，则不处理
                                        if (length < 33) {
                                            return;
                                        }

                                        byte[] bytes = new byte[RFID_DATA_LENGTH];
                                        content.getBytes(RFID_DATA_START_INDEX, bytes);
                                        StringBuilder sb = new StringBuilder();
                                        for (byte b : bytes) {
                                            sb.append(String.format("%02X", b));
                                        }
                                        String rfid = sb.toString();
                                        // 屏蔽非法rfid
                                        PwtVehicle vehicle = vehicleService.getVinByRfid(rfid);
                                        if (vehicle == null) {
                                            return;
                                        }
                                        if (stationService.getExchangeState() < ExchangeWorkState.RFID.getValue() && !rfid.equals(lastRfid)) {
                                            log.info("【rfid】:rfid变更,old:{}, new:{}", lastRfid, rfid);
                                            lastRfid = rfid;
                                            Assist assist = new Assist();
                                            assist.setText("关闭钥匙");
                                            assist.setMedia("http://localhost:8080/static/" + 2 + ".mp3");
                                            MessageSender.send(MSG_ASSIST, assist);
                                        }
                                        // 如果当前工作状态不是扫码状态，则不处理
                                        if (stationService.getExchangeState() != ExchangeWorkState.SCANNED_CODE.getValue()) {
                                            return;
                                        } else {
//                                            Assist assist = new Assist();
//                                            assist.setText("扫码成功！");
//                                            assist.setMedia("http://localhost:8080/static/" + 17 + ".mp3");
//                                            MessageSender.send(MSG_ASSIST, assist);
                                        }
                                        stationService.rfid(rfid);
                                    }

                                    @Override
                                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                        if (evt instanceof IdleStateEvent) {
                                            if (((IdleStateEvent) evt) == IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT) {
                                                if (isReady.compareAndSet(true, false)) {
                                                    RfidInfo rfidInfo = new RfidInfo();
                                                    rfidInfo.setState(Constants.NET_CONNECTION_STATE_OFFLINE);
                                                    Globals.uiData.setRfid(rfidInfo);
                                                    MessageSender.send(MSG_RFID, rfidInfo);
                                                }
                                            }
                                        }
                                        super.userEventTriggered(ctx, evt);
                                    }
                                });
                    }
                });
    }

    private void connect() {
        ChannelFuture future = bootstrap.connect(host, port);
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.cause() != null) {
                    if (log.isWarnEnabled()) {
                        log.info("[RFID-Client] Could not  connect to server <{}:{}> after {} times", host, port, failConnectedTime.get(),
                                channelFuture.cause());
                    }
                    channel = null;
                    failConnectedTime.incrementAndGet();
                } else {
                    channel = channelFuture.channel();
                    failConnectedTime.set(0);
                    if (log.isInfoEnabled()) {
                        log.info("[RFID-Client] Successfully connect to server <{}:{}>", host, port);
                    }
                }
            }
        });
    }

    public void stop() throws Exception {

        if (shouldStart.compareAndSet(true, false)) {
            stopClientIfStarted();
        }
    }

    private void stopClientIfStarted() {
        // Stop retrying for connection.
        shouldRetry.set(false);

        while (currentState.get() == ClientConstants.CLIENT_STATUS_STARTING) {
            try {
                Thread.sleep(200);
            } catch (Exception ex) {
                // Ignore.
            }
        }
        cleanUp();

        if (log.isInfoEnabled()) {
            log.info("[RFID-Client] Client stopped");
        }
    }

    private void cleanUp() {
        if (channel != null) {
            channel.close();
            channel = null;
        }
        if (eventLoopGroup != null) {
            eventLoopGroup.shutdownGracefully();
        }
    }

    @SuppressWarnings("PMD.ThreadPoolCreationRule")
    private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1,
            new NamedThreadFactory("rfid-client-scheduler", true));

    private static final long RECONNECT_DELAY_MS = 1000;

    private static final int MAX_TIME = 1;

    private final Runnable disconnectCallback = new Runnable() {
        @Override
        public void run() {
            if (!shouldRetry.get()) {
                return;
            }

            int time = Math.min(failConnectedTime.get() + 1, MAX_TIME);
            SCHEDULER.schedule(new Runnable() {
                @Override
                public void run() {
                    if (shouldRetry.get()) {
                        if (log.isInfoEnabled()) {
                            log.info("[RFID-Client] Reconnection to server <{}:{}>", host, port);
                        }
                        try {
                            startInternal();
                        } catch (Exception e) {
                            if (log.isWarnEnabled()) {
                                log.warn("[RFID-Client] Failed to reconnect to server", e);
                            }
                        }
                    }
                }
            }, RECONNECT_DELAY_MS * time, TimeUnit.MILLISECONDS);
//            cleanUp();
        }
    };

}
