package com.its.vms.service;

import com.its.app.utils.Elapsed;
import com.its.app.utils.FloodFill;
import com.its.app.utils.ItsUtils;
import com.its.vms.config.ApplicationConfig;
import com.its.vms.dao.mapper.VmsManageMapper;
import com.its.vms.domain.VmsConstants;
import com.its.vms.domain.VmsForm;
import com.its.vms.domain.VmsFormObject;
import com.its.vms.domain.VmsSchedule;
import com.its.vms.domain.enums.*;
import com.its.vms.dto.*;
import com.its.vms.entity.*;
import com.its.vms.process.DbmsData;
import com.its.vms.process.DbmsDataProcess;
import com.its.vms.process.DbmsDataType;
import com.its.vms.xnettcp.vms.process.TcpServerRecvDataProcess;
import com.its.vms.xnettcp.vms.process.TcpServerSendData;
import com.its.vms.xnettcp.vms.protocol.VmsProtocolConst;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsFormObjectKind;
import com.its.vms.xnettcp.vms.protocol.enums.eVmsOpCode;
import com.its.vms.xnettcp.vms.protocol.impl.VmsReqStatusControl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class VmsManageService {

    private final ApplicationConfig config;
    private final VmsManageMapper mapper;
    private final AppRepositoryService repoService;
    private final VmsFontService fontService;
    private final VmsSymbService symbService;
    private final VmsFormService formService;
    private final VmsIfscService ifscService;
    private final VmsAtmpService atmpService;
    private final VmsParkService parkService;
    private final DbmsDataProcess dbmsDataProcess;

    private final TcpServerRecvDataProcess serverDataProcess;
    private int toggleFormSeq = 1;

    public void initVmsDsplPrst() {
        HashMap<String, Object> param = new HashMap<>();
        param.put("maxPhase",  this.config.getMaxDownloadForms()+1);
        this.mapper.initVmsDsplPrst(param);
    }

    /**
     * 전광판 On/Off 시각 정보 읽어서 전광판 제어
     */
    public void loadVmsOnOffTime() {
        Elapsed elapsed = new Elapsed();
        log.info("VmsManageService.loadVmsOnOffTime: START.");
        int nCurrTime = ItsUtils.parseIntDef(ItsUtils.getSysHourMin(), 0);
        String sysDay = ItsUtils.getSysDay();

        List<TbVmsOnOffTime> result = this.mapper.selectVmsOnOffTime();
        result.forEach(obj -> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(obj.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsManageService.loadVmsOnOffTime: VMS Not Found {}.", obj.getVmsCtlrNmbr());
                return;
            }

            String onTime = obj.getPanlOnTime();
            String offTime = obj.getPanlOffTime();
            if ("9999".equals(onTime) || onTime == null || onTime.length() != 4) {
                onTime = "0000";
            }
            if ("9999".equals(offTime) || offTime == null || offTime.length() != 4) {
                offTime = "0000";
            }

            byte oldOnOff = vmsObj.getModule().getOnOff();
            byte controlOnOff = eVmsReqBoardPower.vms_req_board_power_on.getValue();
            vmsObj.getModule().setPowerOnTime(sysDay + onTime);
            vmsObj.getModule().setPowerOffTime(sysDay + offTime);

            int nOnTime = ItsUtils.parseIntDef(onTime, 0);
            int nOffTime = ItsUtils.parseIntDef(offTime, 0);
            if (nOnTime != nOffTime) {
                // 점등 시각과 소등시각이 다른 경우
                if (nOnTime > nOffTime) {
                    // 0100(off), 0500(on)
                    if (nCurrTime >= nOffTime && nCurrTime < nOnTime) {
                        controlOnOff = eVmsReqBoardPower.vms_req_board_power_off.getValue();
                    }
                }
                else {
                    // 2300(off), 0500(on)
                    if (nCurrTime >= nOffTime || nCurrTime < nOnTime) {
                        controlOnOff = eVmsReqBoardPower.vms_req_board_power_off.getValue();
                    }
                }
            }
            if (!Objects.equals(controlOnOff, oldOnOff)) {
                VmsReqStatusControl signboardControl = new VmsReqStatusControl(vmsObj);
                signboardControl.controlSignboardPower(controlOnOff);
                vmsObj.addRequestData(new TcpServerSendData(eVmsOpCode.OP_VMS_STATUS_CONTROL, signboardControl));
            }
        });
        log.info("VmsManageService.loadVmsOnOffTime: ..END. {} ms.", elapsed.milliSeconds());
    }

    /**
     * VMS 기초 정보 로딩
     */
    public void loadDb() {
        Elapsed elapsed = new Elapsed();
        log.info("VmsManageService.loadBaseDatabase: START.");
        this.fontService.loadDb();
        this.symbService.loadDb();
        this.formService.loadDb();
        log.info("VmsManageService.loadBaseDatabase: ..END. {} ms.", elapsed.milliSeconds());
    }

    /**
     * 돌발 및 공사/행사 정보 조회
     */
    public void loadEventOccrInfo() {
        Elapsed elapsed = new Elapsed();
        List<TbVmsIncd> result = this.mapper.selectVmsIncd();
        result.forEach(data -> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(data.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsManageService.loadEventOccrInfo: Not Found VMS {}, Incident {}.", data.getVmsCtlrNmbr(), data.getIncdOcrrId());
                return;
            }

//            if (!vmsObj.isProvide()) {
//                log.error("VmsManageService.loadEventOccrInfo: VMS {}, Provide {}.", data.getVmsCtlrNmbr(), vmsObj.isProvide());
//                return;
//            }

            if ("VIT1".equals(data.getVmsIncdTypeCd())) {
                // 사고
                vmsObj.getIncident().add(data.toDto());
            }
            else {
                // 공사/행사
                vmsObj.getEvent().add(data.toDto());
            }
        });
        log.info("VmsManageService.loadEventOccrInfo: {} ms.", elapsed.milliSeconds());
    }

    /**
     * VMS 정보제공구간 정체 판정
     */
    public void congestJudgment() {
        this.repoService.getCtlrMap().forEach((key, obj) -> {
            obj.getRltnIfscMap().forEach((ifscId, ifsc) -> {
                if (!ifsc.isUsed()) {
                    return; // 사용안함
                }
                if (!ifsc.isCngstCnfmYn()) {
                    return; // 정체판정이 아닌경우
                }

                TbVmsIfscTrafDto ifscTraf = this.ifscService.find(ifsc.getVmsIfscId());
                if (ifscTraf == null) {
                    log.error("VmsManageService.congestJudgment: VMS {}, Not Found VMS IFSC TRAF {}.", ifsc.getVmsCtlrNmbr(), ifsc.getVmsIfscId());
                    ifsc.setCngstCnt(0);
                    return;
                }

                if (ifsc.getCngstSped() > ifscTraf.getSped()) {
                    // 현재속도값이 정체판정속도 이하이면 정체판정 횟수를 증가
                    if (ifsc.getCngstCnt() == 0) {
                        ifsc.setCngstDt(ItsUtils.getSysTime());
                    }
                    // 가공주기에 대해서 정체가 지속되는 경우 주기를 증가
                    // 연속으로 정체로 판정되는 경우에 한해서 정체폼을 생성하기 위함.
                    ifsc.setCngstCnt(ifsc.getCngstCnt()+1);
                }
                else {
                    // 정체판정속도보다 크면 정체횟수를 초기화
                    ifsc.setCngstCnt(0);
                }
            });
        });
    }

    /**
     * VMS 교통정보 표출 스케쥴 조회
     */
    public void loadVmsDsplTrafSchedule(TbVmsCtlrDto vmsObj, TbVmsFormDto vmsForm, TbVmsScheduleDto schedule) {

        eVmsFormType vmsFormType = eVmsFormType.getValue(vmsForm.getVmsFormTypeCd());
        if (vmsFormType != eVmsFormType.eFormTp_figure) {
            // 도형식 소통정보 스케쥴이 아닌 일반 소통정보 스케쥴 인 경우
            TbVmsScheduleDto trafSchedule = schedule.clone();
            vmsObj.getSchedule().add(trafSchedule);
            return;
        }

        // 도형식 소통정보 스케쥴
        boolean isIfscTraf = false;
        Long trafIfscId = 0L;
        int trafIfscCnt = 0;   //전체 표출할 하단 소통정보 갯수
        boolean isAxisTraf = false;
        Long axisIfscId = 0L;
        int trafAxisCnt = 0;   //전체 표출할 하단 소통정보 갯수
        boolean isTraffic = false;
        int missingCnt = 0;

        int figureDisplayTm = schedule.getDsplHh();

        TbVmsSymbLibDto backTrafSymb =  this.symbService.find(vmsForm.getTrfBackImgId());    // 심벌번호+"0"
        if (backTrafSymb == null) {
            log.error("VmsManageService.loadVmsDsplTrafSchedule: VMS {} Back Traffic Form {}, Not Found Back Traffic Image {}.",
                    vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), vmsForm.getTrfBackImgId());
            return;
        }

        // 배경 이미지에 설정된 구간에 대해서 소통정보 확인
        for (Map.Entry<Integer, TbVmsSymbIfscDto> e : backTrafSymb.getCellMap().entrySet()) {
            TbVmsSymbIfscDto cell = e.getValue();
            if (cell.isDup()) {
                continue;
            }
            TbVmsIfscTrafDto ifscTraf = this.ifscService.find(cell.getVmsIfscId());
            if (ifscTraf == null) {
                log.error("VmsManageService.loadVmsDsplTrafSchedule: VMS {} Back Traffic Form {}, IFSC Not Found {}, cellId {}.",
                        vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), cell.getVmsIfscId(), cell.getCellId());
                missingCnt++;
                continue;
            }
            if (0 == ifscTraf.getCmtrGradCd()) {
                missingCnt++;
            }
            else {
                isTraffic = true;
                if ("Y".equals(ifscTraf.getAxisYn())) {
                    // 축 구간
                    if (axisIfscId == 0L) {
                        axisIfscId = ifscTraf.getVmsIfscId();
                    }
                    if (vmsForm.isBottomTrfAxis()) {
                        isAxisTraf = true; // 첫번째 소통정보가 표출되도록 함
                        trafAxisCnt++;
                    }
                }
                else {
                    // 정보제공구간
                    if (trafIfscId == 0L) {
                        trafIfscId = cell.getVmsIfscId();  // 지/정체 정보가 없을 경우 하단 소통정보에 표출할 정보제공구간
                    }
                    if (vmsForm.isBottomTrfIfsc()) {
                        if (3 == ifscTraf.getCmtrGradCd() || 2 == ifscTraf.getCmtrGradCd()) {
                            // 지/정체구간
                            isIfscTraf = true; // 도형식배경소통정보의 셀내의 구간정보 소통정보가 하나라도 있으면 도형식 소통정보를 표출함
                            trafIfscCnt++;
                        }
                    }
                }
            }
        }

        // 민간정보 연계에서 정보가 수집되지 않는 경우 가 종종 있어서 도형식 소통정보를 채우지 못한다.
        // 결측구간이 3구간 이상이면 도형식 소통정보 표출하지 말자
        // 도형식 구간의 모든 소통정보가 생성되지 않았음===> 가공서버가 죽었거나 수집되지 않았음.
        if (!isTraffic || missingCnt >= 3) {
            log.error("VmsManageService.loadVmsDsplTrafSchedule: VMS {} Back Traffic Form {}, {}, IFSC Missing Traffic {} EA.",
                    vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), isTraffic, missingCnt);
            return;
        }

        // 하단에 정보제공구간 소통정보를 표출해야 하는 경우
        if (vmsForm.isBottomTrfIfsc()) {
            // 도형식 하단에 정보제공구간 소통정보 표출하는 경우 다중폼이 생성됨
            // 제공구간소통정보가 정체, 지체인 경우만 하단 소통정보를 표출함
            // 도형식 배경셀에 등록된 구간에 대해서 표출하도록함
            if (trafIfscId != 0L && !isIfscTraf) {
                // 소통정보는 존재하지만 지체, 정체가 없는 경우 하단 소통정보를 표출못하게 된다.
                // 만일 하단 고정문자가 존재하는 경우 하단 고정문자를 표출하면 되지만
                // 고정문자가 존재하기 않으면 하단에 표출할 정보가 없으므로 첫번째 구간의 정보를 표출하도록 한다.
                if (!vmsForm.isBottomFixedText()) {
                    isIfscTraf = true; // 첫번째 소통정보가 표출되도록 함
                    trafIfscCnt = 1;
                }
            }
        }

        // 하단에 VMS 축 소통정보를 표출해야 하는 경우
        if (vmsForm.isBottomTrfAxis()) {
            // 도형식 소통정보 폼일때 하단에 VMS 축 소통정보를 표출해야 하는 경우 다중폼이 생성됨
            // 축 소통정보인 경우 모든 축에 대해 소통정보를 표출, 최대 표출설정값이 설정된 경우 설정값만큼만 표출되게 수정(20200515)
            if (axisIfscId != 0L && !isAxisTraf) {
                // 하단 축 소통정보가 없는 경우 하단 소통정보를 표출못하게 된다.
                // 만일 하단 고정문자가 존재하는 경우 하단 고정문자를 표출하면 되지만
                // 고정문자가 존재하기 않으면 하단에 표출할 정보가 없으므로 첫번째 구간의 정보를 표출하도록 한다.
                if (!vmsForm.isBottomFixedText()) {
                    isAxisTraf = true; // 첫번째 소통정보가 표출되도록 함
                    trafAxisCnt = 1;
                }
            }
        }

        if (!isIfscTraf && !isAxisTraf) {
            // 하단에 표출할 소통정보가 존재하지 않는 경우
            TbVmsScheduleDto trafSchedule = schedule.clone();
            trafSchedule.initTrafId();
            vmsObj.getSchedule().add(trafSchedule);
            return;
        }

        int fromMaxCnt = 0;
        List<Long> ifscIds = new ArrayList<>();
        if (isIfscTraf) {
            fromMaxCnt = trafIfscCnt;
            if (this.config.getBottomTrafficMax() > 0 && trafIfscCnt > this.config.getBottomTrafficMax()) {
                fromMaxCnt = this.config.getBottomTrafficMax();
            }
            if (this.config.getBottomTrafficCycle() > 0) {
                int dsplTm = figureDisplayTm / trafIfscCnt;
                if (dsplTm < this.config.getBottomTrafficCycle()) {
                    figureDisplayTm = this.config.getBottomTrafficCycle();
                } else {
                    figureDisplayTm = dsplTm;
                }
            }

            // 정보제공구간 정체정보 표출
            for (Map.Entry<Integer, TbVmsSymbIfscDto> e : backTrafSymb.getCellMap().entrySet()) {
                TbVmsSymbIfscDto cell = e.getValue();
                if (cell.isDup()) {
                    continue;
                }
                TbVmsIfscTrafDto ifscTraf = this.ifscService.find(cell.getVmsIfscId());
                if (ifscTraf == null || "Y".equals(ifscTraf.getAxisYn())) {
                    continue;
                }
                if (3 == ifscTraf.getCmtrGradCd()) {
                    ifscIds.add(cell.getVmsIfscId());
                }
            }
            // 정보제공구간 지체정보 표출
            for (Map.Entry<Integer, TbVmsSymbIfscDto> e : backTrafSymb.getCellMap().entrySet()) {
                TbVmsSymbIfscDto cell = e.getValue();
                if (cell.isDup()) {
                    continue;
                }
                TbVmsIfscTrafDto ifscTraf = this.ifscService.find(cell.getVmsIfscId());
                if (ifscTraf == null || "Y".equals(ifscTraf.getAxisYn())) {
                    continue;
                }
                if (2 == ifscTraf.getCmtrGradCd()) {
                    ifscIds.add(cell.getVmsIfscId());
                }
            }

            if (ifscIds.isEmpty() && trafIfscId != 0L && !vmsForm.isBottomFixedText()) {
                // 지정체구간이 없고 하단 고정문자가 없으면서 첫번째 정보제공구간 데이터가 존재하는 경우
                ifscIds.add(trafIfscId);
            }
        }

        if (isAxisTraf) {
            fromMaxCnt = trafAxisCnt;
            if (this.config.getBottomTrafficCycle() > 0) {
                int dsplTm = figureDisplayTm / trafAxisCnt;
                if (dsplTm < this.config.getBottomTrafficCycle()) {
                    figureDisplayTm = this.config.getBottomTrafficCycle();
                } else {
                    figureDisplayTm = dsplTm;
                }
            }

            // 축 소통정보 표출
            for (Map.Entry<Integer, TbVmsSymbIfscDto> e : backTrafSymb.getCellMap().entrySet()) {
                TbVmsSymbIfscDto cell = e.getValue();
                if (cell.isDup()) {
                    continue;
                }
                TbVmsIfscTrafDto ifscTraf = this.ifscService.find(cell.getVmsIfscId());
                if (ifscTraf == null || !"Y".equals(ifscTraf.getAxisYn())) {
                    continue;
                }
                if (0 != ifscTraf.getCmtrGradCd()) {
                    ifscIds.add(cell.getVmsIfscId());
                }
            }
            if (ifscIds.isEmpty() && axisIfscId != 0L && !vmsForm.isBottomFixedText()) {
                // 지정체구간이 없고 하단 고정문자가 없으면서 첫번째 정보제공구간 데이터가 존재하는 경우
                ifscIds.add(axisIfscId);
            }
        }

        if (ifscIds.isEmpty()) {
            // 하단에 표출할 정보가 없는 경우
            TbVmsScheduleDto trafSchedule = schedule.clone();
            trafSchedule.initTrafId();
            vmsObj.getSchedule().add(trafSchedule);
        }
        else {
            for (int ii = 0; ii < ifscIds.size() && ii < fromMaxCnt; ii++) {
                TbVmsScheduleDto trafSchedule = schedule.clone();
                trafSchedule.setDnldFormId(schedule.getDnldFormId()+ii);
                trafSchedule.initTrafId();
                trafSchedule.setVmsIfscId(ifscIds.get(ii));
                trafSchedule.setFrstVmsIfscId(ifscIds.get(ii));
                trafSchedule.setDsplHh(figureDisplayTm);
                vmsObj.getSchedule().add(trafSchedule);
            }
        }
    }

    /**
     * VMS 표출 스케쥴 정보를 조회
     */
    public void loadVmsDsplSchedule() {
        Elapsed elapsed = new Elapsed();
        List<TbVmsSchedule> result = this.mapper.selectVmsDsplSchedule();
        result.forEach(data -> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(data.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsManageService.loadVmsDsplSchedule: Not Found VMS {}, Schedule Phase {}.", data.getVmsCtlrNmbr(), data.getPhase());
                return;
            }

            TbVmsFormDto vmsForm = this.formService.find(data.getVmsFormId());
            if (vmsForm == null) {
                log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, Not Found Form {}.", data.getVmsCtlrNmbr(), data.getVmsFormId());
                return;
            }

            eVmsScheduleType vmsSchFormType = eVmsScheduleType.getValue(data.getVmsSchFormType());
            if (vmsSchFormType == null) {
                log.error("VmsManageService.loadVmsDsplSchedule: Unknown Form Schedule Type: VMS({}), FormId({}), FormScheduleType({})",
                        data.getVmsCtlrNmbr(), data.getVmsFormId(), data.getVmsSchFormType());
                return;
            }
            eVmsFormType vmsFormType = eVmsFormType.getValue(vmsForm.getVmsFormTypeCd());
            if (vmsFormType == null) {
                log.error("VmsManageService.loadVmsDsplSchedule: Unknown Form Type: VMS({}), FormId({}), FormScheduleType({})",
                        data.getVmsCtlrNmbr(), data.getVmsFormId(), vmsForm.getVmsFormTypeCd());
                return;
            }

            // 조회정렬은 기본,고정,자동(돌발,공사,행사,우회,교통,정체폼) 순으로 조회된다.
            // 따라서 각 폼에 대한처리를 수행하면 된다.
            // ==> 신규작업에서 폼 표출 우선순위적용함 ==> 조회정렬이 영향이 있는지 확인해야함.
            // eFormTp_traf_1   = 11,  //	소통상황(1단)
            // eFormTp_traf_2   = 12,  //	소통상황(2단)
            // eFormTp_traf_3   = 13,  //	소통상황(3단)
            // eFormTp_traf_4   = 14,  //	소통상황(4단)
            boolean isIncidentForm = false;
            if (vmsSchFormType == eVmsScheduleType.eSchTp_traffic && vmsFormType == eVmsFormType.eFormTp_traf_1) {
                // 교통정보스케쥴이고 폼의 유형이 소통정보(1단) 인경우
                // 해당 폼이 돌발이나 이벤트가 등록되어 있다면 폼을 생성하지 않는다.
                Long frstVmsIfscId = data.getFrstVmsIfscId();
                if (frstVmsIfscId != null && frstVmsIfscId != 0L) {
                    for (int ii = 0; ii < vmsObj.getSchedule().getUnits().size(); ii++) {
                        TbVmsScheduleDto schedule = vmsObj.getSchedule().getUnits().get(ii);
                        eVmsScheduleType scheduleFormType = eVmsScheduleType.getValue(schedule.getVmsSchFormType());
                        if (scheduleFormType == eVmsScheduleType.eSchTp_incident || //돌발
                            scheduleFormType == eVmsScheduleType.eSchTp_gongsa   || //공사/행사
                            scheduleFormType == eVmsScheduleType.eSchTp_deture      //우회도로
                        ) {
                            if (schedule.getEvent() != null && Objects.equals(schedule.getEvent().getVmsIfscId(), frstVmsIfscId)) {
                                isIncidentForm = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (isIncidentForm) {
                log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, Form {}, IFSC ID {} Incident Occurred.",
                        vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), data.getFrstVmsIfscId());
                return;
            }

            final int[] formIdx = new int[1];
            TbVmsScheduleDto schedule = data.toDto();
            switch(vmsSchFormType) {
                case eSchTp_traffic:    // 교통정보
                    loadVmsDsplTrafSchedule(vmsObj, vmsForm, schedule);
                    break;
                case eSchTp_incident:   // 돌발
                    // 돌발 스케줄인 경우 해당 VMS 에 발생한 모든 돌발 정보를 표출하도록 스케줄 추가
                    formIdx[0] = 0;
                    vmsObj.getIncident().getUnits().forEach(incd -> {
                        TbVmsScheduleDto incdSchedule = schedule.clone();
                        incdSchedule.setDnldFormId(schedule.getDnldFormId() + formIdx[0]++);
                        incdSchedule.setVmsIfscId(incd.getVmsIfscId());
                        incdSchedule.setEvent(incd);
                        vmsObj.getSchedule().add(incdSchedule);
                    });
                    break;
                case eSchTp_gongsa:     // 공사/행사문안
                    // 공사/행사 스케줄인 경우 해당 VMS 에 발생한 모든 돌발 정보를 표출하도록 스케줄 추가
                    formIdx[0] = 0;
                    vmsObj.getEvent().getUnits().forEach(event -> {
                        TbVmsScheduleDto eventSchedule = schedule.clone();
                        eventSchedule.setDnldFormId(schedule.getDnldFormId() + formIdx[0]++);
                        eventSchedule.setVmsIfscId(event.getVmsIfscId());
                        eventSchedule.setEvent(event);
                        vmsObj.getSchedule().add(eventSchedule);
                    });
                    break;
                case eSchTp_hongbo:     // 홍보
                    TbVmsScheduleDto hongboSchedule = schedule.clone();
                    vmsObj.getSchedule().add(hongboSchedule);
                    break;
                case eSchTp_deture:     // 우회도로
                    // 돌발발생한 구간중 우회도로 구간이 있는경우
                    formIdx[0] = 0;
                    vmsObj.getIncident().getUnits().forEach(incd -> {
                        if (incd.getDetrId() == 0L || incd.getVmsIfscId() == 0) {
                            return;
                        }
                        TbVmsScheduleDto detureSchedule1 = schedule.clone();
                        detureSchedule1.setDnldFormId(schedule.getDnldFormId() + formIdx[0]++);
                        detureSchedule1.setVmsIfscId(incd.getVmsIfscId());
                        detureSchedule1.setEvent(incd);
                        vmsObj.getSchedule().add(detureSchedule1);
                    });
                    // 공사/행사 발생한 구간중 우회도로 구간이 있는경우
                    vmsObj.getEvent().getUnits().forEach(event -> {
                        if (event.getDetrId() == 0L || event.getVmsIfscId() == 0) {
                            return;
                        }
                        TbVmsScheduleDto detureSchedule2 = schedule.clone();
                        detureSchedule2.setDnldFormId(schedule.getDnldFormId() + formIdx[0]++);
                        detureSchedule2.setVmsIfscId(event.getVmsIfscId());
                        detureSchedule2.setEvent(event);
                        vmsObj.getSchedule().add(detureSchedule2);
                    });
                    break;
                case eSchTp_congest:    // 정체상황
                    formIdx[0] = 0;
                    vmsObj.getRltnIfscMap().forEach((key, cngs) -> {
                        if (!cngs.isUsed() || !cngs.isCngstCnfmYn()) {
                            // 정체판정이 아닌경우
                            return;
                        }
                        if (cngs.getCngstCnt() < this.config.getCngstContCount()) {
                            // 설정되어 있는 정체판정 횟수보다 작으면 정체아님. 정체가 연속에서 반복되는 최소 갯수임...
                            return;
                        }
                        if (vmsObj.getCngstForms() < vmsObj.getMaxCngstForm()) {
                            vmsObj.setCngstForms(cngs.getCngstCnt()+1);
                            vmsObj.setExistCngsForm(true);

                            TbVmsScheduleDto congestSchedule = schedule.clone();
                            congestSchedule.setDnldFormId(schedule.getDnldFormId() + formIdx[0]++);
                            congestSchedule.setFrstVmsIfscId(cngs.getVmsIfscId());
                            congestSchedule.setVmsIfscId(cngs.getVmsIfscId());
                            vmsObj.getSchedule().add(congestSchedule);
                        }
                    });
                    break;
                case eSchTp_video:  //동영상
                    // 동영상명칭이 설정되지 않은 것은 표출하지 않는다.
                    if (data.getSymbLibNmbr() != 0 && data.getSymbLibNmbr() != 200 && !"".equals(data.getStrmAddr())) {
                        TbVmsScheduleDto videoSchedule = schedule.clone();
                        vmsObj.getSchedule().add(videoSchedule);
                    }
                    break;
                case eSchTp_stream: //스트리밍영상
                    //스트리밍주소가 설정되지 않은 것은 표출하지 않는다.
                    if (data.getSymbLibNmbr() == 300 && !"".equals(data.getStrmAddr())) {
                        TbVmsScheduleDto streamSchedule = schedule.clone();
                        vmsObj.getSchedule().add(streamSchedule);
                    }
                    break;
                case eSchTp_atmp: //9:대기환경
                    Long atmpSttnNmbr = 0L;
                    boolean isAtmp = false;
                    for (int ii = 0; ii < vmsForm.getObjects().size(); ii++) {
                        TbVmsFormObjectDto tmpFormObj = vmsForm.getObjects().get(ii);
                        /**
                         90	401	@관측장소	Y
                         90	402	@PM10	Y
                         90	403	@PM2.5	Y
                         90	404	@PM10등급	Y
                         90	405	@PM2.5등급	Y
                         90	406	@통합대기등급 이미지	Y
                         90	407	@미세먼지등급 이미지	Y
                         90	408	@초미세먼지등급 이미지	Y
                         90	409	@통합대기등급	Y
                         90	410	@통합대기	Y
                         90	411	@오존	Y
                         90	412	@오존등급	Y
                         90	413	@오존등급이미지	Y
                        */
                        if (tmpFormObj.getVmsFormObjectTypeCd() >= 401 && tmpFormObj.getVmsFormObjectTypeCd() <= 413) {
                            atmpSttnNmbr = tmpFormObj.getVmsIfscId();
                            TbVmsAtmpDto atmpObj = this.atmpService.find(atmpSttnNmbr);
                            if (atmpObj == null) {
                                log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, ATMP Form {}, Not Found AtmpSttnNmb {}.",
                                        vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), atmpSttnNmbr);
                                continue;
                            }
                            if (atmpObj.isSuccess()) {
                                isAtmp = true;
                                break;
                            }
                        }
                    }
                    if (isAtmp) {
                        TbVmsScheduleDto atmpSchedule = schedule.clone();
                        atmpSchedule.setFrstVmsIfscId(atmpSttnNmbr);
                        vmsObj.getSchedule().add(atmpSchedule);
                    }
                    else {
                        log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, ATMP Form {}, Atmp Traffic Failed.",
                                vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId());
                    }
                    break;
                case eSchTp_park: //10:주차정보
                    boolean isParkingForm = false;
                    for (int ii = 0; ii < vmsForm.getObjects().size(); ii++) {
                        TbVmsFormObjectDto tmpFormObj = vmsForm.getObjects().get(ii);
                        /**
                         100	101	@주차장명	N
                         100	102	@주차면수	Y
                         100	103	@주차가능면수	Y
                         100	104	@주차혼잡도	Y
                         */
                        if (tmpFormObj.getVmsFormObjectTypeCd() >= 101 && tmpFormObj.getVmsFormObjectTypeCd() <= 104) {
                            TbVmsParkDto parkObj = this.parkService.find(tmpFormObj.getVmsIfscId());
                            if (parkObj == null) {
                                log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, Parking Form {}, Not Found Parking {}.",
                                        vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId(), tmpFormObj.getVmsIfscId());
                                continue;
                            }
                            if (parkObj.isSuccess()) {
                                isParkingForm = true;
                                break;
                            }
                        }
                    }
                    if (isParkingForm) {
                        TbVmsScheduleDto parkSchedule = schedule.clone();
                        vmsObj.getSchedule().add(parkSchedule);
                    }
                    else {
                        log.error("VmsManageService.loadVmsDsplSchedule: VMS {}, Parking Form {}, Parking Congest Failed.",
                                vmsObj.getVmsCtlrNmbr(), vmsForm.getVmsFormId());
                    }
                    break;
                case eSchTp_evehicle:   //11:긴급차량우선신호
                    if (vmsForm.getVmsFormDsplDrctCd() == 0) {
                        vmsObj.setEvehIngForm(true);
                    }
                    else {
                        vmsObj.setEvehEndForm(true);
                        schedule.setDsplHh(60);
                    }
                    TbVmsScheduleDto evehSchedule = schedule.clone();
                    vmsObj.getSchedule().add(evehSchedule);
                    break;
                default:
                    break;
            }
        });
        log.info("VmsManageService.loadVmsDsplSchedule: {} ms.", elapsed.milliSeconds());
    }

    /**
     * VMS 운영모드 정보 조회
     */
    public void loadVmsOperationMode() {
        Elapsed elapsed = new Elapsed();
        List<TbVmsOperMode> result = this.mapper.selectVmsOperMode();
        result.forEach(data-> {
            TbVmsCtlrDto vmsObj = this.repoService.getCtrlMap(data.getVmsCtlrNmbr());
            if (vmsObj == null) {
                log.error("VmsManageService.loadVmsOperationMode: Not Found VMS {}.", data.getVmsCtlrNmbr());
                return;
            }
            if (!"A".equals(data.getOperMode()) && !"F".equals(data.getOperMode()) && !"B".equals(data.getOperMode())) {
                data.setOperMode("A");
            }
            String oldOperMode = vmsObj.getOperMode();
            vmsObj.setOperMode(data.getOperMode());
            vmsObj.setMaxPhaseNum(Math.min(this.config.getMaxDownloadForms(), data.getVmsMaxPhaseNum()));
            // VMS 별 정체폼갯수를 제한하는 경우 여기에서 처리하도록 하자
            vmsObj.setMaxCngstForm(Math.min(this.config.getMaxCngstForms(), VmsConstants.VMS_MAX_CNGS_FORM));
            if (data.getOperMode().equals(oldOperMode)) {
                // TODO: 화면 업데이트
            }
        });
        log.info("VmsManageService.loadVmsOperationMode: {} ms.", elapsed.milliSeconds());
    }

    public void initProvide(TbVmsCtlrDto AObj) {
        this.repoService.getCtlrMap().forEach((key, obj) -> {
            if (AObj == null || obj.getVmsCtlrNmbr() == AObj.getVmsCtlrNmbr()) {
                obj.setProvide(true);
            }
        });
    }

    public void clearVmsProvideMode() {
        String dsplDt = ItsUtils.getSysTime();
        this.repoService.getCtlrMap().forEach((key, obj) -> {

            obj.getEvechile().getUnits().clear();

            obj.setEvehIngForm(false);
            obj.setEvehEndForm(false);
            obj.getSchedule().getUnits().clear();

            obj.getIncident().getUnits().clear();
            obj.getEvent().getUnits().clear();
            obj.getFormManager().clear();

            obj.setCngstForms(0);
            obj.setMaxCngstForm(this.config.getMaxCngstForms());
            obj.setExistCngsForm(false);

//            if (!obj.isProvide()) {
//                return;
//            }

            obj.getControlMode().setMaxDisplayForm(0);
            obj.getControlMode().setEnable(false);
            obj.getControlMode().setSvcDate(dsplDt);
            obj.getControlMode().setSaveFlag(true);
            obj.getControlMode().setTimer(System.currentTimeMillis());
            obj.getControlMode().setResult(false);
        });
    }

    /**
     * VMS 다운로드 폼 정보 생성
     */
    public void jobMakeDownloadVmsForm() {
        log.info("VmsManageService.jobMakeDownloadVmsForm: START.");
        Elapsed elapsed = new Elapsed();
        if (this.repoService.isStaticCycle()) {
            loadVmsOperationMode(); // 운영모드 조회
            initProvide(null);
        }

        clearVmsProvideMode();
        makeVmsScenarioForm();      // 시나리오정보 로딩
        makeVmsProvideForm();       // 시나리오에 따른 폼 정보 생성
        makeVmsDatabaseForm();      // 폼 이미지 생성

        /**
         * 데이터베이스 처리 프로세스로 VMS 폼 다운로드 정보를 보낸다.
         * 데이터베이스 처리 프로레스에서 제어기의 네트워크 상태에 따라서 통신 정상인 제어기로 데이터를 전송한다.
         */
        this.repoService.setDsplDt(ItsUtils.getSysTime());
        this.dbmsDataProcess.add(new DbmsData(DbmsDataType.DBMS_DATA_VMS_FORM_DOWNLOAD, this.repoService.isStaticCycle(), this.repoService.getDsplDt()));
        log.info("VmsManageService.jobMakeDownloadVmsForm: {} ms.", elapsed.milliSeconds());
    }

    /**
     * VMS FORM 데이터를 생성한다.
     */
    public void makeVmsScenarioForm() {
        log.info("VmsManageService.makeVmsScenarioForm: START.");
        Elapsed elapsed = new Elapsed();
        if (this.config.isLoadDb()) {
            loadDb();
        }

        this.atmpService.loadDb();  // 기상정보
        this.parkService.loadDb();  // 주차정보
        loadEventOccrInfo();  // 돌발정보조회
        this.ifscService.loadDb();  // VMS 정보제공구간 교통정보

        if (this.repoService.isStaticCycle()) {
            // 정주기 가공일때 처리해야 함
            congestJudgment();  // 정체판정
            this.repoService.setStaticCycle(false);
        }

        loadVmsDsplSchedule();
        log.info("VmsManageService.makeVmsScenarioForm: {} ms.", elapsed.milliSeconds());
    }

    public void makeVmsProvideForm() {
        log.info("VmsManageService.makeVmsProvideForm: START.");
        Elapsed elapsed = new Elapsed();
        this.repoService.getCtlrMap().forEach((ctlrNmbr, vmsObj) -> {
            if ("Y".equals(vmsObj.getDelYn())) {
                return;
            }
            VmsSchedule scheObj = vmsObj.getSchedule();
            int downLoadForms = Math.min(vmsObj.getMaxPhaseNum(), scheObj.size());
            vmsObj.getControlMode().setMaxDisplayForm(downLoadForms);

            this.config.makeDirectory(vmsObj.getLocalFormDir(), "VMS " + vmsObj.getVmsCtlrNmbr() + " Directory.");

            boolean isEVehIngForm = vmsObj.isEvehIngForm() && vmsObj.getEvechile().isOcrr();    // 폼이 존재하고 데이터 발생
            boolean isEVehEndForm = vmsObj.isEvehEndForm() && vmsObj.getEvechile().isEnd();     // 폼이 존재하고 데이터 발생

            for (int scheduleIdx = 0; scheduleIdx < scheObj.size(); scheduleIdx++) {
                TbVmsScheduleDto schedule = scheObj.getUnits().get(scheduleIdx);
                if (vmsObj.isExistCngsForm() && !schedule.isDsplCngsYn()) {
                    // 해당 제어기에 정체폼이 존재하는 경우 스케쥴 폼이 정체일때 표출하지 않는 경우 폼을 생성하지 않는다.
                    log.info("VmsManageService.makeVmsProvideForm: VMS {}, Congest continuous {}/{}.", ctlrNmbr, vmsObj.isExistCngsForm(), schedule.isDsplCngsYn());
                    continue;
                }
                TbVmsFormDto vmsForm = this.formService.find(schedule.getVmsFormId());
                if (vmsForm == null) {
                    log.error("VmsManageService.makeVmsProvideForm: VMS {}, Not Found Form {}.", ctlrNmbr, schedule.getVmsFormId());
                    continue;
                }
                eVmsScheduleType vmsSchFormType = eVmsScheduleType.getValue(schedule.getVmsSchFormType());
                if (vmsSchFormType == null) {
                    log.error("VmsManageService.makeVmsProvideForm: Unknown Form Schedule Type: VMS({}), FormId({}), FormScheduleType({})",
                            ctlrNmbr, schedule.getVmsFormId(), schedule.getVmsSchFormType());
                    continue;
                }
                eVmsFormType vmsFormType = eVmsFormType.getValue(vmsForm.getVmsFormTypeCd());
                if (vmsFormType == null) {
                    log.error("VmsManageService.makeVmsProvideForm: Unknown Form Type: VMS({}), FormId({}), FormScheduleType({})",
                            ctlrNmbr, schedule.getVmsFormId(), vmsForm.getVmsFormTypeCd());
                    continue;
                }

                if (isEVehIngForm) {
                    // 긴급차량 진행중 메시지 표출
                    if (vmsSchFormType != eVmsScheduleType.eSchTp_evehicle || vmsForm.getVmsFormDsplDrctCd() != 0) {
                        // 긴급차량우선신호 진행중 일때 스케쥴 유형 및 폼유형이 맞지 않으면 리턴
                        // 단일 메시지로 표출하는 경우
//                        log.info("VmsManageService.makeVmsProvideForm: VMS {}, FORM {}, EVehicle Ing {}. {} DsplDrctCd {}.",
//                                ctlrNmbr, vmsForm.getVmsFormId(), isEVehIngForm, vmsSchFormType, vmsForm.getVmsFormDsplDrctCd());
                        continue;
                    }
                } else {
                    if (vmsSchFormType == eVmsScheduleType.eSchTp_evehicle && vmsForm.getVmsFormDsplDrctCd() == 0) {
//                        log.info("VmsManageService.makeVmsProvideForm: VMS {}, FORM {}, EVehicle Ing {}. {} DsplDrctCd {}.",
//                                ctlrNmbr, vmsForm.getVmsFormId(), isEVehIngForm, vmsSchFormType, vmsForm.getVmsFormDsplDrctCd());
                        continue;
                    }
                }

                if (isEVehEndForm) {
                    // 긴급차량 운영종료 메시지 표출
                    // 운영종료 메시지는 기타 메시지와 같이 내려보낸다. 단, 표출시간을 60초로 해서 내려보내자.
//                    if (vmsSchFormType != eVmsScheduleType.eSchTp_evehicle || vmsForm.getVmsFormDsplDrctCd() != 1) {
//                        // 긴급차량우선신호 운영종료 일때 스케쥴 유형 및 폼유형이 맞지 않으면 리턴
//                        // 단일메시지로 표출하는 경우
//                        continue;
//                    }
                } else {
                    if (vmsSchFormType == eVmsScheduleType.eSchTp_evehicle && vmsForm.getVmsFormDsplDrctCd() == 1) {
//                        log.info("VmsManageService.makeVmsProvideForm: VMS {}, FORM {}, EVehicle End {}. {} DsplDrctCd {}.",
//                                ctlrNmbr, vmsForm.getVmsFormId(), isEVehEndForm, vmsSchFormType, vmsForm.getVmsFormDsplDrctCd());
                        continue;
                    }
                }

                // 소통정보 표출폼 인 경우 표출할 구간의 소통정보가 존재하지 않는 경우 폼을 생성하지 않도록 한다.
                int reqTraffic = 0;
                int setTraffic = 0;
                switch (vmsFormType) {
                    case eFormTp_traf_1: reqTraffic = 1; break;     //   = 11,  //	소통상황(1단)
                    case eFormTp_traf_2: reqTraffic = 2; break;     //   = 12,  //	소통상황(2단)
                    case eFormTp_traf_3: reqTraffic = 3; break;     //   = 13,  //	소통상황(3단)
                    case eFormTp_traf_4: reqTraffic = 4; break;     //   = 14,  //	소통상황(4단)
                    default: break;
                }
                if (reqTraffic > 0) {
                    int ss;
                    Long[] vmsIfscId = { schedule.getFrstVmsIfscId(), schedule.getSecdVmsIfscId(), schedule.getThirVmsIfscId(), schedule.getFourVmsIfscId() };
                    Long[] imgIfscId = { schedule.getFrstImgIfscId(), schedule.getSecdImgIfscId(), schedule.getThirImgIfscId(), schedule.getFourImgIfscId() };

                    for (ss = 0; ss < 4; ss++) {
                        if (vmsIfscId[ss] == 0L) {
                            vmsIfscId[ss] = imgIfscId[ss];
                        }
                        if (vmsIfscId[ss] != 0L) {
                            setTraffic++;
                        }
                    }
                    if (reqTraffic > setTraffic) {
                        //실제 소통정보 폼 갯수보다 할당된 구간이 적은 경우에 해당함
                        reqTraffic = setTraffic;
                    }
                    setTraffic = 0;
                    for (ss = 0; ss < 4; ss++) {
                        if (vmsIfscId[ss] == 0L) continue;
                        TbVmsIfscTrafDto ifscTraf = this.ifscService.find(vmsIfscId[ss]);
                        if (ifscTraf != null && 0 != ifscTraf.getCmtrGradCd()) {
                            setTraffic++;
                        }
                    }
                    if (reqTraffic != setTraffic) {
                        log.error("VmsManageService.makeVmsProvideForm: No Traffic: VMS({}), FormId({}), Req {}, Set {}.",
                                ctlrNmbr, schedule.getVmsFormId(), reqTraffic, setTraffic);
                        continue;
                    }
                } // 1,2,3,4 단 소통정보 표출 폼인 경우

                // 스케줄에 따른 VMS FORM 을 생성한다.
                VmsForm form = vmsObj.getFormManager().addForm(schedule.getVmsFormId(), schedule.getDnldFormId());
                form.initDownload(vmsObj, vmsForm, schedule);

                for (int objectIdx = 0; objectIdx < vmsForm.getObjects().size(); objectIdx++) {
                    if (objectIdx >= VmsConstants.VMS_MAX_FORM_OBJECTS) {
                        log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Form Object count overflow {} EA.",
                                ctlrNmbr, schedule.getVmsFormId(), vmsForm.getObjects().size());
                        break;
                    }

                    TbVmsFormObjectDto obj = vmsForm.getObjects().get(objectIdx);
                    if (vmsFormType == eVmsFormType.eFormTp_figure) {
                        // 도형식 배경소통정보 폼인 경우 하단에 표출할 정보가 없는 경우 하단 소통정보 객체는 생성하지 않도록 하자.
                        switch(obj.getVmsFormObjectTypeCd()) {
                            case 0: //문자열
                            case 1: //심볼
                            case 2: //이미지
                                //N:일반(모두표출하는것)
                                //T:소통정보(소통정보가 있는 경우에만 표출하는것)
                                //F:하단고정(소통정보가 없는 경우에만 표출하는것)
                                if (schedule.getVmsIfscId() == 0L) {
                                    // 표출할 소통정보가 없는 경우 교통정보 표출인 경우 하단정보 생성하지 않음
                                    if ("T".equals(obj.getTrfcFillCd())) {
                                        continue;
                                    }
                                }
                                else {
                                    // 표출할 소통정보가 있는 경우 고정문자 표출인 경우 하단정보 생성하지 않음
                                    if ("F".equals(obj.getTrfcFillCd())) {
                                        continue;
                                    }
                                }
                                break;
                            case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19:
                                if (schedule.getVmsIfscId() == 0L) {
                                    continue; //하단에 표출할 정보제공구간이 없는 경우임
                                }
                                if (obj.getVmsFormObjectTypeCd() == 17) {
                                    // 하단 소통정보이미지
                                    schedule.setFrstImgIfscId(schedule.getVmsIfscId());
                                }
                                break;
                            default: break;
                        }
                    } // (vmsFormType == eVmsFormType.eFormTp_figure)

                    VmsFormObject formObj = form.addFormObject(obj.getVmsFormObjectId());
                    formObj.setFormObjectInfo(this.fontService.getFontName(obj.getVmsFontNameCd()), obj);

                    if (obj.getVmsFormObjectTypeCd() == 201 || obj.getVmsFormObjectTypeCd() == 301) {
                        // 201:동영상명칭, 301:스트리밍영상주소
                        formObj.setTextData(schedule.getStrmAddr());
                    }

                    if (obj.getVmsFormObjectTypeCd() == 200 || obj.getVmsFormObjectTypeCd() == 300) {
                        // 200:동영상이미지, 300:스트리밍영상이미지
                        formObj.setMemSymbLibNmbr(schedule.getSymbLibNmbr()+"0");
                        if (obj.getVmsFormObjectTypeCd() == 200) {
                            if (formObj.getDsplWidth() == 0 || formObj.getDsplHeight() == 0) {
                                formObj.setPosX(0);                     // NUMBER(5)	Y			VMS 표출 X좌표
                                formObj.setPosY(0);                     // NUMBER(5)	Y			VMS 표출 Y좌표
                                formObj.setDsplWidth(form.getWidth());  // NUMBER(5)	Y	0		VMS 표출 넓이
                                formObj.setDsplHeight(form.getHeight());// NUMBER(5)	Y	0		VMS 표출 높이
                            }
                            TbVmsSymbLibDto symb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (symb == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Form Object Video Symbol Not Found {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                            }
                            else {
                                form.setDsplHh(Math.toIntExact(symb.getPlayTm()));
                            }
                        }
                    }

                    if (formObj.getObjectKind() != eVmsFormObjectKind.OBJECT_TEXT) {
                        if (obj.getVmsFormObjectTypeCd() == 200 || obj.getVmsFormObjectTypeCd() == 300) {
                            // 200:동영상이미지, 300:스트리밍영상이미지
                            TbVmsSymbLibDto symb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (symb != null) {
                                formObj.setImageData(symb.getImageData());
                            }
                            else {
                                formObj.setImageData(formObj.getImageData());
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Symbol Not Found {} set form object image data.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                            }
                        }
                        else if (obj.getVmsFormObjectTypeCd() == 406 || //@통합대기등급 이미지
                                 obj.getVmsFormObjectTypeCd() == 407 || //@미세먼지등급 이미지
                                 obj.getVmsFormObjectTypeCd() == 408 || //@초미세먼지등급 이미지
                                 obj.getVmsFormObjectTypeCd() == 413 )  //@오존등급이미지
                        {
                            TbVmsAtmpDto atmp = this.atmpService.find(obj.getVmsIfscId());
                            if (atmp != null && atmp.isSuccess()) {
                                // 관측장소 정보가 존재하는 경우에만 대기환경이미지를 폼에 적용한다.
                                int atmpGrad = 0;
                                switch(obj.getVmsFormObjectTypeCd())
                                {
                                    case 406: atmpGrad = ItsUtils.parseIntDef(atmp.getIntgAtmpGrad(), 1); break;
                                    case 407: atmpGrad = ItsUtils.parseIntDef(atmp.getPm101hhGrad(), 1);  break;
                                    case 408: atmpGrad = ItsUtils.parseIntDef(atmp.getPm251hhGrad(), 1);  break;
                                    case 413: atmpGrad = ItsUtils.parseIntDef(atmp.getO3Grad(), 1);       break;
                                }
                                if (atmpGrad > 0) {
                                    atmpGrad--;
                                }
                                TbVmsSymbLibDto symb = this.symbService.find(formObj.getMemSymbLibNmbr());
                                if (symb != null && symb.getGradSymbLibNmbrList().size() >= atmpGrad) {
                                    TbVmsSymbLibDto atmpSymb = this.symbService.find(symb.getGradSymbLibNmbrList().get(atmpGrad));
                                    if (atmpSymb != null) {
                                        formObj.setImageData(atmpSymb.getImageData());
                                        formObj.setMemSymbLibNmbr(symb.getGradSymbLibNmbrList().get(atmpGrad));
                                        formObj.setBitmapId(atmpSymb.getDnldSymbLibNmbr());
                                    }
                                    else {
                                        formObj.setImageData(symb.getImageData());
                                        formObj.setMemSymbLibNmbr(formObj.getMemSymbLibNmbr());
                                    }
                                }
                                else {
                                    formObj.setImageData(formObj.getImageData());
                                    log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Atmp Symbol Not Found {} set form object image data.",
                                            ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                                }
                            }
                        }
                        else if (obj.getVmsFormObjectTypeCd() == 1 || obj.getVmsFormObjectTypeCd() == 2) {
                            // 심볼, 이미지
                            TbVmsSymbLibDto symb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (symb != null) {
                                formObj.setImageData(symb.getImageData());
                            }
                            else {
                                formObj.setImageData(obj.getImagData());
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Atmp Symbol Not Found {} set form symbol object image data.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                            }
                        }
                        else {
                            formObj.setImageData(obj.getImagData());
                        }
                    } // (formObj.getObjectKind() != eVmsFormObjectData.text_object_data.getValue())

                    // 폼객체 유형별 Fill In
                    switch(formObj.getObjectType()) {
                        case 0:     // 고정문자열
                        case 201:   // 동영상파일명
                        case 301:   // 스트리밍영상주소
                            formObj.changeTextPosition();
                            break;
                        case 1:     // 심볼
                        case 2:     // 파일이미지
                            TbVmsSymbLibDto tSymb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (tSymb == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found Symbol, File Image Symbol {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                            }
                            else {
                                formObj.setImageData(tSymb.getImageData());
                            }
                            //formObj.setMemSymbLibNmbr(formObj.getMemSymbLibNmbr());
                            break;
                        case 17:    // 1단,소통정보이미지1
                        case 27:    // 2단,소통정보이미지2
                        case 37:    // 3단,소통정보이미지3
                        case 47:    // 4단,소통정보이미지4
                        case 167:   // @우회소통정보이미지
                            TbVmsSymbLibDto symb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (symb == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found Traffic 1,2,3,4,detour Symbol {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                                continue;
                            }
                            formObj.setMemSymbLibNmbr(formObj.getMemSymbLibNmbr());

                            switch(formObj.getObjectType()) {
                                case 167: // @우회소통정보이미지
                                case 17:  formObj.setIfscId(schedule.getFrstImgIfscId()); break;  // 1단,소통정보이미지1
                                case 27:  formObj.setIfscId(schedule.getSecdImgIfscId()); break;  // 2단,소통정보이미지2
                                case 37:  formObj.setIfscId(schedule.getThirImgIfscId()); break;  // 3단,소통정보이미지3
                                case 47:  formObj.setIfscId(schedule.getFourImgIfscId()); break;  // 4단,소통정보이미지4
                            }
                            TbVmsIfscTrafDto ifscTraf = this.ifscService.find(formObj.getIfscId());
                            if (ifscTraf != null) {
                                formObj.setIfscTrafGradCd(ifscTraf.getCmtrGradCd());
                                String trafImagId = symb.getSymbLibNmbr() + String.valueOf(ifscTraf.getCmtrGradCd());
                                TbVmsSymbLibDto trafSymb = this.symbService.find(trafImagId);
                                if (trafSymb != null) {
                                    formObj.setMemSymbLibNmbr(trafImagId);
                                    formObj.setImageData(trafSymb.getImageData());
                                    formObj.setBitmapId(trafSymb.getDnldSymbLibNmbr());
                                }
                                else {
                                    formObj.setImageData(symb.getImageData());
                                }
                            }
                            else {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found IFSC Traffic 1,2,3,4,detour IFSC {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getIfscId());
                                formObj.setIfscTrafGradCd(0);
                                formObj.setImageData(symb.getImageData());
                            }
                            break;
                        case 3:     // 소통정보 배경이미지
                            TbVmsSymbLibDto bkSymb = this.symbService.find(formObj.getMemSymbLibNmbr());
                            if (bkSymb == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found Bk Traffic Symbol {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                                continue;
                            }
                            formObj.setMemSymbLibNmbr(formObj.getMemSymbLibNmbr());
                            //formObj.setPosX(bkSymb.getPosX());
                            //formObj.setPosY(bkSymb.getPosY());
                            Image imgBmp = bkSymb.getImage();  // 원천이미지를 가지고 온다.
                            if (imgBmp == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Image Data Error {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), formObj.getMemSymbLibNmbr());
                                continue;
                            }
                            BufferedImage formImage = new BufferedImage(imgBmp.getWidth(null), imgBmp.getHeight(null), BufferedImage.TYPE_INT_RGB);
                            Graphics2D 	g2d = formImage.createGraphics();
                            FloodFill floodFill = new FloodFill(imgBmp);
                            floodFill.setAntialiased(false);
                            bkSymb.getCellMap().forEach((cellId, cell) -> {
                                if (!cell.isValid()) {
                                    return;
                                }
                                TbVmsIfscTrafDto cellTraf = this.ifscService.find(cell.getVmsIfscId());
                                if (cellTraf == null) {
                                    return;
                                }
                                int cmtrGradCd = cellTraf.getCmtrGradCd();
                                int orgColor = floodFill.getImage().getRGB(cell.getPosX(), cell.getPosY());
                                int trfColor = VmsSymbService.getGradColor(cmtrGradCd);
                                if (orgColor != trfColor) {
                                    floodFill.fill(cell.getPosX(), cell.getPosY(), VmsSymbService.getTrafColor(cmtrGradCd));
                                }
                            });
                            g2d.drawImage(floodFill.getImage(), 0, 0, imgBmp.getWidth(null), imgBmp.getHeight(null), null);
                            ByteArrayOutputStream out = new ByteArrayOutputStream();
                            try {
                                ImageIO.write(formImage, "bmp", out);
                            } catch (IOException e) {
                                log.error("VmsManageService.makeVmsProvideForm: Image Convert error: {}.", formObj.getMemSymbLibNmbr());
                            }
                            g2d.dispose();
                            formObj.setImageData(out.toByteArray());
                            break;
                        case 61:  // 구간명
                        case 62:  // 시점명
                        case 63:  // 종점명
                        case 64:  // 돌발유형
                        case 65:  // 대응문구
                        case 66:  // 통제유형 ==> 여기까지 돌발
                        case 71:  // 발생장소
                        case 72:  // INCD_TITL
                        case 73:  // 통제시점명
                        case 74:  // 통제종점명
                        case 75:  // 00차로 차단
                        case 76:  // 00월00일 ~ 00월00일
                        case 77:  // 대응문구
                        case 78:  // @통제유형 ==> 여기까지 공사/행사
                        case 81:  // 돌발종류
                        case 82:  // 해당도로
                        case 83:  // 우회도로
                        case 84:  // 발생장소 ==> 여기까지 돌발
                            // 돌발문안, 공사/행사문안 임
                            // TB_VMS_FORM_OBJECT_TYPE 테이블에 유형추가하고 돌발화면에서 필요한 항목 추가, TB_INCD_OCRR_VMS 테이블에 컬럼추가해야함
                            formObj.setTextData("");
                            TbVmsIncdDto event = schedule.getEvent();
                            if (event != null) {
                                switch(formObj.getObjectType()) {
                                    case 82: //해당도로
                                    case 83: //우회도로
                                    case 61: formObj.setTextData(event.getVmsIfscNm());         break; //@구간명(사용안함)
                                    case 62: //@시점명
                                    case 73: formObj.setTextData(event.getStrtLctnNm());        break; //통제시점명
                                    case 74: //통제종점명
                                    case 63: formObj.setTextData(event.getEndLctnNm());         break; //@종점명
                                    case 81: //돌발종류
                                    case 64: formObj.setTextData(event.getVmsIncdDetlNm());     break; //@돌발유형
                                    case 66: //@통제유형
                                    case 78: formObj.setTextData(event.getVmsIncdRstrTypeNm()); break; //@통제유형
                                    case 65: //@대응문구(사용안함-발생장소로 일단넣음)
                                    case 77: //대응문구(사용안함-발생장소로 일단넣음)
                                    case 84: //발생장소
                                    case 71: formObj.setTextData(event.getOcrrLctnNm());        break; //발생장소
                                    case 72: formObj.setTextData(event.getIncdTitl());          break; //@공사행사명
                                    case 75: // 00차로 차단
                                        int nLane = ItsUtils.parseIntDef(event.getIncdClsrLane(), 0);
                                        if (nLane == 111111)
                                            formObj.setTextData("차로 차단");
                                        else
                                        if (nLane > 0)
                                            formObj.setTextData("일부 차단");
                                        else
                                            formObj.setTextData(" ");
                                        break;
                                    case 76: //00월00일 ~ 00월00일
                                        try {
                                            String strtDt = event.getIncdStrtDt();
                                            String endDt = event.getIncdEndPrarDt();
                                            String sFrom = strtDt.substring(4,6) + "월" + strtDt.substring(6,8) + "일 " + strtDt.substring(8,10) + "시";
                                            String sTo = endDt.substring(4,6) + "월" + endDt.substring(6,8) + "일 " + endDt.substring(8,10) + "시";
                                            formObj.setTextData(sFrom + " ~ " + sTo);
                                        } catch(Exception e) {}
                                        break;
                                }
                            }
                            if ("".equals(formObj.getTextData())) {
                                formObj.setTextData(" ");
                            }
                            formObj.changeTextPosition();
                            break;
                        case 401: // @관측장소
                        case 402: // @PM10
                        case 403: // @PM2.5
                        case 404: // @PM10등급
                        case 405: // @PM2.5등급
                        case 409: // @통합대기등급
                        case 410: // @통합대기
                        case 411: // @오존
                        case 412: // @오존등급
                            TbVmsAtmpDto atmp = this.atmpService.find(obj.getVmsIfscId());
                            if (atmp == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found Atmp {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), obj.getVmsIfscId());
                            }
                            else {
                                String unitGap = "";
                                boolean isUnit = false;
                                if (formObj.getTextData().contains(" -")) {
                                    isUnit = true;
                                    unitGap = " ";
                                } else if (formObj.getTextData().contains("-")) {
                                    isUnit = true;
                                }

                                boolean isSetFontColor = true;
                                int fontGradeColorCd = 0;
                                if (formObj.getObjectType() == 401) {
                                    isSetFontColor = false;
                                    formObj.setTextData(atmp.getVmsDispNm());
                                } else if (formObj.getObjectType() == 402) {
                                    formObj.setTextData(atmp.getPm10ValDesc(unitGap, isUnit));
                                    fontGradeColorCd = atmp.getPm10Color();
                                } else if (formObj.getObjectType() == 403) {
                                    formObj.setTextData(atmp.getPm25ValDesc(unitGap, isUnit));
                                    fontGradeColorCd = atmp.getPm25Color();
                                } else if (formObj.getObjectType() == 404) {
                                    formObj.setTextData(atmp.getPm10GradDesc());
                                    fontGradeColorCd = atmp.getPm10Color();
                                } else if (formObj.getObjectType() == 405) {
                                    formObj.setTextData(atmp.getPm25GradDesc());
                                    fontGradeColorCd = atmp.getPm25Color();
                                } else if (formObj.getObjectType() == 409) {
                                    formObj.setTextData(atmp.getAtmpGradDesc());
                                    fontGradeColorCd = atmp.getAtmpColor();
                                } else if (formObj.getObjectType() == 410) {
                                    formObj.setTextData(atmp.getAtmpValDesc());
                                    fontGradeColorCd = atmp.getAtmpColor();
                                } else if (formObj.getObjectType() == 411) {
                                    formObj.setTextData(atmp.getO3ValDesc(unitGap, isUnit));
                                    fontGradeColorCd = atmp.getO3Color();
                                } else if (formObj.getObjectType() == 412) {
                                    formObj.setTextData(atmp.getO3GradDesc());
                                    fontGradeColorCd = atmp.getO3Color();
                                }
                                if (isSetFontColor && formObj.getFontColor() != 10) {
                                    // 엘이디 고장난게 있어서 흰색으로 표출해야 해서 폰트색상이 흰색인 경우 그냥 흰색으로 표출하는 것으로.....
                                    formObj.setFontColor(fontGradeColorCd);
                                }
                            }
                            if ("".equals(formObj.getTextData())) {
                                formObj.setTextData(" ");
                            }
                            formObj.changeTextPosition();
                            break;
                        case 101: // @주차장명
                        case 102: // @주차면수
                        case 103: // @주차가능면수
                        case 104: // @주차혼잡도
                            TbVmsParkDto park = this.parkService.find(obj.getVmsIfscId());
                            if (park == null) {
                                formObj.setTextData("-");
                                formObj.setFontColor(eVmsColor.color_red.getValue());

                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Not Found Parking {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), obj.getVmsIfscId());
                            }
                            else {
                                String textFmt = obj.getVmsDsplTxt().replaceAll("@", "");
                                switch(formObj.getObjectType()) {
                                    case 101:// || //@주차장명
                                        formObj.setTextData(park.getParkName());
                                        break;
                                    case 102:// || //@주차면수
                                        formObj.setTextData(park.getTotalCo(textFmt));
                                        break;
                                    case 103:// || //@주차가능면수
                                        formObj.setTextData(park.getRemainCo(textFmt));
                                        formObj.setFontColor(park.getGradeColorCd());
                                        break;
                                    case 104://    //@주차혼잡도
                                        formObj.setTextData(park.getParkingCngstSttsDesc());
                                        formObj.setFontColor(park.getGradeColorCd());
                                        break;
                                }
                            }
                            if ("".equals(formObj.getTextData())) {
                                formObj.setTextData(" ");
                            }
                            formObj.changeTextPosition();
                            break;
                        default:
                            //객체가 가변(소통정보 표출) 인 경우 가변문자를 설정하자....
                            Long vmsIfscId = 0L;
                            switch(formObj.getObjectType()) {
                                case 11: case 12: case 13: case 14: case 15: case 16: case 18: case 19:
                                    vmsIfscId = schedule.getFrstVmsIfscId();
                                    if (form.getVmsFormTypeCd() == eVmsFormType.eFormTp_figure.getValue() || form.getVmsFormTypeCd() == eVmsFormType.eFormTp_congest.getValue()) {
                                        vmsIfscId = schedule.getVmsIfscId();
                                    }
                                    break;
                                case 161: case 162: case 163: case 164: case 165: case 166: case 168:
                                case 21: case 22: case 23: case 24: case 25: case 26: case 28:
                                    vmsIfscId = schedule.getSecdVmsIfscId();
                                    break;
                                case 31: case 32: case 33: case 34: case 35: case 36: case 38:
                                    vmsIfscId = schedule.getThirVmsIfscId();
                                    break;
                                case 41: case 42: case 43: case 44: case 45: case 46: case 48:
                                    vmsIfscId = schedule.getFourVmsIfscId();
                                    break;
                                case 91: case 92:   //@축통행시간(고정), @축소통상황(고정)
                                case 93: case 94: case 95: case 96: // @축시점명(순환), @축종점명(순환), @축소통상황(순환), @축통행시간(순환)
                                    vmsIfscId = schedule.getVmsIfscId();
                                    break;
                                default: continue;
                            }
                            formObj.setTextData("");
                            ifscTraf = this.ifscService.find(vmsIfscId);
                            if (ifscTraf == null) {
                                log.error("VmsManageService.makeVmsProvideForm: VMS({}), FormId({}), Traffic IFSC Not Found {}.",
                                        ctlrNmbr, schedule.getVmsFormId(), vmsIfscId);
                                continue;
                            }
                            switch(formObj.getObjectType()) {
                                case 11: case 21: case 31: case 41: case 161: //구간명
                                    formObj.setTextData(ifscTraf.getRoadNm());
                                    break;
                                case 12: case 22: case 32: case 42: case 93: case 162: //시점명
                                    formObj.setTextData(ifscTraf.getDsplStrtNodeNm());
                                    break;
                                case 13: case 23: case 33: case 43: case 94: case 163: //종점명
                                    formObj.setTextData(ifscTraf.getDsplEndNodeNm());
                                    break;
                                case 14: case 24: case 34: case 44: case 92: case 95: case 164: //소통상황
                                    formObj.setTextData(trafGradeDesc(form.getVmsFormTypeCd(), ifscTraf.getCmtrGradCd()));
                                    // 도형식 하단쪽에 엘이디 고장난게 있어서 흰색으로 표출해야 해서
                                    // 폰트색상이 흰색인 경우 그냥 흰색으로 표출하는 것으로.....
                                    if (formObj.getFontColor() != 10) {
                                        formObj.setFontColor(gradeToColorCode(ifscTraf.getCmtrGradCd()));
                                    }
                                    break;
                                case 19:    //소통상황(가변)
                                    String gradText = trafGradeDesc(form.getVmsFormTypeCd(), ifscTraf.getCmtrGradCd());
                                    if (ifscTraf.getCmtrGradCd() == eTrafficGrade.grade_smoothness.getValue()) {
                                        gradText = String.format("소통원활  약 %d 분", ifscTraf.getTrvlHh());
                                    }
                                    formObj.setTextData(gradText);
                                    formObj.setFontColor(gradeToColorCode(ifscTraf.getCmtrGradCd()));
                                    break;
                                case 15: case 25: case 35: case 45: case 91: case 96: case 165: //통행시간
                                    if (ifscTraf.getTrvlHh() > 0) {
                                        String textFmt = obj.getVmsDsplTxt().replace("@", "");
                                        if (textFmt.contains("0")) {
                                            textFmt = textFmt.replace("0000", "%4d");
                                            textFmt = textFmt.replace("000", "%3d");
                                            textFmt = textFmt.replace("00", "%2d");
                                            textFmt = textFmt.replace("0", "%d");
                                            String textData = String.format(textFmt, ifscTraf.getTrvlHh());
                                            formObj.setTextData(textData);
                                        }
                                        else {
                                            formObj.setTextData(String.valueOf(ifscTraf.getTrvlHh()));
                                        }
                                        formObj.setFontColor(gradeToColorCode(ifscTraf.getCmtrGradCd()));
                                    }
                                    else
                                    {
                                        formObj.setTextData(" ");
                                        formObj.setFontColor(eVmsColor.color_black.getValue());
                                    }
                                    break;
                                case 16: case 26: case 36: case 46: case 166: //통행속도
                                    formObj.setTextData(String.valueOf(ifscTraf.getSped()));
                                    formObj.setFontColor(gradeToColorCode(ifscTraf.getCmtrGradCd()));
                                    break;
                                case 17: case 27: case 37: case 47: //방향이미지...???
                                    break;
                                case 18: case 28: case 38: case 48: case 168: //지점명
                                    formObj.setTextData(ifscTraf.getSpotNm());
                                    break;
                                default: break;
                            }
                            formObj.changeTextPosition();
                            break;
                    }
                } // (ii = 0; ii < vmsForm.getObjects().size()
            } // (int ii = 0; ii < scheObj.size(); ii++)
        });
        log.info("VmsManageService.makeVmsProvideForm: {} ms.", elapsed.milliSeconds());
    }

    /**
     * 데이터베이스에 저장할 폼 이미지를 생성한다.
     */
    public void makeVmsDatabaseForm() {
        log.info("VmsManageService.makeVmsDatabaseForm: START.");
        Elapsed elapsed = new Elapsed();
        this.repoService.getCtlrMap().forEach((ctlrNmbr, vmsObj) -> {
            if ("Y".equals(vmsObj.getDelYn())) {
                return;
            }
            if (this.config.isFigureTrafficCenter()) {
                //도형식폼의 하단소통정보위치를 가운데로 정렬하는 경우
                makeVmsFigureFormPos(vmsObj);
            }
            if (this.config.isTextTrafficCenter() && "VMP2".equals(vmsObj.getTypeCd()))    //2단 12열 일 경우 정렬
            {
                //1단교통정보의 이정을 가운데로 정렬하는 경우
                makeVmsTextFormPos(vmsObj);
            }

            makeVmsDatabaseFormImage(vmsObj);
        });
        this.toggleFormSeq++;
        if (this.toggleFormSeq >= 6) {
            this.toggleFormSeq = 1;
        }
        log.info("VmsManageService.makeVmsDatabaseForm: {} ms.", elapsed.milliSeconds());
    }

    /**
     * 데이터베이스에 저장할 폼 이미지를 생성한다.
     * @param vmsObj
     */
    public void makeVmsDatabaseFormImage(TbVmsCtlrDto vmsObj) {
        if (vmsObj == null) {
            log.error("VmsManagerService.makeVmsDatabaseFormData: VMS Controller null data.");
            return;
        }
        int formCnt = vmsObj.getFormManager().count();
        if (formCnt == 0) {
            log.error("VmsManagerService.makeVmsDatabaseFormData: VMS {}, Form Data Not Found.", vmsObj.getVmsCtlrNmbr());
            return;
        }
        if (formCnt > vmsObj.getMaxPhaseNum()) {
            log.error("VmsManagerService.makeVmsDatabaseFormData: VMS {}, Form Count Over: {}/{} EA.", vmsObj.getVmsCtlrNmbr(), formCnt, vmsObj.getMaxPhaseNum());
            formCnt = vmsObj.getMaxPhaseNum();
        }

        for (int ii = 0; ii < formCnt; ii++) {
            VmsForm pForm = vmsObj.getFormManager().getItem(ii);

            pForm.drawForm(this.formService, this.fontService, this.config);
            pForm.calVmsSchFormType();
            pForm.makeVmsDsplMsgData(this.formService, this.symbService, this.ifscService);

            // FTP 다운로드 하는 경우 폼을 FTP 디렉토리에 저장해야함
            if (pForm.getFileType() != VmsProtocolConst.P_FILE_TYPE_VIDEO  &&   // 비디오
                pForm.getFileType() != VmsProtocolConst.P_FILE_TYPE_STREAM      // 스트리밍영상 등은 로컬파일로 저장할 필요가 없음
            ) {
                // 파일이름을 여기서 변경하면 ftp 파일명이 변경된다.
                if (vmsObj.isFtpDownload()) {
                    String formFileName;
                    if (this.config.isImageSeqSave()) {
                        //시퀀스_운영모드-순서_폼유형-폼번호.bmp
                        formFileName = String.format("%d_%s%02d_%X%s.bmp", this.toggleFormSeq, vmsObj.getOperMode(), ii+1, pForm.getVmsSchFormType(), pForm.getVmsFormId());
                    }
                    else {
                        //운영모드-순서_폼유형-폼번호.bmp
                        formFileName = String.format("%s%02d_%X%s.bmp", vmsObj.getOperMode(), ii+1, pForm.getVmsSchFormType(), pForm.getVmsFormId());
                    }

                    pForm.setLocalFileName(vmsObj.getLocalFormDir() + formFileName);
                    pForm.setFtpFileName(vmsObj.getFtpFormDir() + formFileName);

                    pForm.saveToFile();
                }
            }
            pForm.setSuccess(true);

            log.info("VmsManagerService.makeVmsDatabaseFormData: VMS {}, FORM {}, DnldFormNo {}, Objects {}. ScheduleType {}, FileType {}, File/FtpName {}/{}.",
                    vmsObj.getVmsCtlrNmbr(), pForm.getVmsFormId(), pForm.getDnldFormNo(), pForm.count(), eVmsScheduleType.getValue(pForm.getVmsSchFormType()), pForm.getFileType(), pForm.getLocalFileName(), pForm.getFtpFileName());
        }
    }

    /**
     * 도형식 VMS 하단 소통정보 좌표 위치 조정
     * @param vmsObj
     */
    public void makeVmsFigureFormPos(TbVmsCtlrDto vmsObj) {
        if (vmsObj == null) {
            log.error("VmsManagerService.makeVmsFigureFormPos: VMS Controller null data.");
            return;
        }
        int formCnt = vmsObj.getFormManager().count();
        if (formCnt == 0) {
            log.error("VmsManagerService.makeVmsFigureFormPos: VMS {}, Form Data Not Found.", vmsObj.getVmsCtlrNmbr());
            return;
        }
        if (formCnt > vmsObj.getMaxPhaseNum()) {
            log.error("VmsManagerService.makeVmsFigureFormPos: VMS {}, Form Count Over: {}/{} EA.", vmsObj.getVmsCtlrNmbr(), formCnt, vmsObj.getMaxPhaseNum());
            formCnt = vmsObj.getMaxPhaseNum();
        }

        int arrowGap = 7;
        int trafGap = 17;
        int objCnt;
        int formT;
        VmsFormObject pArw;
        VmsFormObject pStr;
        VmsFormObject pEnd;
        VmsFormObject pTrf;
        int totW;
        int left;

        for (int ii = 0; ii < formCnt; ii++) {
            VmsForm pForm = vmsObj.getFormManager().getItem(ii);
            if (pForm.getVmsFormTypeCd() != eVmsFormType.eFormTp_figure.getValue()) {
                //도형식배경소통정보 폼이 아닌경우 계산하지 않음
                continue;
            }

            formT = pForm.getHeight() - 40; //하단소통정보 객체의 최대높이를 설정, 이것보다 작으면 위치조정 하지 않음
            pArw = null;
            pStr = null;
            pEnd = null;
            pTrf = null;

            //소통상황 시점 -> 종점
            //시점 -> 종점 소통상황
            //위의 2가지 유형에 대하여 가운데 정렬
            objCnt = pForm.count();
            for (int jj = 0; jj < objCnt; jj++) {
                VmsFormObject pFormObj = pForm.getItem(jj);
                if (pFormObj.getPosY() < formT)
                    continue;

                switch(pFormObj.getObjectType()) {
                    case  1:    //	심볼	            Y
                    case  2:    //	이미지	            Y
                        if ("T".equals(pFormObj.getTrfcFillCd()))
                            pArw = pFormObj;
                        break;
                    case 17:    //	@소통정보이미지	    N
                        pArw = pFormObj;
                        break;
                    case 12:    //	@시점명	            Y
                    case 93:    //	@축시점명(순환)	    Y
                    case 13:    //	@종점명	            Y
                    case 94:    //	@축종점명(순환)	    Y
                        if (pFormObj.getObjectType() == 12 || pFormObj.getObjectType() == 93)
                            pStr = pFormObj;
                        else
                            pEnd = pFormObj;
                        break;
                    case 14:    //	@소통상황	        Y
                    case 15:    //	@통행시간	        Y
                    case 16:    //	@통행속도	        N
                    case 95:    //	@축소통상황(순환)	Y
                    case 96:    //	@축통행시간(순환)	Y
                        pTrf = pFormObj;
                        break;
                    default: break;
                }
            }
            if (pArw == null || pStr == null || pEnd == null || pTrf == null) {
                continue;
            }

            int totalWidth = pStr.getTextWidth() + pArw.getTextWidth() + pEnd.getTextWidth() + pTrf.getTextWidth();
            if (totalWidth > (pForm.getWidth() - arrowGap - arrowGap - trafGap - 10) ) {
                arrowGap =  3;
                trafGap = 10;
            }
            totW = pStr.getTextWidth() + arrowGap + pArw.getTextWidth() + arrowGap + pEnd.getTextWidth() + trafGap + pTrf.getTextWidth();
            left = (pForm.getWidth() - totW) / 2;
            if (pStr.getPosX() < pTrf.getPosX()) {
                //시점 -> 종점 소통상황
                pStr.setPosX(left);
                pArw.setPosX(pStr.getPosX() + pStr.getTextWidth() + arrowGap);
                pEnd.setPosX(pArw.getPosX() + pArw.getTextWidth() + arrowGap);
                pTrf.setPosX(pEnd.getPosX() + pEnd.getTextWidth() + trafGap);
            }
            else {
                //소통상황 시점 -> 종점
                pTrf.setPosX(left);
                pStr.setPosX(pTrf.getPosX() + pTrf.getTextWidth() + trafGap);
                pArw.setPosX(pStr.getPosX() + pStr.getTextWidth() + arrowGap);
                pEnd.setPosX(pArw.getPosX() + pArw.getTextWidth() + arrowGap);
            }
        }
    }

    /**
     * 1단 텍스트 소통정보 좌표 위치 조정
     * @param vmsObj
     */
    public void makeVmsTextFormPos(TbVmsCtlrDto vmsObj) {
        // 시점 화살표 종점 의 좌표는 폼생성할때 정의된데로 하고
        // 시점 화살표 종점의 전체 길이를 폼의 넓이로 환산하여 가운데 정렬
        // 시점(오른쪽정렬) 화살표(의미없고) 종점(왼쪽정렬) 로 폼설정에서 설정하도록 한다.
        // 시점/화살표/종점의 Y축 좌표도 설정한 데로 표출한다.
        // 시점/화살표/종점을 폼의 가운데에 정렬하는 것이다.
        if (vmsObj == null) {
            log.error("VmsManagerService.makeVmsTextFormPos: VMS Controller null data.");
            return;
        }
        int formCnt = vmsObj.getFormManager().count();
        if (formCnt == 0) {
            log.error("VmsManagerService.makeVmsTextFormPos: VMS {}, Form Data Not Found.", vmsObj.getVmsCtlrNmbr());
            return;
        }
        if (formCnt > vmsObj.getMaxPhaseNum()) {
            log.error("VmsManagerService.makeVmsTextFormPos: VMS {}, Form Count Over: {}/{} EA.", vmsObj.getVmsCtlrNmbr(), formCnt, vmsObj.getMaxPhaseNum());
            formCnt = vmsObj.getMaxPhaseNum();
        }

        int objCnt;
        int gapL;
        VmsFormObject pArw;
        VmsFormObject pStr;
        VmsFormObject pEnd;
        int totW;
        int left;

        for (int ii = 0; ii < formCnt; ii++) {
            VmsForm pForm = vmsObj.getFormManager().getItem(ii);
            if (pForm.getVmsFormTypeCd() != eVmsFormType.eFormTp_traf_1.getValue()) {
                //소통상황(1단) 폼이 아닌경우 계산하지 않음
                continue;
            }

            pArw = null;
            pStr = null;
            pEnd = null;

            // 시점(12) -> 종점(13)
            objCnt = pForm.count();
            for (int jj = 0; jj < objCnt; jj++) {
                VmsFormObject pFormObj = pForm.getItem(jj);
                switch(pFormObj.getObjectType()) {
                    case 12:    //	@시점명	            Y
                        pStr = pFormObj;
                        break;
                    case 13:    //	@종점명	            Y
                        pEnd = pFormObj;
                        break;
                    default: break;
                }
            }
            if (pStr == null || pEnd == null) {
                continue;
            }
            for (int jj = 0; jj < objCnt; jj++) {
                VmsFormObject pFormObj = pForm.getItem(jj);
                switch(pFormObj.getObjectType()) {
                    case  1:    //	@심볼	            Y
                    case  2:    //	@이미지	            Y
                    case 17:    //	@소통정보이미지      Y
                        // if (pFormObj->TRFC_FILL_CD == "T")
                        if (pFormObj.getPosY() <= (pStr.getPosY()+pStr.getDsplHeight())) {
                            pArw = pFormObj;
                        }
                        break;
                    default: break;
                }
            }

            if (pArw != null && pStr != null && pEnd!= null ) {
                //시점 화살표 종점 순으로 되어 있어야 한다.
                int nRange = pStr.getPosY() + pStr.getDsplHeight();
                if (pArw.getPosY() <= nRange && pEnd.getPosY() <= nRange)
                {
                    gapL = pArw.getPosX()- (pStr.getPosX()+ pStr.getTextWidth());   // 시점 화살표 사이의 간격
                    totW = (pEnd.getPosX()+ pEnd.getTextWidth()) - pStr.getPosX(); // 전체길이
                    left = (pForm.getWidth() - totW) / 2;

                    pStr.setPosX(left);
                    pArw.setPosX(pStr.getPosX()+ pStr.getTextWidth() + gapL);
                    pEnd.setPosX((left + totW) - pEnd.getTextWidth());
                }
            }
        }
    }

    /**
     * 폼유형에 따라 소통정보 등급에 해당하는 문자열 리턴
     * @param formType
     * @param cmtrGradCd
     * @return
     */
    public String trafGradeDesc(int formType, int cmtrGradCd) {
        if (formType == eVmsFormType.eFormTp_figure.getValue()) {
            switch(cmtrGradCd) {
                case 1: return this.config.getFigureTrafGrad1();
                case 2: return this.config.getFigureTrafGrad2();
                case 3: return this.config.getFigureTrafGrad3();
                default: break;
            }
        }
        else {
            switch(cmtrGradCd) {
                case 1: return this.config.getTextTrafGrad1();
                case 2: return this.config.getTextTrafGrad2();
                case 3: return this.config.getTextTrafGrad3();
                default: break;
            }
        }
        return " ";
    }

    /**
     * 소통정보 등급에 따른 표출 색상코드를 리턴
     * @param cmtrGradCd
     * @return
     */
    public int gradeToColorCode(int cmtrGradCd) {
        switch(cmtrGradCd) {
            case 1: return eVmsColor.color_green.getValue();
            case 2: return eVmsColor.color_amber.getValue();
            case 3: return eVmsColor.color_red.getValue();
            default: break;
        }
        return eVmsColor.color_green.getValue();
    }
}
