package com.tsi.comm.server.tcp.codec;

import com.tsi.app.common.app.AppUtils;
import com.tsi.app.common.utils.SysUtils;
import com.tsi.app.common.utils.TimeUtils;
import com.tsi.app.common.xnet.NettyUtils;
import com.tsi.comm.server.mybatis.vo.AbstractDbmsVo;
import com.tsi.comm.server.mybatis.vo.AlarmOccrVo;
import com.tsi.comm.server.mybatis.vo.NodeStatusVo;
import com.tsi.comm.server.process.dbms.TsiCvimDbmsProcess;
import com.tsi.comm.server.process.packet.TsiCvimPacketProcess;
import com.tsi.comm.server.protocol.TsiCpuPacket;
import com.tsi.comm.server.repository.TsiAlarmManager;
import com.tsi.comm.server.repository.TsiNodeManager;
import com.tsi.comm.server.repository.TsiSessionManager;
import com.tsi.comm.server.repository.TsiTpmsManager;
import com.tsi.comm.server.vo.TsiAlarmConfigVo;
import com.tsi.comm.server.vo.TsiNodeVo;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;

@Slf4j
@Component
@ChannelHandler.Sharable
public class CvimServerByteBufMessageDecoder extends MessageToMessageDecoder<ByteBuf> {

    private final TsiCvimPacketProcess tsiCvimPacketService;
    private final TsiCvimDbmsProcess tsiCvimDbmsService;

    public CvimServerByteBufMessageDecoder(TsiCvimDbmsProcess tsiCvimDbmsService) {
        this.tsiCvimDbmsService = tsiCvimDbmsService;
        this.tsiCvimPacketService = (TsiCvimPacketProcess) AppUtils.getBean(TsiCvimPacketProcess.class);
    }
    /*@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.error("userEventTriggered: {}", NettyUtils.getRemoteIpAddress(ctx.channel()));
        ctx.fireUserEventTriggered(evt);
    }*/

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

        /*
        Set<Thread> threads = Thread.getAllStackTraces().keySet();
        for (Thread t : threads) {            String name = t.getName();
            Thread.State state = t.getState();
            int priority = t.getPriority();
            String type = t.isDaemon() ? "Daemon" : "Normal";
            String msg = String.format("Receive: %-20s \t %s \t %d \t %s\n", name, state, priority, type);
            log.info(msg);
        }*/

        try {
            long msec = TimeUtils.currentTimeSeconds();
            long nsec = System.nanoTime();

            if (byteBuf == null) {
                log.error("Receive: Packet frame packet error. length field data error");
                ctx.close();
                return;
            }

            final int readableBytes = byteBuf.readableBytes();
            if (readableBytes < TsiCpuPacket.SIZE_HEAD) {
                log.error("Receive: Packet readableBytes too small: {}, required min size: {}", readableBytes, TsiCpuPacket.SIZE_HEAD);
                ctx.close();
                return;
            }

            long nodeId = byteBuf.getUnsignedInt(6);
            TsiNodeVo nodeVo = ctx.channel().attr(TsiSessionManager.TSI_NODE_ATTRIBUTE_KEY).get();
            if (nodeVo == null) {
                // not found node information from channel map
                nodeVo = attachNodeVo(nodeId, ctx.channel());

                // first connection, save node information to channel attribute map
                ctx.channel().attr(TsiSessionManager.TSI_NODE_ATTRIBUTE_KEY).set(nodeVo);

                NodeStatusVo status = new NodeStatusVo(AbstractDbmsVo.DBMS_NODE_STATUS);
                status.setNodeId(nodeId);
                status.setStatus(1);
                status.setIpAddr(NettyUtils.getRemoteIpAddress(ctx.channel()));
                if (TsiSessionManager.getInstance().isServerRun()) {
                    this.tsiCvimDbmsService.add(status, (int)Thread.currentThread().getId());
                }
                else {
                    log.error("Node Login but server not running: {}", status.toString());
                }
            }

            nodeVo.setLastCommTm(System.currentTimeMillis());   // 통신 수신시각 저장
            TsiCpuPacket packet = new TsiCpuPacket(nodeId, msec, nsec, ctx.channel());
            packet.setBuf(new byte[readableBytes]);
            byteBuf.readBytes(packet.getBuf());
            packet.setObj(nodeVo);  // TsiNodeVo 객체를 저장

            // 작업큐로 데이터 전송
            this.tsiCvimPacketService.add(packet, (int)Thread.currentThread().getId());

            // 작업큐 데이터 전송 시각 셋
            packet.setAdd(System.nanoTime());

            // 패킷 통계정보 생성
            TsiTpmsManager.getInstance().readPacket(packet);
            if (nodeVo.isDump()) {
                log.info("RECV: {}, {}", nodeVo.getNodeId(), SysUtils.byteArrayToHex(packet.getBuf()));
            }
        }
        catch (Exception e) {
            log.error("decode Exception: {}", e.getMessage());
        }
    }

    private TsiNodeVo attachNodeVo(long nodeId, Channel channel) {
        long start = System.nanoTime();

        // 노드맵에서 노드 정보를 찾는다.
        TsiNodeVo nodeVo = TsiNodeManager.getInstance().get(nodeId);
        if (nodeVo == null) {
            // 메모리맵에 노드정보가 존재하지 않은 경우
            // 원래는 종료 시켜야 하나.....
            // 신규로 해당 노드정보 메로리를 생성해서 메모리 맵에 추가한다.

            log.warn("첫번째 수신한 패킷의 노드 ID 가 등록되어 있지 않음: {}, {}", nodeId, channel);

            nodeVo = new TsiNodeVo(nodeId, false, true, true);
            nodeVo.setCheckInstalled(true);
            nodeVo.setRegistered(false);
            TsiNodeManager.getInstance().put(nodeId, nodeVo);

            // oops 알람 설정 되어 있으면 알람테이블에 저장하자.
            if (TsiAlarmManager.getInstance().checkAlarm(TsiAlarmConfigVo.COMM_01)) {
                AlarmOccrVo alarm = new AlarmOccrVo(AbstractDbmsVo.DBMS_ALARM_OCCR_HS);
                alarm.setAlarmCode(TsiAlarmConfigVo.COMM_01);
                alarm.setAlarmTarget(Long.toString(nodeId));
                alarm.setAlarmValue(NettyUtils.getRemoteIpAddress(channel));
                this.tsiCvimDbmsService.add(alarm, (int)Thread.currentThread().getId());
            }
        }

        // 채널 목록에 접속 정보 등록
        nodeVo.setConnect(channel);

        // 채널 맵에 채널에 대한 노드정보를 저장한다.
        TsiSessionManager.getInstance().addChannel(channel, nodeVo);

        log.info("Node Object Register, Node: {}: {}, {}", nodeId, TimeUtils.elapsedTime(start), Thread.currentThread().getId());
        return nodeVo;
    }

}
