package com.its.pis.xnettcp.vds.codec;

import com.its.app.utils.NettyUtils;
import com.its.app.utils.SysUtils;
import com.its.pis.entity.TbVdsCtlr;
import com.its.pis.global.AppRepository;
import com.its.pis.xnettcp.vds.handler.VdsTcpClientIdleHandler;
import com.its.pis.xnettcp.vds.protocol.VdsProtocol;
import com.its.pis.xnettcp.vds.protocol.VdsResFramePacket;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import java.util.List;

import static com.its.pis.xnettcp.vds.protocol.VdsProtocol.vds_DLE;

@Slf4j
public class VdsTcpClientDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) {

        String ipAddress = NettyUtils.getRemoteIpAddress(ctx.channel());
        Channel channel = ctx.channel();

        // TODO
        //int port = NettyUtils.getRemotePort(ctx.channel());
        //log.error("decode...........................................: {}, {}", ipAddress, port);
        //String objKey = ipAddress + ":" + Integer.toString(port);
        //TbVdsCtlr obj = AppRepository.getInstance().getCtlrKeyMap().get(objKey);
        TbVdsCtlr obj = AppRepository.getInstance().getCtlrIpMap().get(ipAddress);
        if (obj == null) {
            log.error("VdsTcpClientDecoder.decode: Unknown Controller IP: {}. will be close.", ipAddress);
            VdsTcpClientIdleHandler.disconnectChannel(channel);
            return;
        }

        if (!channel.isOpen() || !channel.isActive()) {
            log.error("[{}]. RECV: VdsTcpClientDecoder.decode: isOpen: {}, isActive: {}. [{}]", obj.getVDS_CTLR_ID(), channel.isOpen(), channel.isActive(), obj.getLogKey());
            VdsTcpClientIdleHandler.disconnectChannel(channel);
            return;
        }

        MDC.put("id", obj.getLogKey());
        try {
            int readableBytes = byteBuf.readableBytes();
            log.info("[{}]. RECV: ReadableBytes: {} Bytes, ReaderIndex: {}", obj.getVDS_CTLR_ID(), readableBytes, byteBuf.readerIndex());

            if (obj.isDump()) {
                byte[] debugBytes = new byte[byteBuf.readableBytes()];
                byteBuf.getBytes(byteBuf.readerIndex(), debugBytes);
                log.info("[{}]. RECV: {} Bytes. {}", obj.getVDS_CTLR_ID(), debugBytes.length, SysUtils.byteArrayToHex(debugBytes));
            }

            byteBuf.markReaderIndex();
            byte[] recvBytes = new byte[readableBytes];
            byte[] packets = new byte[readableBytes];
            byteBuf.readBytes(recvBytes);

            int stuffing = 0;
            int msgSize = 0;        // 순수 데이터 사이즈
            boolean foundDle = false;
            boolean requiredEtx = false;
            for (int readIdx = 0; readIdx < readableBytes; readIdx++) {
                if (readIdx == 2) {
                    if (recvBytes[0] != VdsProtocol.vds_DLE || recvBytes[1] != VdsProtocol.vds_STX) {
                        log.error("[{}]. RECV: DLE/STX Data Error. {}/{}", obj.getVDS_CTLR_ID(), recvBytes[0], recvBytes[1]);
                        VdsTcpClientIdleHandler.disconnectChannel(channel);
                        return;
                    }
                }

                byte data = recvBytes[readIdx];
                if (foundDle) {
                    foundDle = false;
                    if (data == VdsProtocol.vds_DLE) {
                        // Skip DLE Stuffing Data
                        stuffing++;
                        //log.warn("RECV_0: [{}]. ReadableBytes: {} Bytes, ReadIndex: {}, DLE Stuffing Data Recv.", ipAddress, readableBytes, readIdx);
                    } else if (data == VdsProtocol.vds_STX) {
                        // Packet
                        packets[msgSize++] = data;
                    } else if (data == VdsProtocol.vds_ETX) {
                        packets[msgSize++] = data;

                        // 이전데이터가 DLE 이고 현재 데이터가 ETX 이면 패킷의 끝임.
                        // 다음 2바이트는 CRC 데이터임, 남아 있는 데이터가 CRC 2바이트를 포함한 크기보다 작으면
                        // 하나의 패킷을 완전히 수신하지 못한 것임
                        if ((readIdx + 2) > (readableBytes)) {
                            // 하나의 패킷을 읽지 못함
                            log.warn("[{}]. RECV: ReadableBytes: {} Bytes, ReadIndex: {}, DLE Data Remain.", obj.getVDS_CTLR_ID(), readableBytes, readIdx);
                            break;
                        }
                        packets[msgSize++] = recvBytes[readIdx+1];  // CRC1
                        packets[msgSize++] = recvBytes[readIdx+2];  // CRC2

                        // 하나의 패킷을 읽었음 ***************
                        byteBuf.readerIndex(readIdx+3);
                        byteBuf.markReaderIndex();
                        byteBuf.discardReadBytes();

                        VdsResFramePacket frame = new VdsResFramePacket(obj, packets, msgSize);
//                        if (frame.getHead().getOpCode() == VdsProtocol.vds_Image) {
//                            log.info("[{}]. RECV: OneFramePacket: {} Bytes, vds_Image, stuffing {}", obj.getVDS_CTLR_ID(), frame.getByteBuffer().array().length, stuffing);
//                        } else {
//                            log.info("[{}]. RECV: OneFramePacket: {} Bytes, stuffing {} {}", obj.getVDS_CTLR_ID(), frame.getByteBuffer().array().length, stuffing, SysUtils.byteArrayToHex(frame.getByteBuffer().array()));
//                        }

                        list.add(frame);
                        break;
                    } else {
                        // DLE 다음에 와야할 데이터가 들어오지 않았다. 패킷에 오류가 발생함.
                        log.error("[{}]. RECV: ReadableBytes: {} Bytes, ReadIndex: {}, DLE Next Data Recv Error.", obj.getVDS_CTLR_ID(), readableBytes, readIdx);
                        VdsTcpClientIdleHandler.disconnectChannel(channel);
                        return;
                    }
                } else {
                    packets[msgSize++] = data;
                    if (data == vds_DLE) {
                        foundDle = true;
                    }
                }
            }
        }
        finally {
            byteBuf.resetReaderIndex();
            MDC.remove(obj.getLogKey());
            MDC.clear();
        }

    }
}
