package com.its.vms.dto;

import com.its.app.AppUtils;
import com.its.app.utils.SysUtils;
import com.its.vms.config.ApplicationConfig;
import com.its.vms.domain.*;
import com.its.vms.domain.enums.eVmsFormType;
import com.its.vms.domain.task.VmsCommandTimeoutTask;
import com.its.vms.entity.TbVmsCtlr;
import com.its.vms.entity.TbVmsCtlrStts;
import com.its.vms.entity.TbVmsCtrlHs;
import com.its.vms.entity.VmsCtlrParam;
import com.its.vms.process.DbmsData;
import com.its.vms.process.DbmsDataProcess;
import com.its.vms.process.DbmsDataType;
import com.its.vms.service.VmsFormService;
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.VmsReqFramePacket;
import com.its.vms.xnettcp.vms.protocol.dto.*;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsFileSaveLocation;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsFormObjectKind;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsImageType;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsOpCode;
import com.its.vms.xnettcp.vms.protocol.impl.*;
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.List;
import java.util.Map;
import java.util.Timer;
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 vmsCtlrNmbr;
    private Long groupNo;
    private Integer 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 cmncfailSlotNmbr;
    private Integer pwerFailSlotNmbr;
    private String cmtrinfrCnctYn;
    private String wthrinfrCnctYn;
    private String envrinfrCnctYn;
    private Integer maxPhaseNum;
    private String istlLctnNm;
    private String istlLctnAddr;
    private String trfcStrgUseYn;
    private Long locIfscId;

    private Integer defPhseChngCycl;    // 기본메시지주기
    private String panlOnTime;          // 전광판 ON TIME
    private String panlOffTime;         // 전광판 OFF TIME
    private Integer panlPwerMode;        // 전광판 전원 모드(0x00:꺼짐,0x01:켜짐,0x02:자동,0x09:알수없음)
    private Integer fanMode;            // FAN 동작모드(0x00:꺼짐,0x01:켜짐,0x02:자동,0x09:알수없음)
    private Integer fanRunTmpr;          // 팬 동작 온도
    private Integer hetrMode;           // 히터 동작모드(0x00:꺼짐,0x01:켜짐,0x02:자동,0x09:알수없음)
    private Integer hetrRunTmpr;         // 히터 동작 온도
    private Integer brghMode;           // 휘도 모드(0x00:주간,0x01:야간,0x02:자동,0x09:수동)
    private Integer brghCurrStep;       // 휘도 현재 단계(0~100)
    private Integer brghWeekStep;       // 휘도 주간 단계(0~100)
    private Integer brghNghtStep;       // 휘도 야간 단계(0~100)
    private Integer modlErrRate;        // VMS 모듈 오류 율
    private Integer cmncFailRate;       // VMS 통신 오류 기본 값

    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 byte txtOpCode;

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

    private Integer downloadFormId;
    private VmsDownloadFile downloadFile;           // 제어기로 다운로드할 파일정보(TB_VMS_DOWNLOAD)
    private VmsDownloadData downloadData;           // 제어기로 다운로드할 파일 데이터
    private ConcurrentHashMap<Integer, VmsDownloadForm> downloadFormMap = null;
    private ConcurrentHashMap<Integer, TbVmsSymbLibDnldDto> dnldSymbMap = null;
    private ConcurrentHashMap<Integer, VmsScheduleSymbDto> reqDnldSymbMap;
    Integer dnldSymbLibNmbr;

    private ConcurrentHashMap<Long, TbVmsRltnIfscDto> rltnIfscMap = null;   // VMS 에 설정된 VMS 정보제공구간 목록

    private ConcurrentHashMap<Short, Timer> registeredCommandTimer = null;
    private ConcurrentHashMap<Short, VmsCommandTimeoutTask> registeredCommand = 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.vmsCtlrNmbr);
        this.seq  = new VmsFrameSequence();

        this.downloadFormMap = new ConcurrentHashMap<>();
        this.dnldSymbMap = new ConcurrentHashMap<>();
        this.reqDnldSymbMap = new ConcurrentHashMap<>();
        this.dnldSymbLibNmbr = 0;

        this.registeredCommandTimer = new ConcurrentHashMap<>();
        this.registeredCommand = 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.downloadFormId = 0;
        this.downloadFile = new VmsDownloadFile();
        this.downloadData = new VmsDownloadData();

        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;

        initDownloadFileInfo();
        initDownloadFormDataInfo();
        initNet();

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

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

    /**
     * 파일 다운로드 정보 초기화
     */
    public void initDownloadFileInfo() {
        this.downloadFile.setEnable(false);
        this.downloadFile.setSaveFlag(true);
        this.downloadFile.setDTimer(System.currentTimeMillis());
        this.downloadFile.setCount(0);
    }

    /**
     * 폼 파일 다운로드 정보를 초기화
     * @return
     */
    public void initDownloadFormDataInfo() {
        this.downloadData.init();
        this.downloadFormMap.forEach((formId, obj) -> {
            obj.setAlreadyDownload(false);
        });
    }

    /**
     * 제어기 전송 패킷의 패킷 번호를 구한다.
     * @return
     */
    public short getSeqNext() {
        return this.seq.nextValue();
    }

    /**
     * 제어기 전송 스레드 타스크 큐에 명령을 전송
     * @param data
     * @return
     */
    public synchronized boolean addRequestData(TcpServerSendData data) {
        return this.dataProcess.add(data);
    }

    /**
     * 제어기 네트워크 최초 접속시 발생 이벤트
     * @param ctx
     */
    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();
        clearRegisteredCommandTimer();
    }

    /**
     * 제어기 네트워크 연결 종료 이벤트
     */
    public synchronized void disconnected() {
        clearRegisteredCommandTimer();
        this.netState = NET.CLOSED;
        this.channel = null;
        this.disConnectTm = SysUtils.getSysTimeStr();
        this.stts.initError();
    }

    public String getLastRecvTimeFmt() {
        SimpleDateFormat sdfDate = new SimpleDateFormat("MM-dd HH:mm:ss");
        return sdfDate.format(this.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 clearRegisteredCommandTimer() {
        this.registeredCommand.clear();
        for (Map.Entry<Short, Timer> e : this.registeredCommandTimer.entrySet()) {
            Timer task = e.getValue();
            task.cancel();
        }
        this.registeredCommandTimer.clear();
    }

    /**
     * 제어기 명령 타임아웃 타스크 추가
     * @param cmdTimeoutTask
     * @return
     */
    public synchronized void addRegisteredCommandsTimer(VmsCommandTimeoutTask cmdTimeoutTask) {
        long timeoutSec = 1000L * 5;
        Timer timer = new Timer();
        timer.schedule(cmdTimeoutTask, timeoutSec);
        this.registeredCommandTimer.put(cmdTimeoutTask.getPacketNmbr(), timer);
        this.registeredCommand.put(cmdTimeoutTask.getPacketNmbr(), cmdTimeoutTask);
//        log.info("addRegisteredCommandsTimer: VMS {}, Task {} EA, OpCode {}.",
//                cmdTimeoutTask.getCtlr().getVmsCtlrNmbr(), this.registeredCommandTimer.size(), cmdTimeoutTask.getPacket().getOpCode());
    }

    /**
     * 제어기 명령 타임아웃 타스크 제거
     * @param opCode
     */
    public synchronized void removeRegisteredCommandsTimer(eVmsOpCode opCode) {
        short packetNmbr = (short)opCode.getValue();
        Timer timer = this.registeredCommandTimer.get(packetNmbr);
        if (timer != null) {
            timer.cancel();
            this.registeredCommandTimer.remove(packetNmbr);
        }
        VmsCommandTimeoutTask cmdTimeoutTask = this.registeredCommand.get(packetNmbr);
        if (cmdTimeoutTask != null) {
            this.registeredCommand.remove(packetNmbr);
//            log.info("removeRegisteredCommandsTimer: VMS {}, Task {} EA, OpCode {}.",
//                    cmdTimeoutTask.getCtlr().getVmsCtlrNmbr(), this.registeredCommandTimer.size(), cmdTimeoutTask.getPacket().getOpCode());
        }
    }

    /**
     * Channel Send Data
     * @param packet
     * @param retryCnt
     * @return
     */
    public synchronized boolean sendData(VmsReqFramePacket packet, int retryCnt) {
        boolean result = false;

        ByteBuffer sendBuffer = packet.getByteBuffer();
        String packetDesc = packet.getOpCodeDesc();

        log.info("~REQUEST-{}. VMS {}, {} Bytes.", packetDesc, this.vmsCtlrNmbr, sendBuffer.capacity());
        if (this.channel != null) {
            ChannelFuture f = this.channel.writeAndFlush(sendBuffer);
            f.awaitUninterruptibly();
            if (f.isDone() || f.isSuccess()) {
                result = true;
            } else {
                log.error("~REQUEST-{}. VMS {}, sendData Failed. {} Bytes.", packetDesc, this.vmsCtlrNmbr, sendBuffer.array().length);
            }
        } else {
            log.error("~REQUEST-{}. VMS {}, sendData Failed. Not Connected. {} Bytes.", packetDesc, this.vmsCtlrNmbr, sendBuffer.array().length);
        }
        if (result) {
            VmsCommandTimeoutTask cmdTimeoutTask = new VmsCommandTimeoutTask(this, packet, retryCnt);
            addRegisteredCommandsTimer(cmdTimeoutTask);
        }
        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 boolean reset() {
        if (getChannel() == null || getNetState() == NET.CLOSED) {
            log.error("Reset Request: channel not connected: [{}]", this);
            return false;
        }
        VmsReqStatusControl resetControl = new VmsReqStatusControl(this);
        resetControl.controlReset();
        return addRequestData(new TcpServerSendData(eVmsOpCode.OP_VMS_STATUS_CONTROL, resetControl));
    }

    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;
    }

    public TbVmsCtlr toDto() {
        return TbVmsCtlr.builder()
                .vmsCtlrNmbr(this.vmsCtlrNmbr)
                .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();
    }

    /**
     * 제어기에 메시지를 표출하기 위한 작업.
     * 1. 제어기에 표출할 폼 정보를 다운로드 한다. - downloadForm
     * 2. 파일 다운로드(이미지, 심볼) - downloadFile
     * 3. 폼 스케쥴 다운로드 - downloadFormSchedule
     * 4. Blank - downloadBlank
     * @return
     */
    public boolean downloadForm() {
        VmsFormService formService = (VmsFormService) AppUtils.getBean(VmsFormService.class);
        this.txtOpCode = (byte)0x00;
        this.downloadData.init();

        int forms = 0;
        int formCnt = this.formManager.count();
        if (formCnt == 0) {
            log.info("downloadForm: VMS {}, Form Data Not Found.", this.vmsCtlrNmbr);
            return true;
        }
        if (formCnt > this.maxPhaseNum) {
            log.error("downloadForm: VMS {}, Form Count Over: {}/{} EA.", this.vmsCtlrNmbr, formCnt, this.maxPhaseNum);
            formCnt = this.maxPhaseNum;
        }

        VmsDownloadData.VmsDownloadDataInfo[] units = this.downloadData.getUnits();
        for (int ii = 0; ii < formCnt; ii++) {
            VmsForm pForm = this.formManager.getItem(ii);
            if (!pForm.isSuccess()) {
                log.error("downloadForm: VMS {}, SEQ {}, SUCCESS {}", this.vmsCtlrNmbr, ii, pForm.isSuccess());
                continue;
            }
            Integer vmsFormId = pForm.getVmsFormId();
            TbVmsFormDto vmsForm = formService.find(vmsFormId);
            if (vmsForm == null) {
                log.error("downloadForm: VMS {}, SEQ {}, FORM ID {} Not Found.", this.vmsCtlrNmbr, ii, vmsFormId);
                continue;
            }

            units[forms].setVmsFormId(vmsFormId);
            units[forms].setDownload(true);
            units[forms].setFormIdx(ii);
            //units[forms].setBitmapId(forms + 1000);
            units[forms].setBitmapId(pForm.getDnldFormNo());
            units[forms].setFormNo(9000 + forms);
            units[forms].setDisplaySec(pForm.getDsplHh());
            units[forms].setDisplayType(pForm.getVmsFormDsplMthdCd());
            units[forms].setBkClr(pForm.getVmsFormColrCd());

            if (vmsForm.getVmsFormTypeCd() == eVmsFormType.eFormTp_hongbo.getValue()) {
                // 다운로드 할 폼이 홍보폼이면 여러번 다운로드 하지 않도록 메모리에서 관리하자.
                VmsDownloadForm downloadForm = this.downloadFormMap.get(vmsFormId);
                if (downloadForm != null) {
                    // 이전에 다운로드한 폼 정보가 존재하면
                    if (!vmsForm.getUpdtDt().equals(downloadForm.getUpdtDt())) {
                        // 업데이트시각이 다르기때문에 신규폼으로 판단한다.
                        downloadForm.setAlreadyDownload(false);
                        downloadForm.setUpdtDt(vmsForm.getUpdtDt());
                        units[forms].setDownload(true); // 현재 스케쥴폼으로 다운로드 플래그 설정
                    }
                    if (downloadForm.isAlreadyDownload()) {
                        // 이미 이전에 다운로드를 했기때문에 이번에는 제어기로 다운로드를 하지 않는다.
                        units[forms].setDownload(false);
                    }
                }
                else {
                    // 이전에 다운로드 한 정보가 없기때문에 메모리에 저장해 놓는다.
                    downloadForm = new VmsDownloadForm();
                    downloadForm.setVmsFormId(vmsFormId);
                    downloadForm.setAlreadyDownload(false);
                    downloadForm.setUpdtDt(vmsForm.getUpdtDt());
                    this.downloadFormMap.put(vmsFormId, downloadForm);
                }
            }

            forms++;
        }

        for (int ii = 0; ii < forms; ii++) {
            log.info("downloadForm: VMS {}, Form schedule({}), VMS_FORM_ID: {}, FormIdx: {}, FormId: {}, BitmapId: {}, IsDownload: {}",
                    this.vmsCtlrNmbr, ii, units[ii].getVmsFormId(), units[ii].getFormIdx(), units[ii].getFormNo(), units[ii].getBitmapId(), units[ii].isDownload());
        }

        this.downloadData.setMaxSchedule(forms);
        this.downloadData.setDownloadCnt(0);
        this.downloadData.setDefault("B".equals(this.operMode));
        if (this.downloadData.isDefault()) {
            // 기본모드인 경우
            // 폼 ID "FID0000" 에 여러개의 폼을 다운로드 해야함.
            //e_vms_download_form          = 0x9A,//    Download Form                           송신    Form download                               센터 → VMS
            //e_vms_file_download          = 0x82,//    데이터 다운로드                         송신    INI, CFG , Bitmap , Data파일송신            센터 → VMS
            //e_vms_download_schedule_form = 0x96,//    Display Schedule Form                   송신    계획된 Form 표출 요구                       센터 → VMS
            //e_vms_blank                  = 0x9E,//    Blank                                   송신    schedule 표출                               센터 → VMS
            downloadDefaultFrom();
        }
        else {
            // 스케쥴 모드인 경우(A:자동, F:고정)
            //e_vms_download_form          = 0x9A,//    Download Form                           송신    Form download                               센터 → VMS
            //e_vms_file_download          = 0x82,//    데이터 다운로드                         송신    INI, CFG , Bitmap , Data파일송신            센터 → VMS
            //e_vms_download_schedule_form = 0x96,//    Display Schedule Form                   송신    계획된 Form 표출 요구                       센터 → VMS
            //e_vms_blank                  = 0x9E,//    Blank                                   송신    schedule 표출                               센터 → VMS
            downloadAutoFixForm();
        }
        return true;
    }

    /**
     * 기본 스케쥴의 폼 정보는 하나의 폼 패킷에 다운로드 해야 여러 폼을 표출할 수 있다.
     * 제어기에서 FID0000 인 폼이 기본 표출폼 이기 때문이다.
     * @return
     */
    public boolean downloadDefaultFrom() {
        // Download Form  (기본폼은 하나의 폼에 여러폼을 다운로드한다.)
        int formCnt = this.downloadData.getMaxSchedule();
        if (formCnt <= 0) {
            log.info("downloadDefaultFrom: VMS {}, Download Schedule Form Count Zero. {} EA.", this.vmsCtlrNmbr, formCnt);
            return true;
        }

        log.info("downloadDefaultFrom: VMS {}, Form {} EA.", this.vmsCtlrNmbr, formCnt);
        VmsFormDataDto formData = new VmsFormDataDto(0, formCnt);
        VmsDownloadData.VmsDownloadDataInfo[] units = this.downloadData.getUnits();
        for (int ii = 0; ii < formCnt; ii++) {
            VmsForm pForm = this.formManager.getItem(units[ii].getFormIdx());
            List<VmsFormObject> objects = pForm.getObjects();
            VmsFormDto formDto = formData.addForm(units[ii].getVmsFormId(), units[ii].getDisplaySec(), units[ii].getDisplayType(), units[ii].getBkClr(), objects.size());
            for (int jj = 0; jj < objects.size(); jj++) {
                VmsFormObject formObj = objects.get(jj);

                int objSize = formObj.getObjectSize();
                int blinking = formObj.getBlinking();
                int posX = formObj.getPosX();
                int posY = formObj.getPosY();
                int bkClr = formObj.getBkColor();

                VmsFormObj formObjDto;
                if (objects.get(jj).getObjectKind() == eVmsFormObjectKind.OBJECT_TEXT) {
                    int clrR = formObj.getFontClr().getRed();
                    int clrG = formObj.getFontClr().getGreen();
                    int clrB = formObj.getFontClr().getBlue();
                    formObjDto = new VmsFormObjText(objSize, blinking, posX, posY, bkClr, clrR, clrG, clrB, formObj.getFontSize(), formObj.getFontNameCd(), formObj.getFontBold(), formObj.getTextData());
                }
                else if (objects.get(jj).getObjectKind() == eVmsFormObjectKind.OBJECT_BITMAP_ID) {
                    formObjDto = new VmsFormObjBitmapId(objSize, blinking, posX, posY, bkClr, formObj.getDsplWidth(), formObj.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), units[ii].getBitmapId());
                }
                else {
                    //formObjDto = new VmsFormObjBitmapId(objSize, blinking, posX, posY, bkClr, object.getDsplWidth(), object.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), units[ii].getBitmapId());
                    formObjDto = new VmsFormObjBitmap(objSize, blinking, posX, posY, bkClr, formObj.getDsplWidth(), formObj.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), formObj.getImageData());
                }
                formObjDto.calSize();
                formDto.addObject(formObjDto);
            }
            formDto.calObjCount();
        }
        formData.calFormCount();

        VmsReqDownloadForm downloadForm = new VmsReqDownloadForm(this, formData);
        return sendData(downloadForm, 1);
    }

    /**
     * 자동 및 고정 스케쥴인 경우 폼정보 하나를 하나의 패킷으로 전송한다.
     * @return
     */
    public boolean downloadAutoFixForm() {
        // Download Form  (패킷하나에 하나의 폼 정보를 다운로드한다.)
        ApplicationConfig config = (ApplicationConfig)AppUtils.getBean(ApplicationConfig.class);
        int formCnt = this.downloadData.getMaxSchedule();
        if (formCnt <= 0) {
            log.info("downloadAutoFixForm: VMS {}, Download Schedule Form Count Zero. {} EA.", this.vmsCtlrNmbr, formCnt);
            return true;
        }
        int downloadCnt = this.downloadData.getDownloadCnt();
        log.info("downloadAutoFixForm: VMS {}, Schedule Form {} EA, Download Count {} EA.", this.vmsCtlrNmbr, formCnt, downloadCnt);
        if (downloadCnt >= formCnt) {
            // 폼정보를 모두 다운로드 했기때문에 폼파일을 다운로드 해야 한다.
            this.downloadData.setDownloadCnt(0); // 폼이미지파일을 다운로드해야 하기때문에 다운로드 인덱스를 다시 0으로 리셋
            return downloadFile();
        }

        // 폼아이디는 제어기가 최초에 접속했을때 한번만 내려보내기 때문에
        // 폼아이디를 여기서 설정해 주어야 한다.
        VmsDownloadData.VmsDownloadDataInfo[] units = this.downloadData.getUnits();
        int formId   = units[downloadCnt].getFormNo();
        int downloadFormCnt = 1;
        int displaySec = 1;//units[downloadCnt].getDisplaySec();
        int displayType = units[downloadCnt].getDisplayType();
        int bitmapId = units[downloadCnt].getBitmapId();
        int formIdx  = units[downloadCnt].getFormIdx();
        VmsForm pForm = this.formManager.getItem(formIdx);
        int objSize = 1;
        int blinking = pForm.getVmsFormDsplMthdCd();
        int posX = 0;
        int posY = 0;
        int bkClr = units[downloadCnt].getBkClr();

        VmsFormDataDto formData = new VmsFormDataDto(formId, downloadFormCnt);
        VmsFormDto formDto = formData.addForm(formId, displaySec, displayType, bkClr, objSize);
        VmsFormObj formObjDto= new VmsFormObjBitmapId(objSize, blinking, posX, posY, bkClr, this.vmsWidth, this.vmsHeight, eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), bitmapId);
        formObjDto.calSize();
        formDto.addObject(formObjDto);
        formData.calFormCount();

        this.downloadData.setDownloadCnt(downloadCnt+1);

        log.info("downloadAutoFixForm: VMS {}, Schedule Form {} EA, Download Count {} EA, VmsFormId {}, FormId {}, BitmapId {}.",
                this.vmsCtlrNmbr, formCnt, downloadCnt+1, pForm.getVmsFormId(), formId, bitmapId);

//        VmsFormDataDto formData = new VmsFormDataDto(formId, downloadFormCnt);
//        int ii = downloadCnt;
//        VmsForm pForm = this.formManager.getItem(units[ii].getFormIdx());
//        List<VmsFormObject> objects = pForm.getObjects();
//        VmsFormDto formDto = formData.addForm(units[ii].getVmsFormId(), units[ii].getDisplaySec(), units[ii].getDisplayType(), units[ii].getBkClr(), objects.size());
//        for (int jj = 0; jj < objects.size(); jj++) {
//            VmsFormObject formObj = objects.get(jj);
//
//            int objSize = formObj.getObjectSize();
//            int blinking = formObj.getBlinking();
//            int posX = formObj.getPosX();
//            int posY = formObj.getPosY();
//            int bkClr = formObj.getBkColor();
//
//            String fileName = String.format("C:\\DRIVE_E\\ANDONG_VMS_FTP\\FORM\\%d_%d_%d-%d-%d-%d-%d.bmp", this.vmsCtlrNmbr, ii, jj, posX, posY, formObj.getDsplWidth(), formObj.getDsplHeight());
//
//            VmsFormObj formObjDto;
//            if (objects.get(jj).getObjectKind() == eVmsFormObjectKind.OBJECT_TEXT) {
//                int clrR = formObj.getFontClr().getRed();
//                int clrG = formObj.getFontClr().getGreen();
//                int clrB = formObj.getFontClr().getBlue();
//                int fontNameCd = formObj.getFontNameCd();
//                formObjDto = new VmsFormObjText(objSize, blinking, posX, posY, bkClr, clrR, clrG, clrB, formObj.getFontSize(), fontNameCd, formObj.getFontBold(), formObj.getTextData());
//            }
//            else if (objects.get(jj).getObjectKind() == eVmsFormObjectKind.OBJECT_BITMAP_ID) {
//                //formObjDto = new VmsFormObjBitmapId(objSize, blinking, posX, posY, bkClr, object.getDsplWidth(), object.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), units[ii].getBitmapId());
//                ItsUtils.saveByteArrayToFile(fileName, formObj.getImageData());
//                formObjDto = new VmsFormObjBitmap(objSize, blinking, posX, posY, bkClr, formObj.getDsplWidth(), formObj.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), formObj.getImageData());
//            }
//            else {
//                //formObjDto = new VmsFormObjBitmapId(objSize, blinking, posX, posY, bkClr, object.getDsplWidth(), object.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), units[ii].getBitmapId());
//                ItsUtils.saveByteArrayToFile(fileName, formObj.getImageData());
//                formObjDto = new VmsFormObjBitmap(objSize, blinking, posX, posY, bkClr, formObj.getDsplWidth(), formObj.getDsplHeight(), eVmsImageType.VMS_IMAGE_TYPE_BMP.getValue(), formObj.getImageData());
//            }
//            formObjDto.calSize();
//            formDto.addObject(formObjDto);
//        }
//        formDto.calObjCount();
//        formData.calFormCount();

        VmsReqDownloadForm downloadForm = new VmsReqDownloadForm(this, formData);
        return sendData(downloadForm, 1);
    }

    /**
     * 폼 이미지 및 기타 폼에 표출할 심볼 이미지가 다운로드 완료가 된후에 스케쥴 정보를 표출하도록 한다.
     * @return
     */
    public boolean downloadFile() {
        int formCnt = this.downloadData.getMaxSchedule();
        if (formCnt <= 0) {
            log.info("downloadFile: VMS {}, Download Schedule Form Count Zero. {} EA.", this.vmsCtlrNmbr, formCnt);
            return true;
        }

        int downloadCnt = this.downloadData.getDownloadCnt();
        log.info("downloadFile: VMS {}, Schedule Form {} EA, Download Count {} EA.", this.vmsCtlrNmbr, formCnt, downloadCnt);
        if (downloadCnt >= formCnt) {
            // 폼파일이(폼이미지파일) 모두 다운로드 되었으므로 스케쥴 정보를 다운로드 한다.
            return downloadFormSchedule();  // Display Schedule Form
        }

        this.downloadFormId = 0;
        ApplicationConfig config = (ApplicationConfig)AppUtils.getBean(ApplicationConfig.class);
        VmsDownloadData.VmsDownloadDataInfo[] units = this.downloadData.getUnits();
        for (int ii = downloadCnt; ii < formCnt; ii++) {
            this.downloadData.setDownloadCnt(downloadCnt+1);

            int vmsFormId = units[ii].getVmsFormId();
            int formId    = units[ii].getFormNo();
            int bitmapId  = units[ii].getBitmapId();
            int formIdx   = units[ii].getFormIdx();
            VmsForm pForm = this.formManager.getItem(formIdx);
            if (config.isCheckNewForm() && !units[ii].isDownload()) {
                // 이미 다운로드 했기 때문에 다시 다운로드 하지 않는다.
                log.info("downloadFile: VMS {}, IDX {}, VmsFormId {}, FormId {}, BitmapId {}, formIdx {}, DownloadFormID: {}, already download...",
                        this.vmsCtlrNmbr, ii, vmsFormId, formId, bitmapId, formIdx, this.downloadFormId));
                continue;
            }

            if (pForm.getVmsFormTypeCd() == eVmsFormType.eFormTp_hongbo.getValue()) {
                // 홈보폼인 경우 다운로드 폼 ID를 저장해 놓는다.
                this.downloadFormId = vmsFormId;
            }

            log.info("downloadFile: VMS {}, Idx {}, VmsFormId {}, FormId {}, BitmapId {}, formIdx {}, DownloadFormID: {}",
                    this.vmsCtlrNmbr, ii, pForm.getVmsFormId(), formId, bitmapId, formIdx, this.downloadFormId);

            String fileName = String.format("BID%04d.BMP", bitmapId);
            eVmsFileSaveLocation saveLoc = eVmsFileSaveLocation.LOC_DOWNLOAD_PROG_IMAGE;

            VmsReqDataDownload dataDownload = new VmsReqDataDownload(this, saveLoc, fileName, pForm.getImageData());
            return sendData(dataDownload, 1);
        }

        // 폼이미지 정보가 모두 다운로드 되었으므로 스케쥴 정보를 다운로드 한다.
        return downloadFormSchedule();  // Display Schedule Form
    }

    /**
     * 제어기에 표출할 폼의 스케쥴 정보를 다운로드 한다.
     * @return
     */
    public boolean downloadFormSchedule() {
        // 도로공사 표준에 최대 10개의 스케쥴을 사용하도록 되어 있음.
        int maxSchedule = VmsConstants.VMS_MIN_DOWNLOAD_FORMS;
        int formCnt = this.downloadData.getMaxSchedule();
        if (formCnt <= 0) {
            log.info("downloadScheduleForm: VMS {}, Download Schedule Form Count Zero. {} EA.", this.vmsCtlrNmbr, formCnt);
            return true;
        }

        short[] formId = new short[maxSchedule];
        byte[] dispSec = new byte[maxSchedule];
        for (int ii = 0; ii < maxSchedule; ii++) {
            formId[ii] = 0;
            dispSec[ii] = 0;
        }

        log.info("downloadFormSchedule: VMS {}, Schedule {} EA, Default Schedule {}.", this.vmsCtlrNmbr, formCnt, this.downloadData.isDefault());
        if (this.downloadData.isDefault()) {
            formId[0] = (short)VmsConstants.DEFAULT_FORM_ID;
            dispSec[0] = 4;
            log.info("downloadFormSchedule: VMS {}, Schedule {} EA, Default Schedule {}.", this.vmsCtlrNmbr, formCnt, this.downloadData.isDefault());
        }
        else {
            for (int ii = 0; ii < formCnt && ii < maxSchedule; ii++) {
                formId[ii] = (short) this.downloadData.getUnits()[ii].getFormNo();
                dispSec[ii] = (byte)(this.downloadData.getUnits()[ii].getDisplaySec() & 0xFF);
                log.info("downloadFormSchedule: VMS {}, Schedule {} EA, Idx {}, FormId {}, DisplayTm {}.", this.vmsCtlrNmbr, formCnt, ii+1, formId[ii], dispSec[ii]);
            }
        }

        VmsReqDownloadFormSchedule downloadFormSchedule = new VmsReqDownloadFormSchedule(this, formId, dispSec);
        return sendData(downloadFormSchedule, 1);
    }

    /**
     * 스케쥴을 표출하도록 Blank 명령을 전송한다.
     * @return
     */
    public boolean downloadBlank() {
        log.info("downloadBlank: VMS {}.", this.vmsCtlrNmbr);
        VmsReqBlank reqBlank = new VmsReqBlank(this);
        return sendData(reqBlank, 1);
    }

    /**
     * 제어기로 심벌라이브러리를 다운로드한다.
     * @return
     */
    public boolean downloadSymbLib() {
        this.dnldSymbLibNmbr = 0;
        if (this.reqDnldSymbMap.size() == 0) {
            return false;
        }

        VmsScheduleSymbDto dnldSymb = null;
        for (Map.Entry<Integer, VmsScheduleSymbDto> e : this.reqDnldSymbMap.entrySet()) {
            dnldSymb = e.getValue();
            break;
        }

        if (dnldSymb != null) {
            this.dnldSymbLibNmbr = dnldSymb.getDnldSymbLibNmbr();
            String fileName = String.format("BID%04d.BMP", this.dnldSymbLibNmbr);
            eVmsFileSaveLocation saveLoc = eVmsFileSaveLocation.LOC_DOWNLOAD_PROG_IMAGE;

            VmsReqDataDownload dataDownload = new VmsReqDataDownload(this, saveLoc, fileName, dnldSymb.getImageData());
            sendData(dataDownload, 1);
            return true;
        }
        return false;
    }
/*
    private int   powerCtrlMode;  // 전원제어 모드                1 Byte  0x00 : 꺼짐, 0x01 : 켜짐,
    private int   fanRunMode;     // Fan 동작모드                 1 Byte  0x00 : 꺼짐, 0x01 : 켜짐, 0x02 : 자동, 0x09: Unknown
    private int   fanRunTemp;     // Fan 의 동작개시온도          1 Byte  온도( 0x00 ~0x03f )
    private int   heaterRunMode;  // Heater 의 동작모드           1 Byte  0x00 : 꺼짐, 0x01 : 켜짐, 0x02 : 자동, 0x09: Unknown
    private int   heaterRunTemp;  // Heater 의 동작개시온도       1 Byte  온도(0x00 ~ 0x3f )
    private int   brightMode; // 화면의 밝기 - 휘도 모드, 0x00:주간, 0x01:야간, 0x02:Auto, 0x03:수동
    private int   brightCurr; // 화면의 밝기 - 현재 휘도값, 0～100
    private int   brightWeek; // 화면의 밝기 - 주간 휘도값, 0～100
    private int   brightNght; // 화면의 밝기 - 야간 휘도값, 0～100
    private int   blinkTime;      // 깜빡이는 시간주기            1 Byte  문자 비트맵의 깜빡이는 시간주기 0x00 ~ 0x1e( 0.1 ~3.0 )
    private short scnTurnTime;    // 디폴트 시나리오로 전환시간   2 Byte  디폴트 시나리오로 전환될 때까지 기다리는 시간, 단위 : 초
    private int   modlErrRate;    // 장애 모듈비율(%)             1 Byte  에러난 픽셀의 백분율 (0～100: 초기값:10%)
*/
    public void updateParameter(int powerCtrlMode, int fanRunMode, int fanRunTemp, int heaterRunMode, int heaterRunTemp,
                                int brightMode, int brightCurr, int brightWeek, int brightNght, int blinkTime, short scnTurnTime, int modlErrRate) {
        boolean isDifferent = false;

        if (powerCtrlMode != this.panlPwerMode) {
            isDifferent = true;
            this.panlPwerMode = powerCtrlMode;
        }
        if (fanRunMode != this.fanMode) {
            isDifferent = true;
            this.fanMode = fanRunMode;
        }
        if (fanRunTemp != this.fanRunTmpr) {
            isDifferent = true;
            this.fanRunTmpr = fanRunTemp;
        }
        if (heaterRunMode != this.hetrMode) {
            isDifferent = true;
            this.hetrMode = heaterRunMode;
        }
        if (heaterRunTemp != this.hetrRunTmpr) {
            isDifferent = true;
            this.hetrRunTmpr = heaterRunTemp;
        }

        if (brightMode != this.brghMode) {
            isDifferent = true;
            this.brghMode = brightMode;
        }
        if (brightCurr != this.brghCurrStep) {
            isDifferent = true;
            this.brghCurrStep = brightCurr;
        }
        if (brightWeek != this.brghWeekStep) {
            isDifferent = true;
            this.brghWeekStep = brightWeek;
        }
        if (brightNght != this.brghNghtStep) {
            isDifferent = true;
            this.brghNghtStep = brightNght;
        }

        if (scnTurnTime != this.defPhseChngCycl) {
            isDifferent = true;
            this.defPhseChngCycl = (int)scnTurnTime;
        }
        if (modlErrRate != this.modlErrRate) {
            isDifferent = true;
            this.modlErrRate = modlErrRate;
        }

        if (isDifferent) {
            DbmsDataProcess dbmsDataProcess = (DbmsDataProcess) AppUtils.getBean(DbmsDataProcess.class);
            dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_CTLR_PARAMETER, false, toParamEntity()));
        }
    }

    public VmsCtlrParam toParamEntity() {
        return VmsCtlrParam.builder()
                .vmsCtlrNmbr(this.vmsCtlrNmbr)
                .panlPwerMode(this.panlPwerMode)
                .fanMode(this.fanMode)
                .fanRunTmpr(this.fanRunTmpr)
                .hetrMode(this.hetrMode)
                .hetrRunTmpr(this.hetrRunTmpr)
                .brghMode(this.brghMode)
                .brghCurrStep(this.brghCurrStep)
                .brghWeekStep(this.brghWeekStep)
                .brghNghtStep(this.brghNghtStep)
                .modlErrRate(this.modlErrRate)
                .cmncFailRate(this.cmncFailRate)
                .build();
    }

}

