package com.its.vms.dto;

import com.its.app.utils.SysUtils;
import com.its.vms.domain.*;
import com.its.vms.entity.TbVmsCtlr;
import com.its.vms.entity.TbVmsCtlrStts;
import com.its.vms.entity.TbVmsCtrlHs;
import com.its.vms.xnettcp.vms.handler.VmsServerIdleStateHandler;
import com.its.vms.xnettcp.vms.process.TcpServerSendData;
import com.its.vms.xnettcp.vms.process.TcpServerSendDataProcess;
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.*;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor//(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class TbVmsCtlrDto 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 maxPhaseNum;
    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 ftpDownload;
    private boolean symbolDownload;

    private int maxCngstForm;        // 정체폼 생성 최대 갯수
    private int cngstForms;
    private boolean existCngsForm;
    private boolean provide;
    private boolean evehIngForm;
    private boolean evehEndForm;

    private VmsFormManager formManager;

    /**
     * VMS 메시지 생성 메모리
     */
    private VmsEvehicle evechile;
    private VmsSchedule schedule;   // 스케쥴 정보
    private VmsEvent incident;      // 돌발정보
    private VmsEvent event;         // 공사행사정보

    private VmsModule module;
    private VmsLuminance luminance;
    private VmsControlMode controlMode;
    private VmsDownload download;

    private TbVmsCtlrStts     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, TbVmsRltnIfscDto> rltnIfscMap = null;

    //private ConcurrentHashMap<Long, C2CAuthenticatedMessage> registeredCommands = null;
    private ConcurrentHashMap<Long, Timer> registeredCommandsTimer = null;
    private ConcurrentHashMap<Long, TbVmsCtrlHs> userCommands = null;

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

    private TcpServerSendDataProcess dataProcess;

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

        this.formManager = new VmsFormManager(this.vmsWidth, this.vmsHeight);
        this.evechile = new VmsEvehicle();
        this.schedule = new VmsSchedule();   // 스케쥴 정보
        this.incident = new VmsEvent();      // 돌발정보
        this.event = new VmsEvent();         // 공사행사정보

        this.module = new VmsModule();
        this.luminance = new VmsLuminance();
        this.controlMode = new VmsControlMode();
        this.download = new VmsDownload();

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

        this.ftpDownload = false;
        this.symbolDownload = false;
        this.maxCngstForm = VmsConstants.VMS_MAX_CNGS_FORM;
        this.cngstForms = 0;
        this.evehIngForm = false;
        this.evehEndForm = false;

        initNet();

        this.dataProcess = new TcpServerSendDataProcess(this);
        this.dataProcess.run();
    }

    public void addRequestData(TcpServerSendData data) {
        this.dataProcess.add(data);
    }

    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, TbVmsCtrlHs 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, TbVmsCtrlHs command) {

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

    public synchronized boolean removeUserCommands(Long packetNmbr) {

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

    public synchronized TbVmsCtrlHs getUserCommands(Long packetNmbr) {

        TbVmsCtrlHs 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());
    }

    public TbVmsCtlr toDto() {
        return TbVmsCtlr.builder()
                .vmsCtlrNmbr(this.ctlrNmbr)
                .vmsCtlrLocalNo(this.ctlrLocalNo)
                .vmsCtlrId(this.ctlrId)
                .vmsNm(this.name)
                .vmsCtlrIp(this.ctlrIp)
                .vmsUsagTypeCd(this.usagTypeCd)
                .vmsFrmTypeCd(this.frmTypeCd)
                .vmsTypeCd(this.typeCd)
                .vmsModlTypeCd(this.modlTypeCd)
                .vmsCtlrPort(this.ctlrPort)
                .operMode(this.operMode)
                .cmncFailRate(this.cmncFailRate)
                .brghNghtStep(this.brghNghtStep)
                .brghWeekStep(this.brghWeekStep)
                .defPhseChngCycl(this.defPhseChngCycl)
                .modlErrRate(this.modlErrRate)
                .cmncfailSlotNmbr(this.cmncfailSlotNmbr)
                .pwerFailSlotNmbr(this.pwerFailSlotNmbr)
                .delYn(this.delYn)
                .cmtrinfrCnctYn(this.cmtrinfrCnctYn)
                .wthrinfrCnctYn(this.wthrinfrCnctYn)
                .envrinfrCnctYn(this.envrinfrCnctYn)
                .vmsMaxPhseNum(this.maxPhaseNum)
                .panlOnTime(this.panlOnTime)
                .panlOffTime(this.panlOffTime)
                .panlPwerMode(this.panlPwerMode)
                .brghMode(this.brghMode)
                .istlLctnNm(this.istlLctnNm)
                .trfcStrgUseYn(this.trfcStrgUseYn)
                .vmsLocIfscId(this.locIfscId)
                .fanRunTmpr(this.fanRunTmpr)
                .hetrRunTmpr(this.hetrRunTmpr)
                .fanMode(this.fanMode)
                .hetrMode(this.hetrMode)
                .brghCurrStep(this.brghCurrStep)
                .istlLctnAddr(this.istlLctnAddr)
                .protocolVer(this.protocolVer)
                .build();
    }
}
