let _size  = [48, 48, 48, 48, 40, 32, 24, 22, 20, 18, 18];
let _AtrdMap = new Map();
let selectIncidentId = null;
let _MarkerArr = [];

const vmsWHMap = new Map();
vmsWHMap.set('VMC1', {width: 400, height: 64});
vmsWHMap.set('VMC2', {width: 384, height: 64});
vmsWHMap.set('VMC3', {width: 288, height: 160});
vmsWHMap.set('VMC4', {width: 256, height: 192});

//시설물 유형
// const _FacilityArray = ['cctv', 'vms', 'intersection', 'incident', 'traffic', 'parking', 'intersectionCamera', 'atrd'];
const _FacilityArray = ['cctv', 'vms', 'incident', 'traffic', 'parking', 'atrd'];

const g_color = new Map();
g_color.set('LTC0', '#888888;');
g_color.set('LTC1', '#2fba2c;');
g_color.set('LTC2', '#ffc500;');
g_color.set('LTC3', '#ee0000;');

const CCTV_DISPLAY_TIME = 30 * 1000; // 영상 표출 시간
const VMS_DISPLAY_TIME  = 4 * 1000; // VMS 이미지 표출 시간
const _TrafficListMap = new Map();
let _MapHandler; //카카오 맵 핸들러
let _Level = 6; //현재 줌레벨
let _MarkerHandle;

let _methodMap = new Map();
_methodMap.set('cctv', getCctv);
_methodMap.set('vms', getVms);
_methodMap.set('incident', getIncident);
// _methodMap.set('intersection', getIntersection);
_methodMap.set('traffic', getAtrd);
_methodMap.set('parking', getParking);

$(()=>{
    // Map 요소 ID로 생성
    _MapHandler = new MapHandler('map');
    _MapHandler.init();
    _MarkerHandle = new MarkerManager([..._FacilityArray]);
    _MarkerHandle.init();

    // 시설물 유형 정보로 flag 값 세팅
    if (_type) {
        _MarkerHandle.setIsDraw(_type, true);
        _MarkerHandle.setIsDrawList(_type, true);
        _methodMap.get(_type)();
    }

    /**
     * 범례 클릭 이벤트 및 색 변환
     * */
    $('.legend-bottom button').on('click', function (){
        const target     = $(this);
        const legendIcon = target.children().eq(0);
        const isShow     = !legendIcon.hasClass('active');

        const id = target.attr('id');
        const type = id.replace('-legend', '');

        let subType = null;
        //
        // if (type === 'intersection') {
        //     subType = 'intersectionCamera';
        // }

        if (type === 'traffic') {
            subType = 'atrd';
            getVertex();
        }
        else {
            const markerArr = _MarkerHandle.findMarkerArr(type);
            if (markerArr && markerArr.length <= 0){
                _methodMap.get(type)();
            }
        }

        let classToggle = 'removeClass';
        if (isShow === true) {
            classToggle = 'addClass';
        }

        legendIcon[classToggle]('active');
        target[classToggle]('active');
        _MarkerHandle.showHideMarker(type, isShow);
        if (subType) {
            _MarkerHandle.showHideMarker(subType, isShow);
        }
    })

    /**
     * 범례 호버 색 변환
     * */
    $('.legend-bottom ul li').hover((event)=>{
        const iconDiv = $(event.currentTarget).children().eq(0);
        const childDiv = iconDiv.children().eq(0);
        if (!iconDiv.hasClass('active')) {
            childDiv.addClass('hover');
        }
        else {
            if (childDiv.hasClass('hover')) {
                childDiv.removeClass('hover');
            }
        }
    },
    (event)=>{
        const iconDiv = $(event.currentTarget).children().eq(0);
        const childDiv = iconDiv.children().eq(0);

        if (!iconDiv.hasClass('active')) {
            childDiv.removeClass('hover');
        }
        else {
            if (childDiv.hasClass('hover')) {
                childDiv.removeClass('hover');
            }
        }
    });

    /**
     * 탭 호버 색 변환
     * */
    $('.left-list-area .list-tab > li').hover((event)=>{
        const iconDiv  = $(event.currentTarget);
        const childDiv = iconDiv.children().eq(0);
        if (!iconDiv.hasClass('active')) {
            childDiv.addClass('hover');
        }
        else {
            if (childDiv.hasClass('hover')) {
                childDiv.removeClass('hover');
            }
        }
    },
    (event)=>{
        const iconDiv = $(event.currentTarget);
        const childDiv = iconDiv.children().eq(0);
        if (!iconDiv.hasClass('active')) {
            childDiv.removeClass('hover');
        }
        else {
            if (childDiv.hasClass('hover')) {
                childDiv.removeClass('hover');
            }
        }
    });

    //
    // $('.tab-title > div').on('click', function(){
    //     const $spot = $('.list-content.spot');
    //     const $list = $('.list-content.list');
    //     let searchType = $('.tab-title > div.active').text();
    //     if (searchType === $(this).text()) return;
    //     $('.tab-title > div').toggleClass("active");
    //     searchType = $(this).text();
    //     let list;
    //     let spot;
    //     if (searchType === '지점 검색') {
    //         list = 'none';
    //         spot = 'block';
    //         $spot.html("");
    //     }
    //     else {
    //         list = 'block';
    //         spot = 'none';
    //         $('.list-content.list > li').css('display', 'block');
    //     }
    //
    //     $('#search-box').val("");
    //     $list.css('display', list);
    //     $spot.css('display', spot);
    //     if (_MarkerArr.length > 0) {
    //         _MarkerArr.forEach((obj)=>{
    //             obj.setMap(null);
    //         });
    //         _MarkerArr = [];
    //     }
    // })

    //좌측 리스트 검색 이벤트 (리스트 명칭 또는 주소와 비교)
    $('#search-box').on('keyup', function () {
        const searchText = $(this).val();
        const $list = $('.list-content.list > li');
        if (!$list.length) return;
        const $activeTab = $('.list-tab .active');
        const id = $activeTab.attr('id');
        let type = id.replace("-tab", "");
        // if (type === 'intersection') type = 'intersectionCamera';

        const markerArr = _MarkerHandle.findMarkerArr(type);
        let showIdArr = [];
        if (markerArr.length) {
            showIdArr = markerArr.map((item)=>{
                if (item.ADDR && item.ADDR.includes(searchText)) {
                    // if (type === 'intersectionCamera') {
                    //     return 'intersection-' + item.prop.ixr_id;
                    // }
                    return type + '-' + item.ID;
                }
            })
        }

        for (let ii = 0; ii < $list.length; ii++) {
            const li = $list.eq(ii);
            if ($(this).val().length === 0) {
                li.css('display', 'block');
            }
            else {
                let text = li.text().toLowerCase();
                const liId = li.attr('id');
                if (showIdArr.includes(liId)){
                    li.css('display', 'block');
                }
                else if (text.includes(searchText.toLowerCase())) {
                    li.css('display', 'block');
                }
                else {
                    li.css('display', 'none');
                }
            }
        }
    })
});

/**
 * 지점찾기 이벤트
 * @param event
 */
function searchLocation(event) {
    if (event && event.key !== 'Enter') {
        return;
    }
    if (_MarkerHandle.selectedMarker) {
        _MarkerHandle.selectedMarker.close();
    }
    $('.list-content.spot').empty();
    const $search = $('.location-box input');
    if (_MarkerArr.length > 0) {
        _MarkerArr.forEach((obj)=>{
            obj.setMap(null);
        });
        _MarkerArr = [];
    }
    const searchText = $search.val();
    if (!searchText || !searchText.trim()) return;
    const geoCoder = new kakao.maps.services.Geocoder();
    geoCoder.addressSearch('경북 포항시 '+searchText, function(result, status){
        if (status === 'OK' && result && result.length > 0) {
            let str = "";
            const setMap = new Map();

            const bounds = new kakao.maps.LatLngBounds();
            let minX;
            let minY;
            let maxX;
            let maxY;

            result.forEach((obj, idx)=>{
                let addr = obj.address_name;
                if (obj.x && obj.y) {
                    const position = getKakaoPosition(obj.y, obj.x);
                    let marker = new kakao.maps.Marker({
                        position : position,
                        title : addr ,
                        content : addr
                    });

                    new kakao.maps.event.addListener(marker, 'click', function() {
                        moveLocation(obj.x, obj.y, idx);
                    })
                    if (!minX) {
                        minX = obj.x;
                    }

                    if (!maxX) {
                        maxX = obj.x;
                    }
                    if (!minY) {
                        minY = obj.y;
                    }

                    if (!maxY) {
                        maxY = obj.y;
                    }

                    minX = minX <= obj.x ? minX : obj.x;
                    maxX = maxX >= obj.x ? maxX : obj.x;
                    minY = minY <= obj.y ? minY : obj.y;
                    maxY = maxY >= obj.y ? maxY : obj.y;

                    marker.setMap(_MapHandler.map);
                    _MarkerArr.push(marker);
                    setMap.set(addr, obj);

                    str+= `<li id="spot-${idx}" onclick="moveLocation(${obj.x}, ${obj.y}, ${idx})">
                                ${obj.address_name}
                           </li>`;
                }
            });

            if (minX && minY && maxX && maxY) {
                bounds.extend(new kakao.maps.LatLng(minY, minX));
                bounds.extend(new kakao.maps.LatLng(maxY, maxX));
                _MapHandler.setBounds(bounds);
                $('.list-content.spot').html(str);
                getVertex();
            }
        }
    }, {page : 1, size: 30});
}

/**
 * 지점 클릭 위치 이동
 * @param xCoordinate x좌표
 * @param yCoordinate y좌표
 * @param listIndex 지점 선택 인덱스
 */
function moveLocation(xCoordinate, yCoordinate, listIndex) {
    if (_MarkerHandle.selectedMarker) {
        _MarkerHandle.selectedMarker.close();
    }
    const position = getKakaoPosition(yCoordinate, xCoordinate);
    _MapHandler.setCenter(position);
    _MapHandler.setLevel(3);
    $('.list-content.spot > li.click').removeClass('click');
    $('#spot-' +listIndex).addClass('click');
    getVertex();
}

/**
 * cctv 데이터 가져오기
 */
function getCctv() {
    getDataAsync('/api/traffic/cctv-list', 'POST', null, null, (cctvData)=>{
        getDataAsync('/api/itcs/detail-list', 'POST', null, null, (itcsData)=> {
            let cctvTotalData = [...cctvData];
            if (itcsData && itcsData.length > 0) {
                itcsData.forEach((itcs)=>{
                    let obj = intersectionCameraTransferToCctv(itcs);
                    cctvTotalData.push(obj);
                })
            }
            receiveMarkerData(cctvTotalData, TbCCtvObj, 'cctv');
        })
    }, null);
}

function intersectionCameraTransferToCctv(itcs) {
    return {
        cctv_mngm_nmbr: itcs.cmra_id + '_' + itcs.drct_dvsn_cd,
        cctv_ctlr_id: itcs.cmra_id + '_' + itcs.drct_dvsn_cd,
        cctv_ctlr_ip: null,
        cctv_ctlr_port: null,
        cctv_capt_ip: null,
        cctv_capt_port: null,
        cctv_fibr_ip: null,
        cctv_encd_ip: null,
        strm_svr_ip: null,
        strm_svr_port: null,
        strm_sesn_nm: null,
        x_crdn: itcs.cmra_x_crdn,
        y_crdn: itcs.cmra_y_crdn,
        istl_lctn_nm: itcs.drct_lctn,
        istl_lctn_addr: itcs.istl_lctn_addr,
        cctv_chnl: 0,
        del_yn: "N",
        frst_regr_nmbr: null,
        frst_rgst_dt: null,
        last_crpr_nmbr: null,
        last_crct_dt: null,
        link_id: null,
        node_id: null,
        cctv_id: null,
        rely_port: null,
        strm_rtsp_addr: null,
        strm_rtmp_addr: null,
        strm_http_addr: itcs.hmpg_cmra_url,
        strm_stor_addr: null,
        area_cd: null,
        cctv_type: null,
        cctv_sbst_imgn: null,
        cctv_sbst_dspl_yn: null,
        cctv_lc: null,
        cmra_id: null,
        cmra_pswd: null,
        cmra_chnl: null,
        cmra_modl: null,
        full_strm_sesn_nm: null,
        cmra_port: null,
        hmpg_dspl_en: 1,
        mac_addr: null,
        serial_no: null,
        tta_cnfn_yn: null
    };
}

/**
 * vms 데이터 가져오기
 */
function getVms() {
    getDataAsync('/api/traffic/vms-list', 'POST', null, null, (jsonData)=>{
        if (jsonData && jsonData.length > 0) {
            jsonData.sort((a, b)=>{
                return a.vms_nm > b.vms_nm ? 1 : a.vms_nm < b.vms_nm ? -1 : 0;
            })
            // receiveFacilityData(jsonData, TbVmsObj,'vms');
        }
        receiveMarkerData(jsonData, TbVmsObj, 'vms');
    }, null);
}

/**
 * 돌발정보
 */
function getIncident () {
    getDataAsync('/api/traffic/incident-list', 'POST', null, null, (jsonData)=>{
        receiveMarkerData(jsonData, TbIncdObj, 'incident');
    }, null);
}

/**
 * 스마트교차로
 */
function getIntersection() {
    getDataAsync('/api/itcs/list', 'POST', null, null, (jsonData)=>{
        if (jsonData && jsonData.length > 0) {
            jsonData.sort((a, b)=>{
                return a.ixr_nm > b.ixr_nm ? 1 : a.ixr_nm < b.ixr_nm ? -1 : 0;
            });
            let data = [];
            jsonData.forEach((obj)=>{
                if (obj.detail.length > 0) {
                    data.push(obj);
                }
            })
            receiveMarkerData(data, IntersectionObj, 'intersection');
        }
    }, null);
}

/**
 * 주차정보
 */
function getParking() {
    getDataAsync('/api/traffic/parking-list', 'POST', null, null, (jsonData)=>{
        receiveMarkerData(jsonData, TbParkingObj, 'parking');
    }, null);
}

/**
 * 간선도로 정보
 */
function getAtrd() {
    getDataAsync("/api/traffic/atrd-vertex-all", "POST", null, null, (jsonData)=>{
        if (jsonData && jsonData.length > 0) {
            _AtrdMap = new Map();
            jsonData.forEach((obj)=>{
                _TrafficListMap.set(obj.level, obj.list);
                obj.list.forEach((atrd)=>{
                    const key = atrd.atrd_id +'_' + atrd.drct_cd +'_' + obj.level;
                    let atrdArr = _AtrdMap.get(key);
                    if (!atrdArr) {
                        _AtrdMap.set(key, []);
                        let atrdTemp = {...atrd};
                        atrdTemp.ID = key;
                        const atrdObj = new TbAtrdObj(atrdTemp);
                        atrdObj.init();
                        _MarkerHandle.markerMaps.get('atrd').markers.push(atrdObj);
                    }
                    _AtrdMap.get(key).push(atrd);
                });
            });

            getVertex();
        }
    }, null);

    getDataAsync('/api/traffic/atrd-list', 'POST', null, null, (jsonData)=>{
        let emptyStr = '<div class="empty">소통정보 리스트가 없습니다.</div>'
        let listStr = "";
        let mobileStr = "";

        if (jsonData) {
            const atrdList = Object.keys(jsonData).sort().reduce(
                (newObj,key) => {
                    newObj[key] = jsonData[key];
                    return newObj;
                },
                {}
            );

            for (let key in atrdList){
                let list = jsonData[key];
                if (list && list.length === 2) {
                    const upHill   = list[list.findIndex(obj => obj.drct_cd === '0')];
                    const downHill = list[list.findIndex(obj => obj.drct_cd === '1')];
                    const upHillId   = upHill.atrd_id;
                    const downHillId = downHill.atrd_id;

                    listStr += `<li id="atrd_${upHillId}_${downHillId}" onclick="atrdClickEvent('${upHillId}', '${downHillId}')">
                                    ${key}
                                </li><ul id="${upHillId}_${downHillId}"></ul>`
                    mobileStr += `<option value="${upHillId}_${downHillId}">${key}</option>`
                }
            }
            const $mobileSelect = $('.mobile-select');
            $mobileSelect.append($(mobileStr));
            $mobileSelect.on('change', function(){
                const ids = $(this).val();
                if (ids && ids !== '-') {
                    const idArr = ids.split('_');
                    atrdClickEvent(idArr[0], idArr[1]);
                }
                else {
                    let array = _MarkerHandle.selectedAtrd;
                    if (array.length > 0) {
                        array.forEach((atrdId)=>{
                            const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
                            if (atrdObj) {
                                atrdObj.marker.setMap(null);
                            }
                        })
                        _MarkerHandle.selectedAtrd = [];
                        // getVertex();
                    }
                }
            })
        }
        else {
            listStr = emptyStr;
        }
        const listSection = $('.left-list-area .list-content.list');
        listSection.empty();
        listSection.html(listStr);
    });

}

/**
 * 소통정보 가져오기
 */
function getVertex() {
    let level = _MapHandler.getLevel();
    const bounds = _MapHandler.getBounds();
    const swLatLng = bounds.getSouthWest();
    const neLatLng = bounds.getNorthEast();
    const data = {
        levl : level,
        swLat : swLatLng.getLat(),
        neLat : neLatLng.getLat(),
        swLng : swLatLng.getLng(),
        neLng : neLatLng.getLng(),
    }

    getDataAsync('/api/traffic/vertex-list', 'POST', data, null, (jsonData)=>{
        let markerArr = _MarkerHandle.findMarkerArr('traffic');
        if (markerArr.length > 0) {
            markerArr.forEach((obj) => {
                obj.setVisibleMarker(false);
                if (obj.infoWindow) {
                    obj.infoWindow.setMap(null);
                }
            })

            _MarkerHandle.findMarkerArr('traffic').markers = [];
        }
        if (jsonData && jsonData.length > 0) {
            let upHill = null;
            let dowHill = null;
            let upHillId = null;
            let downHillId = null;
            let roadIdArr = [];
            let uRoadIdArr = [];
            let dRoadIdArr = [];
            if (_MarkerHandle.selectedAtrd.length > 0 && _MarkerHandle.isDraw('traffic')) {
                const selectedAtrd = _MarkerHandle.selectedAtrd;
                upHill = _MarkerHandle.findMarkerObj('atrd', selectedAtrd[0]);
                dowHill = _MarkerHandle.findMarkerObj('atrd', selectedAtrd[1]);
                upHill.setImage(0);
                dowHill.setImage(1);
                upHill.marker.setMap(_MapHandler.map);
                dowHill.marker.setMap(_MapHandler.map);
                const upHillArr = _AtrdMap.get(selectedAtrd[0]);
                const downHillArr = _AtrdMap.get(selectedAtrd[1]);
                const roadArr = [...upHillArr, ...downHillArr];
                upHillId = selectedAtrd[0].substring(0, selectedAtrd[0].indexOf('_'));
                downHillId = selectedAtrd[1].substring(0, selectedAtrd[1].indexOf('_'));
                roadArr.forEach((obj)=>{
                    if (obj.atrd_id === upHillId) {
                        uRoadIdArr.push(obj);
                    }
                    else {
                        dRoadIdArr.push(obj);
                    }
                    roadIdArr.push(obj.road_id.toString());
                });

            }

            const $ul = $(`#${upHillId}_${downHillId}`);

            const trafficArr = [];
            jsonData.forEach((obj)=>{
                const trafficObj = new TrafficObj(obj);
                if (roadIdArr.length > 0) {
                   if (roadIdArr.includes(trafficObj.ID.toString())) {
                        trafficObj.polyBackLine.setOptions({
                            strokeColor : 'black',
                        });
                        trafficArr.push(obj);
                    }
                }
                _MarkerHandle.markerMaps.get('traffic').markers.push(trafficObj);
            })
            if ($ul[0]) {
                $ul.empty();
                let uRoadObjArr = [];
                let dRoadObjArr = [];
                let upStr = '';
                let downStr = '';
                for (let ii= uRoadIdArr.length - 1; ii >= 0; ii--) {
                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === uRoadIdArr[ii].road_id.toString());
                    if (idx >= 0) {
                        uRoadObjArr.push(trafficArr[idx])
                    }
                }

                uRoadObjArr.forEach((obj, idx)=>{
                    const {cmtr_grad_cd, strt_nm_node, end_nm_node, sped, trvl_hh, roadway_id} = obj;
                    const isEnd = (uRoadObjArr.length - 1) === idx;
                    let addClass = "";
                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
                        addClass = "on";
                    }
                    //upStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isEnd})" onmouseleave="showSelectLine('${roadway_id}', false, ${isEnd})">
                    upStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
                                <div>
                                    <div class="up arrow ${cmtr_grad_cd}"></div>
                                </div>
                                <div>
                                    <div>${end_nm_node}</div>
                                    <div>${sped}km/h 약 ${trvl_hh}분</div>
                                </div>
                            </div>`
                    if (isEnd) {
                        upStr+= `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${strt_nm_node}</div>`;
                    }
                })

                for (let ii= 0; ii < dRoadIdArr.length; ii++) {
                    const idx = trafficArr.findIndex(obj=> obj.roadway_id === dRoadIdArr[ii].road_id.toString());
                    if (idx >= 0) {
                        dRoadObjArr.push(trafficArr[idx]);
                    }
                }

                dRoadObjArr.forEach((obj, idx)=>{
                    const {sped, trvl_hh, cmtr_grad_cd, strt_nm_node, end_nm_node, roadway_id} = obj;
                    let isStart = idx === 0;
                    // downStr +=`<div class="traffic-list" onmouseover="showSelectLine('${roadway_id}', true, ${isStart})" onmouseleave="showSelectLine('${roadway_id}', false, ${isStart})">
                    let addClass = "";
                    if (_MarkerHandle.trafficObj && roadway_id === _MarkerHandle.trafficObj) {
                        addClass = "on";
                    }
                    downStr +=`<div class="traffic-list ${addClass}" onclick="selectRoadPoint('${roadway_id}')">
                                    <div>
                                        <div class="down arrow ${cmtr_grad_cd}"></div>
                                    </div>
                                    <div>
                                        <div>${strt_nm_node}</div>
                                        <div>${sped}km/h 약 ${trvl_hh}분</div>
                                    </div>
                                </div>`
                    if (dRoadObjArr.length - 1 === idx) {
                        downStr += `<div style="margin-left: 35px; padding-top: 10px; padding-bottom: 10px;">${end_nm_node}</div>`
                    }
                })
                let str = `<li>
                                <div>
                                        <div>
                                            ${upStr}
                                        </div>
                                        <div>
                                            ${downStr}
                                        </div>
                                </div>
                            </li>`;

                $ul.html(str);
            }
        }
    });
}
let timer = null;
let infoWindow = null;
function selectRoadPoint(id) {
    if (timer) {
        clearTimeout(timer);
        if (infoWindow) {
            infoWindow.setMap(null);
        }
    }
    const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
    _MarkerHandle.trafficObj = id;

    const xCoordinateArr = trafficObj.obj.x_crdn.split(',');
    const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
    const x = xCoordinateArr[Math.round(xCoordinateArr.length / 2)];
    const y = yCoordinateArr[Math.round(yCoordinateArr.length / 2)];
    let position    = getKakaoPosition(y, x);

    _MapHandler.map.setCenter(position);
    getVertex();

    trafficObj.polyBackLine.setOptions({
        strokeColor : '#FF00FF',
    });


    const imageSize     = new kakao.maps.Size(19, 27);
    const imageOffset   = new kakao.maps.Point(8.5, 27);
    const imageOption = {
        offset: imageOffset,
        alt   : trafficObj.NAME,
    };

    let markerImage = new kakao.maps.MarkerImage('/images/icon/select_position.png', imageSize, imageOption);
    infoWindow = new kakao.maps.Marker({
        position: position,
        image: markerImage,
        zIndex: 99999,
    });
    infoWindow.setMap(_MapHandler.map);
    timer = setTimeout(()=>{
        infoWindow.setMap(null);
    }, 2000);

}

function showSelectLine(id, isShow, isEnd) {
    const trafficObj = _MarkerHandle.findMarkerObj('traffic', id);
    if (trafficObj.infoWindow) trafficObj.infoWindow.setMap(null);
    if (isShow) {
        const xCoordinateArr = trafficObj.obj.x_crdn.split(',');
        const yCoordinateArr = trafficObj.obj.y_crdn.split(',');
        let position    = getKakaoPosition(yCoordinateArr[0], xCoordinateArr[0]);
        if (isEnd) {
            const lastYIdx = yCoordinateArr.length - 1;
            const lastXIdx = xCoordinateArr.length - 1;
            position = getKakaoPosition(yCoordinateArr[lastYIdx], xCoordinateArr[lastXIdx])
        }
        const {cmtr_grad_cd, grad_nm, strt_nm_node, end_nm_node, trvl_hh, sped} = trafficObj.obj;
        const iwContent =
            `<div class="trafficPop">
                            <div class="traffic-speed ${cmtr_grad_cd}">
                                <span class="traffic-name">${trafficObj.NAME}</span>
                                <span class="traffic-speed-info border-back ${cmtr_grad_cd}">${grad_nm}</span>
                            </div>
                            <div class="traffic-info">
                                <span>${strt_nm_node} → ${end_nm_node}</span>
                                <br>
                                <span>소요시간 : </span>
                                <span class="${cmtr_grad_cd}">약 ${textFormat(trvl_hh)}분 </span>
                                <span>&nbsp; 속도 : </span>
                                <span class="${cmtr_grad_cd}">약 ${textFormat(sped)}km/h</span>
                            </div>
                         </div>`;
        trafficObj.infoWindow = new kakao.maps.CustomOverlay({
            clickable: true,
            position: position,
            content: iwContent,
            xAnchor: -0.05,
            yAnchor: 1.1,
            zIndex: 999
        });

        trafficObj.polyLine.setOptions({strokeColor: '#0000FF'});
        trafficObj.infoWindow.setMap(_MapHandler.map);
    }
    else {
        trafficObj.polyLine.setOptions({strokeColor: g_color.get(trafficObj.obj.cmtr_grad_cd)});
    }
}

/**
 * 간선도로 클릭 이벤트
 * @param AupHillId 상행 ID
 * @param AdownHillId 하행 ID
 */
function atrdClickEvent(AupHillId, AdownHillId) {
    if (_MarkerHandle.trafficObj) {
        _MarkerHandle.trafficObj = null;
    }
    if (! _MarkerHandle.findMarkerArr('atrd').length) {
        return alert('도로정보를 불러오는 중입니다.');
    }

    if (_MarkerHandle.selectedAtrd.length > 0) {
        _MarkerHandle.selectedAtrd.forEach((atrdId)=>{
            _MarkerHandle.findMarkerObj('atrd', atrdId).marker.setMap(null);
        })
    }
    _MarkerHandle.selectedAtrd = [];
    const isDrawList = _MarkerHandle.isDrawList('traffic');
    const $selectedLi = $('.left-list-area .list-content.list > li.click');
    if (isDrawList && $selectedLi) {
        $selectedLi.removeClass('click');
    }

    //상행, 하행 ID 가 있다면 실행
    if (AupHillId && AdownHillId) {
        const $selectLi = $('#atrd_' + AupHillId  + "_" + AdownHillId);
        $selectLi.addClass('click');
        $selectLi.focus();
        let level = getAtrdLevel(_Level);
        let upHill   = _MarkerHandle.findMarkerObj('atrd', AupHillId + '_0_' + level);
        let downHill = _MarkerHandle.findMarkerObj('atrd', AdownHillId + '_1_' + level);

        const bounds = new kakao.maps.LatLngBounds();
        bounds.extend(new kakao.maps.LatLng(upHill.prop.y_crdn_min, upHill.prop.x_crdn_min));
        bounds.extend(new kakao.maps.LatLng(upHill.prop.y_crdn_max, upHill.prop.x_crdn_max));
        bounds.extend(new kakao.maps.LatLng(downHill.prop.y_crdn_min, downHill.prop.x_crdn_min));
        bounds.extend(new kakao.maps.LatLng(downHill.prop.y_crdn_max, downHill.prop.x_crdn_max));
        _MapHandler.setBounds(bounds);
        let boundAfterLevel = getAtrdLevel(_Level);

        _MarkerHandle.selectedAtrd = [AupHillId + '_0_' + boundAfterLevel, AdownHillId + '_1_' + boundAfterLevel];
        getVertex();
        if ($('.list-content > ul.on')[0]) {
            $('.list-content > ul.on').removeClass('on');
        }
        $(`#${AupHillId}_${AdownHillId}`).addClass('on');
    }
}

/**
 * 간선도로 레벨 설정
 * @param level 현재 레벨
 * @returns {number} 데이터 레벨
 */
function getAtrdLevel(level) {
    if (level >= 8) {
        level = 7;
    }
    else if (level >= 6) {
        level = 6;
    }
    return level;
}

/**
 * 마커 객체
 */
class MarkerObj {
    constructor({ID, NAME, X_CRDN, Y_CRDN, URL, TYPE, IMAGE, IMAGE_TYPE, DATA, ADDR}) {
        this.ID         = ID;
        this.NAME       = NAME;
        this.X_CRDN     = X_CRDN;
        this.Y_CRDN     = Y_CRDN;
        this.URL        = URL;
        this.type       = TYPE;
        this.ADDR       = ADDR;
        this.marker     = null;
        this.infoWindow = null;
        this.iwContent  = null;
        this.isClick    = false;
        this.image      = IMAGE;
        this.imageType  = IMAGE_TYPE;
        this.timer      = null;
        this.phaseArray = [];
        this.prop       = {...DATA};
        this.size       = [48, 48, 48, 48, 40, 32, 24, 22, 20, 18, 18];
        this.atrdSize   = [48, 48, 48, 48, 48, 48, 48, 24, 24, 24, 24];
        this.imageOnOff = ['cctv', 'incident', 'vms', 'parking'];
    }

    //스마트교차로 카메라는 카메라 방향별로 이미지를 돌려야하므로 두개 만드는 방식이 다름
    init () {
        const position = getKakaoPosition(this.Y_CRDN, this.X_CRDN);
        // if (this.type !== 'intersectionCamera') {
            const imageLocation = this.image + this.imageType + '.png';
            const { imageSize, imageOption } = this.getImageOptions();
            let markerImage = new kakao.maps.MarkerImage(imageLocation, imageSize, imageOption);
            this.marker = new kakao.maps.Marker({
                position: position,
                image: markerImage,
                zIndex: 5,
                title: this.NAME,
            });

            const _self = this;

            //마커 클릭이벤트 등록
            new kakao.maps.event.addListener(this.marker, 'click', function() {
                _self.click();
            });
        // }
        // else {
        //     const content = $('<div id="camera_'+this.ID+'" title="'+this.NAME+'">');
        //     const angle = Number(this.prop.cmra_angn);
        //     content.css(
        //         {
        //             width: '48px',
        //             height: '48px',
        //             backgroundImage:'url(/images/icon/intersection-cctv.png)',
        //             backgroundSize : '48px 48px',
        //             backgroundRepeat: 'no-repeat',
        //             backgroundPosition : 'center',
        //             transform : 'rotate(' + angle +'deg)'
        //         });
        //
        //     this.marker = new kakao.maps.CustomOverlay({
        //         content: content[0],
        //         position: position,
        //         zindex: 15,
        //     });
        //
        //     const linePath1 = [this.prop.start_x, this.prop.start_y]
        //     const linePath2 = [this.prop.end_x, this.prop.end_y];
        //     const color = ['#888888', '#15B337', '#15B337', '#15B337', '#FFAA00', '#FFAA00', '#EB260C', '#EB260C', '#EB260C'];
        //     this.polyLine = new kakao.maps.Polyline({
        //         path: [
        //             getKakaoPosition(linePath1[1], linePath1[0]),
        //             getKakaoPosition(linePath2[1], linePath2[0]),
        //         ],
        //         strokeWeight: 10,
        //         strokeColor: color[this.prop.acrd_los],
        //         strokeOpacity: 1,
        //         strokeStyle: 'solid',
        //         endArrow: true,
        //         name : this.NAME,
        //     });
        //
        //     if (_Level <= 2 && _MarkerHandle.isDraw('intersectionCamera')){
        //         marker.setMap(_MapHandler.map);
        //         this.polyLine.setMap(_MapHandler.map);
        //     }
        //
        //     content.on('click', ()=> {
        //         this.click();
        //     });
        // }

    }

    //레벨별 이미지 사이즈 변경
    setImage (type) {
        const imageLocation = this.image + type + '.png';
        const { imageSize, imageOption } = this.getImageOptions();

        let markerImage = new kakao.maps.MarkerImage(imageLocation, imageSize, imageOption);
        this.marker.setImage(markerImage);
    }

    //현재 레벨별 이미지 Option 값
    getImageOptions() {
        let size = this.size[_Level];
        if (this.isClick) {
            size = size * 1.5;
        }
        let offsetX = size/2;
        let offsetY = size/2;

        if (this.type === 'atrd') {
            size = this.atrdSize[_Level];
            offsetX = size/2;
            offsetY = size;
        }

        const imageSize     = new kakao.maps.Size(size, size);
        const imageOffset   = new kakao.maps.Point(offsetX, offsetY);
        const imageOption = {
            offset: imageOffset,
            alt   : this.NAME,
        };
        return {imageSize : imageSize, imageOption : imageOption};
    }

    //클릭 이벤트
    async click() {
        const selectObj = _MarkerHandle.selectedMarker;
        if (selectObj) {
            if (selectObj === this) {
                // if (selectObj.type !== 'intersection' || (selectObj.type === 'intersection' && _Level <= 2)) {
                    return;
                // }
            }

            // 선택 마커 종료
            selectObj.close();
        }

        // if (this.type === 'intersectionCamera' && _MarkerHandle.isDrawList('intersection')) {
        //     const ixrId = this.prop.ixr_id;
        //     const ixrLi = $('#intersection-' + ixrId);
        //     ixrLi.addClass('click');
        //     $('.mobile-select').val(ixrId);
        //     moveToScroll(true, _MarkerHandle.findMarkerArr('intersection') , ixrId); // 클릭 시 리스트 이동
        // }

        _MarkerHandle.selectedMarker = this;

        const center = getKakaoPosition(this.Y_CRDN, this.X_CRDN);
        let level = _Level;
        // if (this.type === 'intersection') {
        //     level = 2;
        // }

        _MapHandler.setLevel(level);
        _MapHandler.setCenter(center);

        if (this.iwContent) {
            this.infoWindow = $(this.iwContent);
            $('body').append(this.infoWindow);
            if (this.type === 'parking' && this.prop.prk_plce_nmbr) {
                const parkData = await $.ajax({url: '/api/traffic/parking-live-info', method: 'post', data:{nmbr: this.prop.prk_plce_nmbr}});
                if (parkData.remndr_prk_cmprt_co && parkData.remndr_prk_cmprt_co >= 0) {
                    const remndrPrkCmprtCo = parkData.remndr_prk_cmprt_co + '대';
                    $('.remndr_prk_cmprt_co').attr('title', '주차가능면수 : '+ remndrPrkCmprtCo);
                    $('.remndr_prk_cmprt_co').html(remndrPrkCmprtCo);
                }
            }
            let type = this.type;
            // if (type === 'intersectionCamera') type = 'cctv';
            setInfoWindowPositionWidthDraggable(this, type);
        }

        if (this.URL) {
            this.videoEvent();
        }

        if (this.phaseArray) {
            this.vmsEvent();
        }

        const selectedLi = $('#' + this.type + '-' + this.ID);

        selectedLi.addClass('click');

        if (_MarkerHandle.isDraw('traffic')) {
            getVertex();
        }

        this.isClick = true;

        if (this.imageOnOff.includes(this.type)) {
            this.setImage('2');
        }



        if (_MarkerHandle.isDrawList(this.type)) {
            const markerArr = _MarkerHandle.findMarkerArr(this.type);
            moveToScroll(true, markerArr, this.ID); // 클릭 시 리스트 이동
        }

    }

    //닫기 이벤트
    close() {
        if (this.timer) { // 인터벌이 있는 경우
            clearInterval(this.timer);
            clearTimeout(this.timer);
            this.timer = null;
        }

        if (this.limitTimer) {
            clearInterval(this.limitTimer);
            this.limitTimer = null;
        }

        this.isClick = false;
        if (this.imageOnOff.includes(this.type)) {// 클릭 이미지가 있는 경우
            this.setImage('1');
        }

        let oldPlayer = document.getElementById("video-" + this.ID);
        if (oldPlayer) { // video 태그가 있는 경우

            videojs(oldPlayer).dispose();
        }

        if (this.infoWindow) { // 인포 윈도우 삭제
            this.infoWindow.remove();
            this.infoWindow = null;
        }
        const mobilSelect = $('.mobile-select');

        if (mobilSelect[0] && _MarkerHandle.isDrawList(this.type)) { // 리스트가 있는 경우
            mobilSelect.val("-");
            let selectedLi   = $("#" + this.type + "-" + this.ID);

            if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부
                selectedLi.removeClass('click');
            }
        }

        // if (this.type === 'intersectionCamera' && _MarkerHandle.isDrawList('intersection')) {
        //     mobilSelect.val("-");
        //     let selectedId = _MarkerHandle.selectedMarker.prop.ixr_id;
        //     let selectedLi   = $("#intersection-" + selectedId);
        //
        //     if (selectedLi.hasClass('click')) { // 선택 리스트가 있는지 여부
        //         selectedLi.removeClass('click');
        //     }
        // }


        //선택 해제
        _MarkerHandle.selectedMarker = null;
    }

    //영상 30초 제한 시간 보여주기
    showLimitTime() {
        const timer = $('.timer');
        if (!this.video) {
            timer.text('-');
            return;
        }

        timer.text(30);

        if (this.limitTimer) {
            clearInterval(this.limitTimer)
            this.limitTimer = null;
        }
        let cnt = 30;
        this.limitTimer = setInterval(()=>{
            if(cnt === 0) {
                clearInterval(this.limitTimer);
                this.limitTimer = null;
                return;
            }
            $('.timer').text(--cnt);
        }, 1000)
    }

    //CCTV 타이머
    playTimer() {
        if (this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }

        this.timer = setTimeout(()=>{
            if (this.video) {
                this.video.pause();
            }
        }, CCTV_DISPLAY_TIME);
    }

    //비디오 생성 및 플레이
    videoEvent() {
        this.video = createVideoJs(this.ID, this.URL, this);

        this.playTimer();

        this.showLimitTime();

        $('.continue-play').on('click', ()=>{
            this.playTimer();
            this.showLimitTime();

            if (this.video) {
                this.video.play();
            }
            else {
                $('#video-error').css('display', 'none');
                $('.cctv-info-window .content > div:nth-child(1)').append($('<video id="video-'+this.ID+'" class="video-js" style="width: 100%; height: 100%;"></video>'))
                this.videoEvent();
            }
        });
    }

    //vms 이미지 표출 이벤트
    vmsEvent() {
        let cnt = 1;
        if (this.phaseArray.length > 0) {
            this.timer = setInterval(()=>{
                if (cnt === this.phaseArray.length) {
                    cnt = 0;
                }
                const activeImage = $('.vms-info-window .content img.active');
                if (activeImage[0]) {
                    activeImage.removeClass('active');
                }
                $("#phase-" + this.phaseArray[cnt]).addClass('active');
                cnt++;
            }, VMS_DISPLAY_TIME);
        }
    }
}

/**
 * 소통정보 Object
 */
class TrafficObj {
    constructor(obj) {
        this.ID = obj.roadway_id;
        this.NAME = obj.roadway_nm;
        this.obj = obj;
        this.infoWindow = null;
        this.polyLine = null;
        this.polyBackLine = null;
        this.ADDR = obj.addr;
        this.init();
    }

    init() {
        const _self = this;
        const trafficObj = this.obj;
        const xArray = trafficObj.x_crdn.split(",");
        const yArray = trafficObj.y_crdn.split(",");
        const linePath = [];
        for (let ii = 0; ii < xArray.length; ii++) {
            const x_crdn = Number(xArray[ii]);
            const y_crdn = Number(yArray[ii]);
            const coordinates = getKakaoPosition(y_crdn, x_crdn);
            linePath.push(coordinates);
        }

        let strokeWeight = 5;
        let strokeWeightBack = 7;
        const level = _Level;

        if (level === 3) {
            strokeWeightBack = 6;
        } else if (level === 5 || level === 4) {
            strokeWeight = 3;
            strokeWeightBack = 5;
        } else if (level >= 6) {
            strokeWeight = 2;
            strokeWeightBack = 4;
        }

        if (_MarkerHandle.trafficObj && this.ID === _MarkerHandle.trafficObj) {
            strokeWeightBack += 2
        }
        this.polyBackLine = new kakao.maps.Polyline({
            path: linePath, // 선을 구성하는 좌표배열 입니다
            strokeWeight: strokeWeightBack, // 선의 두께 입니다
            strokeColor: '#eeeeee', // 선의 색깔입니다
            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
            strokeStyle: 'solid', // 선의 스타일입니다
            zIndex: 1
        });
        this.polyLine = new kakao.maps.Polyline({
            path: linePath, // 선을 구성하는 좌표배열 입니다
            strokeWeight: strokeWeight, // 선의 두께 입니다
            strokeColor: g_color.get(trafficObj.cmtr_grad_cd), // 선의 색깔입니다
            strokeOpacity: 1, // 선의 불투명도 입니다 1에서 0 사이의 값이며 0에 가까울수록 투명합니다
            strokeStyle: 'solid', // 선의 스타일입니다
            zIndex: 2
        });


        this.setVisibleMarker(_MarkerHandle.isDraw('traffic'));

        // new kakao.maps.event.addListener(this.polyLine, 'mouseover', function (event) {
        //
        //     this.setOptions({strokeColor: '#0000FF'});
        //     const iwContent =
        //         `<div class="trafficPop">
        //                     <div class="traffic-speed ${trafficObj.cmtr_grad_cd}">
        //                         <span class="traffic-name">${_self.NAME}</span>
        //                         <span class="traffic-speed-info border-back ${trafficObj.cmtr_grad_cd}">${trafficObj.grad_nm}</span>
        //                     </div>
        //                     <div class="traffic-info">
        //                         <span>${trafficObj.strt_nm_node} → ${trafficObj.end_nm_node}</span>
        //                         <br>
        //                         <span>소요시간 : </span>
        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.trvl_hh)}분 </span>
        //                         <span>&nbsp; 속도 : </span>
        //                         <span class="${trafficObj.cmtr_grad_cd}">약 ${textFormat(trafficObj.sped)}km/h</span>
        //                     </div>
        //                  </div>`;
        //     _self.infoWindow = new kakao.maps.CustomOverlay({
        //         map: _MapHandler.map,
        //         clickable: true,
        //         position: event.latLng,
        //         content: iwContent,
        //         xAnchor: -0.1,
        //         yAnchor: 1.1,
        //         zIndex: 999
        //     });
        //
        // });
        //
        // daum.maps.event.addListener(this.polyLine, 'mouseout', function () {
        //     this.setOptions({strokeColor:  g_color.get(trafficObj.cmtr_grad_cd)});
        //     if (_self.infoWindow != null) _self.infoWindow.setMap(null);
        // });
    }

    setVisibleMarker(isVisible) {
        const isShow = isVisible ? _MapHandler.map : null;
        this.polyBackLine.setMap(isShow);
        this.polyLine.setMap(isShow);
    }
}

/**
 * 간선도로 Object
 */
class TbAtrdObj extends MarkerObj{
    constructor(obj) {
        super({
            ID : obj.ID,
            NAME : obj.atrd_nm + ' ['+ obj.drct_nm + ']',
            X_CRDN : obj.x_crdn_arr.split(",")[0],
            Y_CRDN : obj.y_crdn_arr.split(",")[0],
            URL    : null,
            TYPE   : 'atrd',
            DATA   : obj,
            IMAGE_TYPE : obj.drct_cd,
            IMAGE : '/images/icon/atrd'
        })
    }
}

/**
 * CCTV Object
 */
class TbCCtvObj extends MarkerObj{
    constructor(obj) {
        super({
            ID         : obj.cctv_mngm_nmbr,
            NAME       : obj.istl_lctn_nm,
            X_CRDN     : obj.x_crdn,
            Y_CRDN     : obj.y_crdn,
            URL        : obj.strm_http_addr,
            TYPE       : 'cctv',
            DATA       : obj,
            IMAGE_TYPE : '1',
            IMAGE      : '/images/icon/cctv',
            ADDR       : obj.istl_lctn_addr,
        });
        this.iwContent = this.getIwContent();
    }

    getIwContent() {
        const {ID, NAME, type} = this;
        let iwContent =
            `<div class="cctv-info-window">
                        <div class="title">
                            <div class="cctv-name-${ID}">${NAME}</div>
                            <div onclick="infoWindowEvent('${type}', '${ID}', 'close')">X</div>
                        </div>
                        <div class="content">
                            <div style="display: flex; align-items: center; justify-content: center;">
                                <video id="video-${ID}" class="video-js" style="width: 100%; height: 100%;"></video>
                                <img id="video-error" style="display: none;" src="/images/icon/error.png" alt="스트리밍 오류 이미지">
                            </div>
                            <div>
                                <div>남은시간 : <span class="timer">30</span> 초</div>
                                <div class="continue-play">계속재생</div>
                            </div>
                        </div>
                    </div>`;
//<!--                                <div>※ CCTV영상은 30초간 제공됩니다.</div>-->
        return iwContent;
    }
}
/**
 * Parking Object
 */
class TbParkingObj extends MarkerObj{
    constructor(obj) {
        super({
            ID          : obj.parking_id,
            NAME        : obj.parking_nm,
            X_CRDN      : obj.x_crdn,
            Y_CRDN      : obj.y_crdn,
            URL         : null,
            TYPE        : 'parking',
            DATA        : obj,
            IMAGE_TYPE  : '1',
            IMAGE       : '/images/icon/parking' + (obj.prk_plce_nmbr ? '1-' : '2-'),
            ADDR        : obj.parking_addr,
        })
        this.iwContent = this.getIwContent();
    }

    getIwContent() {
        const {ID, NAME, prop, X_CRDN, Y_CRDN} = this;
        let iwContent =
            `<div class="parking-info-window">
                        <div class="title">
                            <div class="parking-name-${ID}">${NAME}</div>
                            <div onclick="infoWindowEvent('parking', '${ID}', 'close')"></div>
                        </div>
                        <div class="content">
                            <div class="row">
                                <div>주차면수</div>
                                <div title="주차면수 : ${prop.parking_num} 대">${prop.parking_num} 대</div>
                            </div>`;
                if (prop.prk_plce_nmbr) {
                    iwContent +=`<div class="row">
                                    <div>주차가능면수</div>
                                    <div class="remndr_prk_cmprt_co">- 대</div>
                                </div>`;
                }

                iwContent +=`<div class="row">
                                <div>구분</div>
                                <div title="구분 : ${prop.parking_type_desc}">${prop.parking_type_desc}</div>
                            </div>
                            <div class="row">
                                <div>기본요금</div>
                                <div title="기본요금 : ${prop.parking_fee_type_desc}">${prop.parking_fee_type_desc}</div>
                            </div>
                             <div class="row">
                                <div>운영시간</div>
                                <div title="운영시간 : ${prop.parking_oper_time}">${prop.parking_oper_time}</div>
                            </div>
                            <div class="row">
                                <div>주소</div>
                                <div style="cursor:pointer;" title="주소 : ${prop.parking_addr}" onclick="openNavigationApp('${X_CRDN}','${Y_CRDN}','${prop.parking_addr}')">${prop.parking_addr}</div>
                            </div>
                        </div>
                    </div>`;
        return iwContent;
    }
}

/**
 * VMS Object
 */
class TbVmsObj extends MarkerObj{
    constructor(obj) {
        super({
              ID            : obj.vms_ctlr_nmbr,
              NAME          : obj.vms_nm,
              X_CRDN        : obj.x_crdn,
              Y_CRDN        : obj.y_crdn,
              URL           : null,
              TYPE          : 'vms',
              IMAGE         : '/images/icon/vms',
              IMAGE_TYPE    : '1',
              DATA          : obj,
              ADDR          : obj.istl_lctn_addr,
        });
        this.iwContent = this.getIwContent();
    }

    getIwContent() {
        const {ID, NAME, prop} = this;
        this.phaseArray = [];

        let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
        const WH = vmsWHMap.get(prop.vms_type_cd);
        let width = 256;
        let height = 50;

        if (WH) {
            width = WH.width;
            height = WH.height
        }

        const msg = prop.msg;
        let content = "";
        if (msg && msg.length > 0) {
            const windowHeight = $(window).height();
            if (isMobile || windowHeight < 900) {
                width = WH.width/2;
                height = WH.height/2;
            }

            for (let idx in msg) {
                let msgObj = msg[idx];
                let className = '';
                if (idx === "0") {
                    className = 'active'
                }
                content += `<img id="phase-${msgObj.phase}" class="${className}" style="width: ${width}px; height: ${height}px;" 
                                         src="/api/traffic/vms-dspl-image/${ID}/${msgObj.phase}" alt="VMS 표출 이미지">`;
                this.phaseArray.push(msgObj.phase);
            }
        }
        else {
            width = 256;
            height = 50;
            content += '표출 이미지 데이터가 없습니다.';
        }
        width = width +'px';
        height = height + 'px';

        let iwContent =
            `<div class="vms-info-window" style="width: calc(${width} + 10px); height: calc(${height} + 50px);">
                        <div class="title">
                            <div class="vms-name-${ID}">${NAME}</div>
                            <div onclick="infoWindowEvent('vms', ${ID}, 'close')">X</div>
                        </div>
                        <div class="content" style="width: ${width}; height: ${height};">
                            ${content}
                        </div>
                    </div>`;
        return iwContent;
    }
}

/**
 * 돌발정보 Object
 */
class TbIncdObj extends MarkerObj{
    constructor(obj) {
        super({
            ID          : obj.incd_ocrr_id,
            NAME        : obj.incd_titl,
            X_CRDN      : obj.x_crdn,
            Y_CRDN      : obj.y_crdn,
            URL         : null,
            TYPE        : 'incident',
            IMAGE       : '/images/icon/incd',
            IMAGE_TYPE  : '1',
            DATA        : obj,
            ADDR        : obj.road_nm,
        });

        this.iwContent = this.getIwContent();
    }

    getIwContent() {
        const {ID, NAME, prop} = this;
        let iwContent =
            `<div class="incident-info-window">
                <div class="title">
                    <div class="incident-name-${ID}">${NAME}</div>
                    <div onclick="infoWindowEvent('incident', '${ID}', 'close')">X</div>
                </div>
                <div class="content">
                    <div>
                        위치 : ${prop.road_nm}
                    </div>
                    <div>
                        설명 : ${prop.incd_expl}
                    </div>
                    <div>
                        기간 : ${prop.incd_strt_dt} ~ ${prop.incd_end_prar_dt}
                    </div>
                </div>
            </div>`;
        return iwContent;
    }
}

/**
 * 스마트 교차로 Object
 */
class IntersectionObj extends MarkerObj{
    constructor(obj) {
        super({
            ID : obj.ixr_id,
            NAME : obj.ixr_nm,
            X_CRDN : obj.x_crdn,
            Y_CRDN : obj.y_crdn,
            URL    : null,
            TYPE   : 'intersection',
            DATA   : obj,
            IMAGE_TYPE : "",
            IMAGE : '/images/icon/intersection'
        })
    }
}

/**
 * 스마트교차로 카메라 Object
 */
class IntersectionCameraObj extends MarkerObj{
    constructor(obj) {
        super(
            {
                ID          : obj.cmra_id + '_' + obj.drct_dvsn_cd,
                NAME        : obj.drct_lctn,
                X_CRDN      : obj.cmra_x_crdn,
                Y_CRDN      : obj.cmra_y_crdn,
                URL         : obj.hmpg_cmra_url,
                TYPE        : 'intersectionCamera',
                DATA        : obj,
                IMAGE_TYPE  : "",
                ADDR        : obj.istl_lctn,
                IMAGE       : '/images/icon/intersection'
            }
        )
        this.iwContent = this.getIwContent();
    }

    getIwContent() {
        const {ID, NAME, type} = this;
        let iwContent =
            `<div class="cctv-info-window">
                        <div class="title">
                            <div class="cctv-name-${ID}">${NAME}</div>
                            <div onclick="infoWindowEvent('${type}', '${ID}', 'close')">X</div>
                        </div>
                        <div class="content">
                            <div>
                                <video id="video-${ID}" class="video-js" playsinline style="width: 100%; height: 100%;"></video>
                            </div>
                            <div>
                                <div>남은시간 : <span class="timer">30</span> 초</div>
                                <div class="continue-play">계속재생</div>
                            </div>
                        </div>
                    </div>`;

        return iwContent;
    }
}

/**
 * 인포윈도우 이벤트
 * @param type 시설물 유형
 * @param id 요소 ID
 * @param event 이벤트 종류 (click, close)
 */
function infoWindowEvent(type, id, event) {
    const markerInfo = _MarkerHandle.findMarkerInfo(type); // 유형별 시설물 정보 찾기

    // 시설물 마커들 중 id에 해당하는 객체 찾기
    const markers = markerInfo.markers;
    let idx = markers.findIndex(obj => obj.ID.toString() === id.toString());

    // click, close 등 메서드로 등록한 것들도 키값으로 찾을 수 있음 obj.click() == obj['click']();
    markers[idx][event]();
}


let isHide = false

/**
 * 좌측 목록 토글 이벤트
 */
function toggleEvent() {
    const $listArea = $('.left-list-area');
    const $toggleButton = $('.toggle-button');
    const $locationButton = $('.location-btn');
    const $locationBox = $('.location-box');

    if (!isHide) {
        $toggleButton.animate({
            left: 0
        }, 'slow');
        $listArea.animate({
            left: -$listArea.width()
        }, 'slow');
        $locationButton.animate({
            left: 27
        }, 'slow');
        $locationBox.animate({
            left: 27
        }, 'slow');
        $toggleButton.text('>');
    }
    else {
        $toggleButton.animate({
            left: $listArea.width()
        }, 'slow');
        $listArea .animate({
            left: 0
        }, 'slow');
        $locationButton.animate({
            left: $listArea.width() + 27
        }, 'slow')
        $locationBox.animate({
            left: $listArea.width() + 27
        }, 'slow')
        $toggleButton.text('<');
    }
    isHide = !isHide;
}


/**
 * 웹페이지 사이즈 별 위치 변경 이벤트
 */
window.addEventListener('resize', function(event) {
    const $toggleButton   = $('.toggle-button');
    const $locationButton = $('.location-btn');
    const $listArea       = $('.left-list-area');
    const $locationBox    = $('.location-box');

    if ($(this).width() > 450) {
        const left = $toggleButton.offset().left;
        const listLeft = $listArea.offset().left;

        if ($(this).width() <= 795) {
            $locationButton.css('display', 'none');
            $locationBox.css('display', 'none');
            if(_MarkerArr.length > 0) {
                _MarkerArr.forEach((obj)=>{
                    obj.setMap(null);
                });
                _MarkerArr = [];
            }
            if ($locationButton.hasClass('on')) {
                $locationButton.removeClass('on');
            }
            $('.list-content.spot').empty();
            $('#location-text').val('');
        }
        else {
            $locationButton.css('display', 'flex');
        }
        if ($(this).width() >= 920) {
            if (left > 0 && left < 400) {
                $toggleButton.css('left', 400);
                $locationButton.css('left', 427);
                $locationBox.css('left', 427);
            }

            if (listLeft > -400 && listLeft < 0) {
                $listArea.css('left', -400);
            }
        }
        else {
            if (left > 0 && left > 275) {
                $toggleButton.css('left', 275);
                $locationButton.css('left', 302);
                $locationBox.css('left', 302);
            }

            if (listLeft < -273) {
                $listArea.css('left', -273);
            }
        }
    }
    else {
        $locationBox.css('display', 'none');
        $locationButton.removeClass('on');
    }
});


/**
 * 선택 리스트 스크롤 높이 반환 이벤트
 * @param selectIndex 선택 인덱스
 * @returns {number} 스크롤 높이
 */
function getScrollTop(selectIndex) {
    let scrollTop = 0;
    for (let ii=0; ii < selectIndex; ii++) {
        let height = $('.left-list-area .list-content.list').children().eq(ii).css('height');
        if (height) {
            height = Number(height.replace('px', ''));
            if (!isNaN(height)) {
                scrollTop += height;
            }
        }
    }
    return scrollTop;
}

/**
 * 좌측 리스트 선택 시 스크롤 변경 이벤트
 * @param flag
 * @param array
 * @param id
 */
function moveToScroll(flag, array, id) {
    if (flag) {
        let selectIndex = array.findIndex((obj)=> obj.ID == id);
        let scrollTop = getScrollTop(selectIndex);
        $('.left-list-area .list-content.list').animate({
            scrollTop : scrollTop + 'px'
        });
        $('.mobile-select').val(id);
    }
}

/**
 *
 * @param jsonData Marker Info
 * @param classType Marker Class
 * @param type Marker type
 */
function receiveMarkerData(jsonData, classType, type) {
    let listStr = "";
    const title = $('.left-list-area .list-tab li.active > div:nth-child(2)').text();
    let emptyStr = "<div class='empty'>" + title + " 리스트가 없습니다.</div>";
    let mobileStr = "";
    const { markers, isDraw, isDrawList } = _MarkerHandle.findMarkerInfo(type);
    if (jsonData && jsonData.length) {
        let markerArr = [];
        jsonData.forEach((obj)=>{
            const markerObj = new classType(obj);
            markerObj.init();
            listStr += `<li id="${markerObj.type}-${markerObj.ID}" onclick="infoWindowEvent('${type}', '${markerObj.ID}' , 'click')">${markerObj.NAME}</li>`
            mobileStr += `<option value="${markerObj.ID}">${markerObj.NAME}</option>`;
            markers.push(markerObj);
            // if (type ==='intersection') {
            //     markerObj.prop.detail.forEach((detail)=>{
            //         const detailObj = new IntersectionCameraObj(detail);
            //         detailObj.init();
            //         _MarkerHandle.findMarkerInfo('intersectionCamera').markers.push(detailObj);
            //     });
            //     _MarkerHandle.setIsDraw('intersectionCamera', isDraw);
            // }
            markerArr.push(markerObj);
        });

        _MarkerHandle.setMarkers(type, markerArr);
        _MarkerHandle.showHideMarker(type, isDraw);
    }
    else {
        listStr = emptyStr;
    }

    if (isDrawList) {
        const listSection  = $('.left-list-area .list-content.list');
        listSection.empty();
        listSection.html(listStr);
        const $mobileSelect = $('.mobile-select');
        $mobileSelect.append($(mobileStr));
        $mobileSelect.on('change', function(){
            const id = $(this).val();
            if (id && id !== "-") {
                infoWindowEvent(type, id , 'click');
            }
        });
    }

    if (selectIncidentId) {
        infoWindowEvent('incident', selectIncidentId , 'click');
    }
}


/**
 * 맵 중앙 아이콘 위 위치 좌표
 * @param infoWindow
 * @returns {number[]}
 */
function getInfoWidowPosition(infoWindow) {
    const map    = $('#map');
    let mapHalfW = map.innerWidth() / 2;
    let mapHalfH = map.innerHeight() / 2;
    let mapTop   = map.offset().top;
    let halfW    = infoWindow.innerWidth() / 2;
    let height   = infoWindow.innerHeight();
    let left     = mapHalfW - halfW;
    let iconH    = _size[_Level];
    let top = mapTop + mapHalfH - height - iconH - 10;
    return [top, left];
}

/**
 * Marker Drag Event
 * @param markerObj Drag Marker Object
 * @param type Marker Type
 */
function setInfoWindowPositionWidthDraggable(markerObj, type) {
    const { infoWindow, ID } = markerObj;
    const position = getInfoWidowPosition(infoWindow); //Current Marker Position
    // console.log(infoWindow.width());
    let top = position[0];
    let left = position[1] + (infoWindow.width() / 2) + (markerObj.atrdSize[_Level] / 2);

    if (markerObj.type === 'parking') {
        top = $('header').outerHeight() + $('.toggle-button').outerHeight() + 2;
        left = $('.left-list-area').outerWidth();
    }
    infoWindow.css({
        top : top + 'px',
        left : left + 'px',
        position : 'absolute',
        zIndex : 999,
    });

    //containment : 이동 반경 제한, handle : Drag Element
    infoWindow.draggable({containment : 'body', handle: '.'+ type + '-name-' + ID});
}


/**
 * Kakao Map 좌표 지정
 */
function getKakaoPosition(yCoordinate, xCoordinate) {
    return new kakao.maps.LatLng(Number(yCoordinate), Number(xCoordinate));
}

/**
 * videoJs 객체 생성
 */
function createVideoJs(id, url, obj) {
    let video = videojs("video-" + id, {
        sources: [
            {
                src: url,
                type: "application/x-mpegURL",
                crossorigin: "anonymous",
            },
        ],
        responsive: false,
        autoplay: true,
        muted: true,
        preload: "metadata",
    });

    //에러발생 시 대체 이미지 생성
    video.on('error', function(){
        if (this.error().code === 4) {
            this.pause();
            this.dispose();
            $('#video-error').css('display', 'block');
            clearInterval(obj.limitTimer);
            clearTimeout(obj.timer);
            $('.timer').text('-');
            obj.video = null;
        }
    });

    return video;
}

/**
 * Map Handler Class
 */
class MapHandler {
    constructor(id) {
        this.selectedObj = null;
        this.map = null;
        this.mapElement = id;
        this.atrd = [];
    }

    //Kakao Map 생성 및 이벤트 설정
    init () {
        const container = document.getElementById(this.mapElement); //지도를 담을 영역의 DOM 레퍼런스
        const options = { //지도를 생성할 때 필요한 기본 옵션
            center: getKakaoPosition(36.0191816, 129.3432983), //지도의 중심좌표.
            level: _Level, //현재 레벨
            maxLevel: 9, //최대 레벨
            minLevel: 1, //최소 레벨
            disableDoubleClickZoom: true, //더블 클릭 줌 방지
        };
        this.map = new kakao.maps.Map(container, options);
        const mapTypeControl = new kakao.maps.MapTypeControl(); //지도, 스카이뷰 버튼 생성
        this.map.addControl(mapTypeControl, kakao.maps.ControlPosition.TOPRIGHT); //지도, 스카이뷰 버튼 위치 설정
        const zoomControl = new kakao.maps.ZoomControl(); //줌 버튼 생성
        this.map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT); // 줌 버튼 위치 설정

        /**
         * Map Zoom Level Change 이벤트 (이미지 사이즈, 스마트 교차로 이미지 토글, 소통정보 정보 변경)
         */
        kakao.maps.event.addListener(this.map, 'zoom_changed', function () {
            _Level = this.getLevel();
            const zoomChangeArray = ['cctv', 'vms', 'parking', 'incident'];
            zoomChangeArray.forEach((type)=>{
                const isDraw = _MarkerHandle.isDraw(type);

                if (isDraw) {
                    const markers = _MarkerHandle.findMarkerArr(type);
                    if (markers && markers.length > 0) {
                        markers.forEach((obj)=>{
                            let imageType = "";
                            if (obj.imageType === '1') {
                                imageType = obj.isClick ? '2' :'1';
                            }

                            // if (obj.type === "intersectionCamera") {
                            //     let isIxrCm = _Level > 2 ? null : _MapHandler.map;
                            //     obj.marker.setMap(isIxrCm);
                            //     obj.polyLine.setMap(isIxrCm);
                            // }
                            // else {
                                obj.setImage(imageType);
                            // }

                            // if (obj.type === 'intersection') {
                            //     let isIxr = _Level <= 2 ? null : _MapHandler.map;
                            //     obj.marker.setMap(isIxr);
                            // }

                        })
                    }
                }
            });

            if (_MarkerHandle.isDraw('traffic')) {
                const selectedAtrd = _MarkerHandle.selectedAtrd;
                if (selectedAtrd.length > 0) {
                    let level = getAtrdLevel(_Level);
                    let array = [];
                    selectedAtrd.forEach((atrdId)=>{
                        const atrdObj = _MarkerHandle.findMarkerObj('atrd', atrdId);
                        if (atrdObj) {
                            atrdObj.marker.setMap(null);
                            let changeId = atrdId.slice(0, -1);
                            array.push(changeId + level);
                        }
                    });
                    _MarkerHandle.selectedAtrd = array;
                }
                getVertex();
            }
        });

        /**
         * 맵 드래그 이벤트
         * 좌표 데이터가의 양이 많으므로 이동 할때마다 영역별 소통정보를 새로 그려줌
         */
        kakao.maps.event.addListener(this.map, 'dragend', function() {
            if (_MarkerHandle.isDraw('traffic')) {
                getVertex();
            }
        });

        // kakao.maps.event.addListener(this.map, 'bounds_changed', function() {
        //     if (_MarkerHandle.isDraw('traffic')) {
        //         getVertex();
        //     }
        // });

        /**
         * 맵 클릭 이벤트
         */
        kakao.maps.event.addListener(this.map, 'click', function (mouseEvent) {
            /** 클릭 이벤트 필요 시 설정 **/
        });
    }

    getLevel() {
        return this.map.getLevel();
    }

    setLevel(level) {
        if (!isNaN(level)) {
            this.map.setLevel(level);
        }
        else {
            console.error("Invalid Value Error - MapHandler.setValue(), Please Check The Zoom Level Value : ", level);
        }
    }

    setCenter(position) {
        if (position) {
            this.map.setCenter(position);
        }
        else {
            console.error("Invalid Values Error - MapHandler.setCenter, Value is Empty : ", position);
        }
    }

    getBounds() {
        return this.map.getBounds();
    }

    setBounds(bounds) {
        if (bounds) {
            this.map.setBounds(bounds);
        }
        else {
            console.error("Invalid Values Error - MapHandler.setBound, Value is Empty : ", bounds);
        }
    }

}

/**
 * 마커 관리 Class
 */
class MarkerManager{
    constructor(typeArr) {
        this.markerMaps = new Map();
        this.typeArr    = typeArr;
        this.selectedMarker = null;
        this.selectedAtrd   = [];
        this.trafficObj = null;
    }

    //마커 초기화
    init() {
        if (this.typeArr && this.typeArr.length > 0) {
            for (let type of this.typeArr) {
                this.markerMaps.set(type, {
                    isDraw     : false, //마커 지도 이미지
                    isDrawList : false, //좌측 교통정보 리스트
                    markers    : [],    //마커 빈값 초기화
                    type       : type,  //교통정보 종류
                });
            }
        }
    }

    //마커 세팅
    setMarkers(type, array) {
        if (array && array.length) {
            this.markerMaps.get(type).markers = [...array];
        }
    }

    //유형별 마커 관리 객체 찾기
    findMarkerInfo(type) {
        return this.markerMaps.get(type);
    }

    //유형별 화면 표출 여부
    isDraw(type) {
        return this.findMarkerInfo(type).isDraw;
    }

    //유형별 리스트 표출 여부
    isDrawList(type) {
        return this.findMarkerInfo(type).isDrawList;
    }

    //유형별 화면 표출 여부 세팅
    setIsDraw(type, isDraw) {
        this.findMarkerInfo(type).isDraw = isDraw;
    }

    //유형별 리스트 표출 여부 세팅
    setIsDrawList(type, isDraw) {
        this.findMarkerInfo(type).isDrawList = isDraw;
    }

    //유형별 마커 Array 찾기
    findMarkerArr(type) {
        return this.findMarkerInfo(type).markers;
    }

    //유형별 개별 마커 찾기
    findMarkerObj(type, id) {
        const markerArr = this.findMarkerArr(type);
        let markerObj = null;
        if (markerArr && markerArr.length > 0) {
            let idx = markerArr.findIndex(obj => obj.ID === id);
            if (!isNaN(idx) && markerArr[idx]) {
                markerObj = markerArr[idx];
            }
        }
        return markerObj;
    }

    //유형별 마커 보이기 / 감추기
    showHideMarker(type, isShow) {
        const markerArr = this.findMarkerArr(type);

        if (markerArr && markerArr.length > 0) {
            const map = _MapHandler.map;
            for (let markerObj of markerArr) {
                let visible = null

                //스마트 교차로의 경우 2레벨 이하일때 카메라 위치, 그 외 교차로 위치를 보여준다.
                switch (type) {
                    // case 'intersectionCamera':
                    //     if (isShow && _Level <= 2) visible = map;
                    //     break;
                    // case 'intersection':
                    //     if (isShow && _Level > 2) visible = map;
                    //     break;
                    case 'atrd': //소통 정보 시 선택된 도로가 있는지 확인 후 진행
                        if (this.selectedAtrd.length > 0) {
                            if (this.selectedAtrd.includes(markerObj.ID.toString()) && isShow) {
                                visible = map;
                            }
                        }
                        break;
                    default : //그 외 보이기 감추기
                        visible = isShow ? map : null;
                        break;
                }

                //마커, 폴리 라인을 맵에 보이기/감추기
                if (markerObj.polyBackLine){
                    markerObj.polyBackLine.setMap(visible);
                }
                if (markerObj.polyLine) {
                    markerObj.polyLine.setMap(visible);
                }
                if (markerObj.marker) {
                    markerObj.marker.setMap(visible);
                    // if (type !== 'intersectionCamera') {
                    //     markerObj.setImage(markerObj.imageType);
                    // }
                }
            }
        }

        //현재 보이기/감추기 상태값 설정
        this.setIsDraw(type, isShow);
    }
}

/**
 * 지점 검색 리스트 보이기 / 감추기
 */
function toggleLocationBox() {
    const $locationBox = $('.location-box');
    if (_MarkerArr.length > 0) {
        _MarkerArr.forEach((obj)=>{
            obj.setMap(null);
        });
        _MarkerArr = [];
    }
    $('.location-box input').val('');
    $('.list-content.spot').empty();
    $locationBox.toggle();
    $('.location-btn').toggleClass('on');
}
