package com.its.vms.entity;

import com.its.app.utils.SysUtils;
import com.its.vms.dto.NET;
import com.its.vms.dto.TbVmsCtlrSttsDto;
import com.its.vms.xnettcp.vms.handler.VmsServerIdleStateHandler;
import com.its.vms.xnettcp.vms.protocol.VmsFrameSequence;
import com.its.vms.xnettcp.vms.protocol.enums.eControlCommand;
import com.its.vms.xnettcp.vms.protocol.enums.eControlDeviceId;
import com.its.vms.xnettcp.vms.task.VmsTimeoutTask;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import lombok.*;
import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor//(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class TbVmsCtlr implements Serializable {
    private static final long serialVersionUID = 1L;

    private int  index;
    private Long ctlrNmbr;
    private String ctlrLocalNo;
    private String ctlrId;
    private String name;
    private String ctlrIp;
    private String ctlrPort;
    private String usagTypeCd;
    private String frmTypeCd;
    private String typeCd;
    private String modlTypeCd;
    private String operMode;
    private Integer cmncFailRate;
    private Integer brghNghtStep;
    private Integer brghWeekStep;
    private Integer defPhseChngCycl;
    private Integer modlErrRate;
    private Integer cmncfailSlotNmbr;
    private Integer pwerFailSlotNmbr;
    private String cmtrinfrCnctYn;
    private String wthrinfrCnctYn;
    private String envrinfrCnctYn;
    private Integer maxPhseNum;
    private String panlOnTime;
    private String panlOffTime;
    private String panlPwerMode;
    private String brghMode;
    private String istlLctnNm;
    private String trfcStrgUseYn;
    private Long locIfscId;
    private Double fanRunTmpr;
    private Double hetrRunTmpr;
    private Integer fanMode;
    private Integer hetrMode;
    private Integer brghCurrStep;
    private String istlLctnAddr;
    private Integer protocolVer;
    private String delYn;

    private int vmsWidth;
    private int vmsHeight;
    private int modlRowNum;
    private int modlColNum;
    private int powrRowNum;
    private int powrColNum;

    private String localFormDir;
    private String ftpFormDir;
    private boolean isFtpDownload;
    private boolean isSymbolDownload;


    private TbVmsCtlrSttsDto stts;
    private int 			  netState;
    private boolean           isDupCon;
    private boolean           isDupLogin;
    private String 		      dstIpAddr;
    private Channel channel;
    private Channel           dupChannel;
    private InetSocketAddress cliReq;
    private long              syncTime;
    private VmsFrameSequence seq;

    private ConcurrentHashMap<Long, TbVmsRltnIfsc> rltnIfscMap = null;

    //private ConcurrentHashMap<Long, C2CAuthenticatedMessage> registeredCommands = null;
    private ConcurrentHashMap<Long, Timer> registeredCommandsTimer = null;
    private ConcurrentHashMap<Long, TbRseCtrlHs> userCommands = null;
    private ConcurrentHashMap<String, TbRseOffrSect> offrSectMap = null;

    private int connectCount;
    private String connectTm;
    private String disConnectTm;
    private long lastRecvTime;

    public void init() {
        this.stts = new TbVmsCtlrSttsDto(this.ctlrNmbr);
        this.seq  = new VmsFrameSequence();
        this.registeredCommandsTimer = new ConcurrentHashMap<>();
        this.userCommands = new ConcurrentHashMap<>();
        this.offrSectMap = new ConcurrentHashMap<>();
        this.rltnIfscMap = new ConcurrentHashMap<>();

        this.connectCount = 0;
        this.connectTm = "-";
        this.disConnectTm = "-";
        this.lastRecvTime = 0;

        this.isFtpDownload = false;
        this.isSymbolDownload = false;

        initNet();
    }

    public synchronized void connected(ChannelHandlerContext ctx) {
        this.netState = NET.LOGIN_REQ;
        this.connectTm = SysUtils.getSysTimeStr();
        this.channel = ctx.channel();
        this.lastRecvTime = System.currentTimeMillis();
        this.connectCount++;
        this.stts.initNormal();
    }

    public synchronized void disconnected() {
        this.netState = NET.CLOSED;
        //this.registeredCommands.clear();
        this.registeredCommandsTimer.clear();
        this.channel = null;
        this.stts.initError();
        this.disConnectTm = SysUtils.getSysTimeStr();
        this.stts.initError();
    }

    public String getLogKey() {
        return this.ctlrId;
    }

    public String getLastRecvTimeFmt() {
        SimpleDateFormat sdfDate = new SimpleDateFormat("MM-dd HH:mm:ss");
        return sdfDate.format(lastRecvTime);
    }

    public void resetConnectCount() {
        if (this.netState == NET.CLOSED)
            this.connectCount = 0;
        else
            this.connectCount = 1;
    }

    void initNet() {
        this.netState   = NET.CLOSED;
        this.isDupCon   = false;
        this.isDupLogin = false;
        this.dstIpAddr  = "";
        this.channel    = null;
        this.dupChannel = null;
        this.cliReq     = null;
        this.syncTime   = 0;
    }

    public synchronized void channelLogin(Channel channel) {
        this.channel = channel;

        //this.registeredCommands.clear();
        for (Map.Entry<Long, Timer> e : this.registeredCommandsTimer.entrySet()) {
            Timer task = e.getValue();
            task.cancel();
        }
        this.registeredCommandsTimer.clear();

        this.netState = NET.LOGINED;
    }
    public synchronized void channelLoginInit() {
        //this.registeredCommands.clear();
        for (Map.Entry<Long, Timer> e : this.registeredCommandsTimer.entrySet()) {
            Timer task = e.getValue();
            task.cancel();
        }
        this.registeredCommandsTimer.clear();

        this.netState = NET.LOGINED;
    }

    public void sleep(long milliSeconds) {
        try {
            Thread.sleep(milliSeconds);
        } catch (InterruptedException e) {
            log.error("sleep: InterruptedException");
        }
    }

    /**
     * Channel Send Data
     * @param sendBuffer
     * @param delayMilliSeconds
     * @param packetDesc
     * @return
     */
    public boolean sendData(ByteBuffer sendBuffer, int delayMilliSeconds, String packetDesc) {
        boolean result = false;
        if (this.channel != null) {
            ChannelFuture f = this.channel.writeAndFlush(sendBuffer);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                result = true;
                if (delayMilliSeconds > 0) {
                    sleep(delayMilliSeconds);
                }
            } else {
                log.error("[{}]. sendData: Failed. {}, {} Bytes.", this.ctlrNmbr, packetDesc, sendBuffer.array().length);
            }
        } else {
            log.error("[{}]. sendData: Failed. Not Connected. {}, {} Bytes.", this.ctlrNmbr, packetDesc, sendBuffer.array().length);
        }
        return result;
    }

    public boolean channelClose() {
        if (getChannel() == null || getNetState() == NET.CLOSED) {
            log.error("Close Request: channel not connected: [{}]", this);
            return false;
        }
        VmsServerIdleStateHandler.disconnectChannel(this, getChannel());
        return true;
    }

    public int reset(int devcType, int cntlType, TbRseCtrlHs ctrlHs) {
        if (getChannel() == null || getNetState() == NET.CLOSED) {
            log.error("Reset Request: channel not connected: [{}]", this);
            return 1;
        }
        eControlDeviceId controlDeviceId = eControlDeviceId.getByValue(devcType);
        eControlCommand commandType = eControlCommand.getByValue(cntlType);
        if (controlDeviceId == null || commandType == null) {
            log.error("Reset Request: control type missMatched: [{}], {}, {}", this, controlDeviceId, commandType);
            return 2;
        }

        log.warn("[{}] [{},{}], controlDeviceId: {}, commandType: {}", this.ctlrNmbr, devcType, cntlType, controlDeviceId.toString(), commandType.toString());
//		boolean res = ControlDeviceService.getInstance().requestResetCommand(true, this, getChannel());
//		if (res) {
//			log.info("RSE REQ CONTROL SEND OK: [{}] [{},{}]", this.RSE_CTLR_NMBR, devcType, cntlType);
//			return 0;
//		}
        log.error("RSE REQ CONTROL SEND Error: [{}] [{},{}]", this.ctlrNmbr, devcType, cntlType);
        return 3;
    }

    public synchronized boolean addUserCommands(Long packetNmbr, TbRseCtrlHs command) {

        this.userCommands.put(packetNmbr, command);
        return addCommandTimer(packetNmbr);
    }

    public synchronized boolean removeUserCommands(Long packetNmbr) {

        TbRseCtrlHs command = this.userCommands.get(packetNmbr);
        if (command != null) {
            this.userCommands.remove(packetNmbr);
            return true;
        }
        return false;
    }

    public synchronized TbRseCtrlHs getUserCommands(Long packetNmbr) {

        TbRseCtrlHs command = this.userCommands.get(packetNmbr);
        return command;
    }

    private boolean addCommandTimer(Long packetNmbr) {

        long timeoutSec = 10;//this.login.getDatexLoginResponseTimeOutQty().value.longValue();
        if (timeoutSec == 0L || timeoutSec > 30L) {
            timeoutSec = 30L;
        }
        if (timeoutSec < 5L)
            timeoutSec = 5L;

        timeoutSec = 1000L * timeoutSec;
        Timer timer = new Timer();
        timer.schedule((TimerTask)new VmsTimeoutTask(this, packetNmbr), timeoutSec);
        this.registeredCommandsTimer.put(packetNmbr, timer);

        log.info("addCommandTimer: [{}], packetNmbr: {}, timeoutSec: {} ms, {}", this.ctlrNmbr, packetNmbr, timeoutSec, timer);
        return true;
    }

//	public synchronized boolean putRegisteredCommands(C2CAuthenticatedMessage c2c) {
//
//		if (this.login == null || this.headerOptions == null) {
//			return false;
//		}
//		PDUs pdUs = c2c.getPdu();
//		if (pdUs.getSubscription() == null && pdUs.getPublication() == null) {
//			// Subscription 또는 Publication 요청에 대해서만 타임아웃 체크
//			return false;
//		}
//
//		Long packetNmbr = c2c.getDatexDataPacketNumber().value.longValue();
//		this.registeredCommands.put(packetNmbr, c2c);
//		return addCommandTimer(packetNmbr);
//	}

    public synchronized boolean removeRegisteredCommandsTimer(int packetNmbr) {

        Timer timer = this.registeredCommandsTimer.get(Integer.valueOf(packetNmbr));
        if (timer != null) {
            this.registeredCommandsTimer.remove(Integer.valueOf(packetNmbr));
        }
        return true;
    }

    public synchronized boolean removeRegisteredCommands(Long packetNmbr, boolean cancelTimer) {

        Timer timer = this.registeredCommandsTimer.get(packetNmbr);
        if (timer != null) {
            if (cancelTimer)
                timer.cancel();
            this.registeredCommandsTimer.remove(packetNmbr);
        }

//		C2CAuthenticatedMessage c2c = this.registeredCommands.get(packetNmbr);
//		if (c2c != null) {
//			this.registeredCommands.remove(packetNmbr);
//		}
        return false;
    }

//	public synchronized C2CAuthenticatedMessage getRegisteredCommands(int packetNmbr) {
//		C2CAuthenticatedMessage c2c = this.registeredCommands.get(packetNmbr);
//		return c2c;
//	}

    public synchronized boolean sendFrED() {
        if (getChannel() == null || getNetState() == NET.CLOSED) {
            log.error("sendFrED Request: channel not connected: [{}]", this);
            return false;
        }
        return true;
        //return FredResponse.sendFrED(this, getChannel());
    }
}
