package com.tsi.comm.server.vo;

import com.tsi.comm.server.protocol.TsiCpuPacket;
import com.tsi.comm.server.repository.TsiAlarmManager;
import com.tsi.common.utils.Counter;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
@Getter
@Setter
public class TsiNodeVo {
    private long nodeId;
    private String ipAddr;
    private boolean sendTest;
    private boolean sendNode;
    private boolean sendCvim;
    private boolean addNodeEnabled;

    private Channel channel;

    private boolean registered;         // Database 등록 여부, DB에 등록안되고 통신으로 접속하는 경우 false
    private boolean installed;
    private boolean checkInstalled;

    private Counter connectCount    = new Counter();
    private Counter disconnectCount = new Counter();

    TsiNodeAddVo tsiNodeAddVo;

    protected long connectTm;
    protected long disconnectTm;
    protected long lastCommTm;

    protected long recvTime;
    protected long handleTime;
    protected long queueTime;
    protected long endTime;

    private boolean dump;
    private boolean tcpDump;

    private int pktQIdx;
    private int dbmsQIdx;
    private int logQIdx;

    private int sigCount;

    // 빈번한 패킷 수신 처리를 위한 변수
    private final AtomicLong lastProcessTime = new AtomicLong(0);
    private final AtomicLong droppedPacketCount = new AtomicLong(0);

    // 작업 완료 여부 확인을 위한 변수
    private final AtomicInteger pendingPacketCount = new AtomicInteger(0);
    private final AtomicLong overlappingPacketCount = new AtomicLong(0);

    // 패킷 수신부터 처리까지 평균 작업 시간
    private final AtomicLong averageProcessingTimeNanos = new AtomicLong(0);

    public TsiNodeVo(long nodeId, String ipAddr, boolean sendTest, boolean sendNode, boolean sendCvim) {
        this.nodeId = nodeId;
        this.ipAddr = ipAddr;
        this.sendTest = sendTest;
        this.sendNode = sendNode;
        this.sendCvim = sendCvim;
        this.addNodeEnabled = false;

        this.channel = null;
        this.installed = true;
        this.checkInstalled = true;

        this.connectTm = 0;
        this.disconnectTm = 0;
        this.lastCommTm = 0;
        this.dump = false;
        this.tcpDump = false;
        this.pktQIdx = 0;
        this.dbmsQIdx = 0;
        this.logQIdx = 0;
        this.sigCount = 0;

        this.tsiNodeAddVo = null;
    }

    public boolean isConnect() {
        return (this.channel != null);
    }

    /**
     * 기준시간 이상 간격으로 패킷이 수신되지 않으면 해당 패킷을 버린다.
     */
    public synchronized boolean isProcessable(long intervalMillis) {
        long now = System.currentTimeMillis();
        long last = this.lastProcessTime.get();

        if (now - last < intervalMillis) {
            // 마지막 처리 후 intervalMillis가 지나지 않았으면, 중복 카운트를 증가시키고 false를 반환
            this.droppedPacketCount.incrementAndGet();
            return false;
        }
        // intervalMillis가 지났으면, 마지막 처리 시각을 현재로 업데이트하고 true 반환
        this.lastProcessTime.set(now);
        return true;
    }
    public long getDroppedPacketCount() {
        return this.droppedPacketCount.get();
    }

    public void packetProcessingStarted() {
        // pendingPacketCount를 1 증가시키고, 증가시키기 '전'의 값을 가져옴.
        if (this.pendingPacketCount.getAndIncrement() > 0) {
            // 이전 값이 0보다 컸다면, 이미 처리 중인 패킷이 있었다는 의미임.
            this.overlappingPacketCount.incrementAndGet();
        }
    }
    public void packetProcessingFinished(TsiCpuPacket packet) {
        // 패킷처리 완료
        this.pendingPacketCount.decrementAndGet();

        if (packet.getEnd() > 0) {
            long startTime = packet.getTimespec().timestamp();
            if (startTime > 0) {
                long durationNanos = System.nanoTime() - startTime;
                updateAverageProcessingTime(durationNanos);
            }
        }
    }
    public long getOverlappingPacketCount() {
        return this.overlappingPacketCount.get();
    }

    public synchronized void updateAverageProcessingTime(long durationNanos) {
        long currentAvg = this.averageProcessingTimeNanos.get();
        if (currentAvg == 0) {
            // 첫 번째 측정인 경우, 그대로 값을 설정
            this.averageProcessingTimeNanos.set(durationNanos);
        } else {
            // 기존 평균과 새로운 값의 중간값을 계산하여 이동 평균을 구함
            long newAvg = (currentAvg + durationNanos) / 2L;
            this.averageProcessingTimeNanos.set(newAvg);
        }
    }
    public long getAverageProcessingTimeMicros() {
        return TimeUnit.NANOSECONDS.toMicros(this.averageProcessingTimeNanos.get());
    }

    /**
     * 채널을 설정을 동기화를 처리하여 스레드 안전성을 보장.
     */
    public synchronized void setConnect(Channel channel) {
        this.channel = channel;
        if (channel != null) {
            this.connectCount.increment();
            this.connectTm = System.currentTimeMillis();
        } else {
            this.disconnectCount.increment();
            this.disconnectTm = System.currentTimeMillis();
        }
    }

    /**
     * 중복 연결을 확인하고, 필요한 경우 이전 채널을 닫은 후, 새로운 채널을 설정하
     */
    public synchronized Channel connectChannel(Channel newChannel, TsiAlarmManager alarmManager, String remoteIpAddress) {
        Channel oldChannel = this.channel;

        if (oldChannel != null && oldChannel.isActive() && oldChannel != newChannel) {
            // 중복 연결 로깅 및 이전 채널 닫기
            alarmManager.alarmDupConnect(this.getNodeId(), this.getIpAddr(), remoteIpAddress);
            oldChannel.close();
        }

        setConnect(newChannel);
        return oldChannel;
    }

    /**
     * 현재 비활성화되는 채널이 자신에게 등록된 활성 채널과 동일한 경우에만 연결을 끊도록 함.
     */
    public synchronized boolean disconnectChannel(Channel channelToDisconnect) {
        if (this.channel == channelToDisconnect) {
            setConnect(null);
            return true;
        }
        return false;
    }

    public String getKey() {
        return String.valueOf(this.nodeId);
    }

//    public byte[] getTestPacket() {
//        return null;//this.packet;
//    }
//    public byte[] getCvimPacket() {
//        return null;//this.packet;
//    }
//
//    public void makeNodePacket(int length) {
//        this.nodePacket = null;
//        if (length > TsiCvibProtocolSpec.INDEX_NODE_ID) {
//            // STX1, STX2, LEN(2), OpCode(1), Version(1), NodeId
//            // LEN = LEN ~ CHECK SUM
//            int packetLen = length-2-1-1-2;
//            this.nodePacket = new byte[packetLen];
//            try {
//                //System.arraycopy(this.packet, 6, this.nodePacket, 0, packetLen);
//            }
//            catch(Exception e) {
//            }
//        }
//        else {
//            log.warn("makeNodePacket: The length must be at least {} bytes: {}, {}", TsiCvibProtocolSpec.INDEX_NODE_ID, this.nodeId, length);
//        }
//    }

}
