package com.its.dsrc.entity;

import com.beanit.asn1dsrc.dsrc.C2CAuthenticatedMessage;
import com.beanit.asn1dsrc.dsrc.HeaderOptions;
import com.beanit.asn1dsrc.dsrc.Login;
import com.beanit.asn1dsrc.dsrc.PDUs;
import com.beanit.asn1dsrc.enums.eControlCommand;
import com.beanit.asn1dsrc.enums.eControlDeviceId;
import com.beanit.asn1dsrc.util.DsrcAsn1Sequence;
import com.its.app.utils.SysUtils;
import com.its.dsrc.vo.NET;
import com.its.dsrc.xnettcp.dsrc.handler.DsrcAsn1ServerIdleStateConnectionHandler;
import com.its.dsrc.xnettcp.dsrc.process.response.FredResponse;
import com.its.dsrc.xnettcp.dsrc.process.service.ControlDeviceService;
import com.its.dsrc.xnettcp.dsrc.task.DsrcAsn1TimeoutTask;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

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
public class TbRseCtlr {

	private int    index;
	private String RSE_CTLR_NMBR;
	private String RSE_CTLR_ID;
	private String RSE_NM;
	private String RSE_CTLR_IP;
	private int RSE_CTLR_PORT;
	private int RSE_CTLR_LOCAL_NO;
	private String RSE_TYPE_CD;
	private String RSE_MCU_ID;
	private String LOG_CNNC_ID;
	private String LOG_CNNC_PW;
	private int    MNFC_CMPY_CD;
	private int    CLCT_ABNR_BASI;
	private String DEL_YN;

	private TbRseCtlrStts     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 String            LOG_ID;
	private DsrcAsn1Sequence seq;
	private HeaderOptions headerOptions = null;
	private Login login = 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 Integer getId() {
		return Integer.parseInt(this.RSE_CTLR_NMBR);
	}

	public TbRseCtlr() {

		this.stts = new TbRseCtlrStts();
		this.seq  = new DsrcAsn1Sequence();
		this.registeredCommands = new ConcurrentHashMap<Long, C2CAuthenticatedMessage>();
		this.registeredCommandsTimer = new ConcurrentHashMap<Long, Timer>();
		this.userCommands = new ConcurrentHashMap<Long, TbRseCtrlHs>();
		this.offrSectMap = new ConcurrentHashMap<String, TbRseOffrSect>();

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

		initNet();
	}
	public void setLastRecvTime() {
		this.lastRecvTime = System.currentTimeMillis();
//		MainUI mainUI = MainUI.getInstance();
//		if (mainUI != null) {
//			mainUI.updateCtlrStts(this);
//		}
	}
	public String getLastRecvTimeFmt() {
		SimpleDateFormat sdfDate = new SimpleDateFormat("MM-dd HH:mm:ss");
		return sdfDate.format(lastRecvTime);
	}

	public String getID() {
		return this.RSE_CTLR_NMBR;
	}
	public String getMcuID() {
		return this.RSE_MCU_ID;
	}
	public String getRSE_ID() {
		return this.RSE_CTLR_ID;
	}
	public String getLogKey() {
		return this.RSE_MCU_ID;
	}
	public void resetConnectCount() {
		if (this.netState == NET.CLOSED)
			this.connectCount = 0;
		else
			this.connectCount = 1;
	}
	public void setConnectTm() {
		this.connectTm = SysUtils.getSysTimeStr();
		this.connectCount++;
	}
	public void setDisConnectTm() {
		this.disConnectTm = SysUtils.getSysTimeStr();
	}

	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, HeaderOptions headerOptions, Login login) {
		this.channel = channel;
		this.headerOptions = headerOptions;
		this.login = login;

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

	public synchronized void channelLogout() {
		this.netState = NET.CLOSED;
		this.registeredCommands.clear();
		this.registeredCommandsTimer.clear();
		this.headerOptions = null;
		this.login = null;
		this.channel = null;
		getStts().initError();
		setDisConnectTm();
	}

	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.RSE_CTLR_NMBR, packetDesc, sendBuffer.array().length);
			}
		} else {
			log.error("[{}]. sendData: Failed. Not Connected. {}, {} Bytes.", this.RSE_CTLR_NMBR, 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;
		}
		DsrcAsn1ServerIdleStateConnectionHandler.disconnectChannel(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.RSE_CTLR_NMBR, devcType, cntlType, controlDeviceId.toString(), commandType.toString());
		boolean res = ControlDeviceService.getInstance().requestSubscriptionDeviceCommand(true, this, getChannel(), controlDeviceId.getValue(), commandType.getValue(), ctrlHs);
		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.RSE_CTLR_NMBR, 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 = 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 DsrcAsn1TimeoutTask(this, packetNmbr), timeoutSec);
		this.registeredCommandsTimer.put(packetNmbr, timer);

		log.info("addCommandTimer: [{}], packetNmbr: {}, timeoutSec: {} ms, {}", this.RSE_CTLR_NMBR, 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 FredResponse.sendFrED(this, getChannel());
	}

	public int getManufacturerid() {
		return 0;
	}

	public int getIndividualid() {
		return 0;
	}
}
