package com.tsi.navi.activity;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.naver.maps.geometry.LatLng;
import com.naver.maps.geometry.LatLngBounds;
import com.naver.maps.map.CameraPosition;
import com.naver.maps.map.CameraUpdate;
import com.naver.maps.map.CameraUpdateParams;
import com.naver.maps.map.LocationTrackingMode;
import com.naver.maps.map.MapFragment;
import com.naver.maps.map.NaverMap;
import com.naver.maps.map.NaverMapOptions;
import com.naver.maps.map.NaverMapSdk;
import com.naver.maps.map.OnMapReadyCallback;
import com.naver.maps.map.overlay.Align;
import com.naver.maps.map.overlay.InfoWindow;
import com.naver.maps.map.overlay.Marker;
import com.naver.maps.map.overlay.OverlayImage;
import com.naver.maps.map.util.FusedLocationSource;
import com.tsi.navi.R;
import com.tsi.navi.TsiNaviApplication;
import com.tsi.navi.activity.dialog.AppInformationDialog;
import com.tsi.navi.activity.dialog.IDialogClickListener;
import com.tsi.navi.activity.dialog.MapSettingDialog;
import com.tsi.navi.activity.dialog.NodeCrossDialog;
import com.tsi.navi.activity.fragment.AreaCrossFragment;
import com.tsi.navi.activity.fragment.DirectionNaviSignalFragment;
import com.tsi.navi.activity.fragment.DirectionSignalFragment;
import com.tsi.navi.activity.fragment.UserSignalFragment;
import com.tsi.navi.activity.model.NodeCrossItem;
import com.tsi.navi.database.DbQuery;
import com.tsi.navi.databinding.ActivityMainBinding;
import com.tsi.navi.dto.CpuNodeStatusDTO;
import com.tsi.navi.preferences.PrefManager;
import com.tsi.navi.util.BackPressedForFinish;
import com.tsi.navi.util.BoundaryUtils;
import com.tsi.navi.util.CameraInfo;
import com.tsi.navi.util.TsiUtils;
import com.tsi.navi.vo.NodeVo;
import com.tsi.navi.vo.TsiGpsTrackingVo;
import com.tsi.navi.websocket.NaviWebsocketClientService;

import org.jetbrains.annotations.NotNull;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

@RequiresApi(api = Build.VERSION_CODES.R)
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {

    private static final String TAG = MainActivity.class.getSimpleName();

    private Context context;
    private BackPressedForFinish backPressedForFinish = null;
    private ActivityMainBinding binding = null;
    private NaverMap naverMap = null;

    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
    private final String[] permissions = {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
    };
    private FusedLocationSource locationSource;

    private boolean dispAllSignal = false;
    private boolean isTrackingMode = false;
    private boolean isDebugMode = false;
    private double trackingZoom;
    private float currSpeed = 0;
    private float lastBearing = -1, currBearing = -1;
    private float moveDist = 0;

    private CameraInfo cameraInfo;
    private BoundaryUtils mapUtils;
    private float carX = 0;
    private float carY = 0;
    private double tilt = 45;
    private float trackingDistance = 5;
    private float driveDistance = 8;

    private TsiGpsTrackingVo gpsTracking;
    private Location lastLocation = null;
    private final float[] resultDistance = {-999, -999, -999};
    private String deviceId;

    private DbQuery dbQuery;

    private int overlayResourceId;
    private CameraUpdateParams cameraUpdateParams;
    private float width, height;
    private NodeVo selectedNode = null;
    private InfoWindow infoWindow;
    private Vector<Marker> activeMarkers;
    private ConcurrentHashMap<Long, NodeVo> activeNodes;
    //private TsiApiService tsiApiService;

    private FragmentManager fragmentManager;
    private DirectionSignalFragment directionSignalFragment;
    private DirectionNaviSignalFragment directionNaviSignalFragment;
    private UserSignalFragment userSignalFragment;
    private AreaCrossFragment areaCrossFragment;
    private String lastTopics;

    private NaviWebsocketClientService naviClientService;

    private final AtomicBoolean moveByGps = new AtomicBoolean(false);

    private final ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //private NaviWebsocketClient naviClient;
            NaviWebsocketClientService.NaviWebsocketClientBinder naviClientBinder = (NaviWebsocketClientService.NaviWebsocketClientBinder) iBinder;
            naviClientService = naviClientBinder.getService();
            //naviClient = naviClientService.getNaviWebsocketClient();
            Toast.makeText(context, "서버 통신 시작", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Toast.makeText(context, "서버 통신 연결 종료", Toast.LENGTH_SHORT).show();
        }
    };

    private class NaviWebsocketMessageListener extends BroadcastReceiver {

        ObjectMapper mapper = new ObjectMapper();

        @Override
        public void onReceive(Context context, Intent intent) {
            String message = intent.getStringExtra("message");
            try {
                if (message.contains("offline")) {
                    List<String> requests = TsiUtils.split(message, ":");
                    if (requests.size() == 2) {
                        NodeVo node = TsiNaviApplication.getNodeMap().get(Long.parseLong(requests.get(0)));
                        if (node != null) {
                            Toast.makeText(context, node.getName() + " 는(은) 통신 OffLine 입니다.", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
                else {
                    CpuNodeStatusDTO status = this.mapper.readValue(message, CpuNodeStatusDTO.class);
                    if (directionSignalFragment.isVisible() && directionSignalFragment.getNodeId() == status.getA()) {
                        directionSignalFragment.updateSignal(status);
                    }
                    if (directionNaviSignalFragment.isVisible() && directionNaviSignalFragment.getNodeId() == status.getA()) {
                        directionNaviSignalFragment.updateSignal(status);
                    }
                    if (userSignalFragment.isVisible() && userSignalFragment.getNodeId() == status.getA()) {
                        userSignalFragment.updateSignal(status, 10);
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    }

    private void requestTopicMessage(String topic) {
        try {
            this.naviClientService.sendMsg("topics:" + topic);
            this.lastTopics = topic;

            if (topic.equalsIgnoreCase("x")) {
                this.lastTopics = "";   // 교차로 신호정보 요청 취소

                setSelectedNode(null);
                setRequestNode(null);

                if (this.userSignalFragment.isVisible()) {
                    this.fragmentManager.beginTransaction().hide(this.userSignalFragment).commit();
                }
                if (this.directionSignalFragment.isVisible()) {
                    this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
                }
                if (this.directionNaviSignalFragment.isVisible()) {
                    this.fragmentManager.beginTransaction().hide(this.directionNaviSignalFragment).commit();
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Request topic error: " + e.getMessage());
        }
    }

    private class NaviWebsocketEventListener extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {

            String event = intent.getStringExtra("event");
            //Log.e(TAG, "onEvent: " + event);
            if (event.equalsIgnoreCase("open")) {
                naviClientService.sendMsg("group:" + deviceId);                 // group id send
                if (!lastTopics.equals("")) {
                    requestTopicMessage(lastTopics);
                }
                //Toast.makeText(context, "서버 통신 연결", Toast.LENGTH_SHORT).show();
            } else if (event.equalsIgnoreCase("close")) {
                Toast.makeText(context, "서버와 연결이 종료되었습니다.", Toast.LENGTH_SHORT).show();
            } else if (event.equalsIgnoreCase("error")) {
                Toast.makeText(context, "서버와 연결 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private static class InfoWindowAdapter extends InfoWindow.ViewAdapter {
        @NonNull
        private final Context context;
        private View rootView;
        private ImageView icon;
        private TextView text;

        private InfoWindowAdapter(@NonNull Context context) {
            this.context = context;
        }

        @NonNull
        @Override
        public View getView(@NonNull InfoWindow infoWindow) {
            if (rootView == null) {
                rootView = View.inflate(context, R.layout.marker_info_window, null);
                icon = rootView.findViewById(R.id.icon);
                text = rootView.findViewById(R.id.text);
            }

            if (infoWindow.getMarker() != null) {
                icon.setImageResource(R.drawable.ic_place_black_24dp);
                text.setText((String) infoWindow.getMarker().getTag());
            } else {
                icon.setImageResource(R.drawable.ic_app);
                text.setText(context.getString(
                        R.string.format_coord, infoWindow.getPosition().latitude, infoWindow.getPosition().longitude));
            }

            return rootView;
        }
    }

    @SuppressLint("NonConstantResourceId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(this.binding.getRoot());

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);   // 화면 꺼짐 방지

        this.context = MainActivity.this;
        this.deviceId = PrefManager.getDeviceId(this);
        this.dispAllSignal = PrefManager.getDispAreaCross(this);

        TsiNaviApplication.getMapConfig().loadConfig(this);
        //this.tsiApiService = ApiUtils.getApiService(PrefManager.getApiUrl(this));
        this.trackingZoom = TsiNaviApplication.getMapConfig().getTrackingZoom();
        this.tilt = TsiNaviApplication.getMapConfig().getRunTilt();
        this.trackingDistance = TsiNaviApplication.getMapConfig().getRunTrackingDistance();
        this.driveDistance = TsiNaviApplication.getMapConfig().getRunDriveDistance();

        this.isTrackingMode = false;

        this.cameraInfo = new CameraInfo(this);
        this.mapUtils = new BoundaryUtils(this);
        this.gpsTracking = new TsiGpsTrackingVo();
        this.backPressedForFinish = new BackPressedForFinish(this);     // BackPressedForFinish 객체를 생성한다.
        //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        MainActivityUtils.initDatabase(this);
        this.dbQuery = new DbQuery(this);
        this.dbQuery.createDatabase();
        this.dbQuery.open();

        // 지도사용권한
        this.locationSource = new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE);

        this.binding.tvLog.setText(" ");
        this.binding.tvLog.setVisibility(View.INVISIBLE);
        this.binding.tvSpeed.setText("   ");

        this.fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = this.fragmentManager.beginTransaction();

        this.areaCrossFragment = new AreaCrossFragment();
        this.directionSignalFragment = new DirectionSignalFragment();
        this.directionNaviSignalFragment = new DirectionNaviSignalFragment();
        this.userSignalFragment = new UserSignalFragment();

        transaction.add(R.id.frame_area_cross, areaCrossFragment, "AreaCross");
        transaction.add(R.id.frame_direction_signal, this.directionSignalFragment, "DirectionSignal");
        transaction.add(R.id.frame_direction_navi_signal, this.directionNaviSignalFragment, "DirectionNaviSignal");
        transaction.add(R.id.frame_user_signal, this.userSignalFragment, "UserSignal");
        transaction.hide(areaCrossFragment);
        transaction.hide(this.directionSignalFragment);
        transaction.hide(this.directionNaviSignalFragment);
        transaction.hide(this.userSignalFragment);
        transaction.commit();
        /*this.areaCrossFragment.setOnItemClickListener(item -> {
            if (item != null) {
                requestNodeSignal(item.getNodeId(), true);
            }
        });
*/
        // Naver 지도객체 생성
        FragmentManager fragmentManager = getSupportFragmentManager();
        MapFragment mapFragment = (MapFragment)fragmentManager.findFragmentById(R.id.map);
        if (mapFragment == null) {
            mapFragment = MapFragment.newInstance(new NaverMapOptions());
            fragmentManager.beginTransaction().add(R.id.map, mapFragment).commit();
        }
        mapFragment.getMapAsync(this);

        startNaviWebsocketClientService();

        this.binding.fabSetCurrent.setVisibility(View.INVISIBLE);
        this.binding.fabSetCurrent.setOnClickListener(v -> {
            setTrackingMode(true);
        });

        this.binding.fabMenu.setOnClickListener(v -> {
            PopupMenu popup = new PopupMenu(MainActivity.this, v);
            final MenuInflater inflater = popup.getMenuInflater();
            inflater.inflate(R.menu.bottom_menu, popup.getMenu());

            MenuItem itemDispCross = popup.getMenu().findItem(R.id.menu_disp_cross);
            //itemDispCross.setVisible(false);
            if (itemDispCross != null) {
                if (this.dispAllSignal) {
                    itemDispCross.setIcon(R.drawable.ic_check_yes);
                }
                else {
                    itemDispCross.setIcon(R.drawable.ic_check_no);
                }
            }
            MenuItem itemDebugMode = popup.getMenu().findItem(R.id.menu_debug_mode);
            if (itemDebugMode != null) {
                if (this.isDebugMode) {
                    itemDebugMode.setIcon(R.drawable.ic_check_yes);
                }
                else {
                    itemDebugMode.setIcon(R.drawable.ic_check_no);
                }
            }

            binding.fabMenu.setImageResource(R.drawable.ic_action_close);

            popup.setOnMenuItemClickListener(item -> {
                switch (item.getItemId()){
                    case R.id.menu_cross:
                        NodeCrossDialog nodeCrossDialog = new NodeCrossDialog(MainActivity.this,
                                new IDialogClickListener() {
                                    @Override
                                    public void onPositiveClick() {
                                    }
                                    @Override
                                    public void onNegativeClick() {
                                    }
                                });

                        nodeCrossDialog.setOnDismissListener(d -> {
                            NodeCrossItem nodeItem = nodeCrossDialog.getSelectedNode();
                            if (nodeItem != null) {
                                // 교차로 선택 다이얼로그에서 교차로 선택했음
                                NodeVo node = TsiNaviApplication.getNodeMap().get(nodeItem.getId());
                                if (node != null) {
                                    naverMap.moveCamera(CameraUpdate.scrollTo(node.getLatLng()));
                                    setSelectedNode(node);
                                    setTrackingMode(false);
                                    requestSelectedNode();
                                }
                            }
                        });

                        nodeCrossDialog.setCanceledOnTouchOutside(true); //다이얼로그 밖에 터치했을 때 다이얼로그가 꺼짐.
                        nodeCrossDialog.setCancelable(true);             //다이얼로그 취소 가능. ( back 버튼 )
                        nodeCrossDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
                        nodeCrossDialog.show();
                        break;
                    case R.id.menu_map_settings:
                        MapSettingDialog mapSettingDialog = new MapSettingDialog(MainActivity.this,
                                new IDialogClickListener() {
                                    @Override
                                    public void onPositiveClick() {
                                        // 설정값을 레퍼런스에 저장
                                        TsiNaviApplication.getMapConfig().saveConfig(MainActivity.this);
                                        mapUtils.loadConfig();
                                        trackingZoom = TsiNaviApplication.getMapConfig().getTrackingZoom();
                                    }
                                    @Override
                                    public void onNegativeClick() {
                                        // 레퍼런스를 다시읽어 설정값 초기화
                                        TsiNaviApplication.getMapConfig().loadConfig(MainActivity.this);
                                    }
                                }, naverMap, mapUtils);

                        mapSettingDialog.setCanceledOnTouchOutside(true); //다이얼로그 밖에 터치했을 때 다이얼로그가 꺼짐.
                        mapSettingDialog.setCancelable(true);             //다이얼로그 취소 가능. ( back 버튼 )
                        mapSettingDialog.setOnDismissListener(d -> {
                            // 맵 설정정보 셋팅
                            MainActivityUtils.setupMap(naverMap, trackingZoom);
 /*                           if (TsiNaviApplication.getMapConfig().isNightMode()) {
                                this.overlayResourceId = (this.overlayResourceId == R.drawable.ic_navi_car_on) ? R.drawable.ic_navi_car_night_on : R.drawable.ic_navi_car_night_off;
                            }
                            else {
                                this.overlayResourceId = (this.overlayResourceId == R.drawable.ic_navi_car_night_on) ? R.drawable.ic_navi_car_on : R.drawable.ic_navi_car_off;
                            }
                            this.naverMap.getLocationOverlay().setIcon(OverlayImage.fromResource(this.overlayResourceId));
*/                        });
                        mapSettingDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
                        mapSettingDialog.show();
                        break;
                    case R.id.menu_map_cache_delete:
                        NaverMapSdk.getInstance(MainActivity.this).flushCache(new NaverMapSdk.CacheFlushCallback() {
                            @Override
                            public void onCacheFlushed() {
                                Toast.makeText(MainActivity.this, "지도캐시초기화 완료.", Toast.LENGTH_SHORT).show();
                            }
                        });
                        break;
                    case R.id.menu_app_info:
                        AppInformationDialog appInformationDialog = new AppInformationDialog(MainActivity.this, new IDialogClickListener() {
                            @Override
                            public void onPositiveClick() { }
                            @Override
                            public void onNegativeClick() { }
                        });
                        appInformationDialog.setCanceledOnTouchOutside(true); //다이얼로그 밖에 터치했을 때 다이얼로그가 꺼짐.
                        appInformationDialog.setCancelable(true);             //다이얼로그 취소 가능. ( back 버튼 )
                        appInformationDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
                        appInformationDialog.show();
                        break;
                    case R.id.menu_disp_cross:
                        this.dispAllSignal = !this.dispAllSignal;
                        PrefManager.setDispAreaCross(this.context, this.dispAllSignal);
                        if (!this.isTrackingMode) {
                            if (this.dispAllSignal) {
                                if (this.selectedNode != null) {
                                    if (!this.directionSignalFragment.isVisible()) {
                                        this.fragmentManager.beginTransaction().show(this.directionSignalFragment).commit();
                                        this.directionSignalFragment.setNodeCross(this.selectedNode, 10);
                                    }
                                    if (!this.directionNaviSignalFragment.isVisible()) {
                                        this.fragmentManager.beginTransaction().show(this.directionNaviSignalFragment).commit();
                                        this.directionNaviSignalFragment.setNodeCross(this.selectedNode, 10);
                                    }
                                }
                            }
                            else {
                                if (this.directionSignalFragment.isVisible()) {
                                    //this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
                                    this.directionSignalFragment.setNodeCross(null, 10);
                                }
                                if (this.directionNaviSignalFragment.isVisible()) {
                                    //this.fragmentManager.beginTransaction().hide(this.directionNaviSignalFragment).commit();
                                    this.directionNaviSignalFragment.setNodeCross(null, 10);
                                }
                            }
                        }
                        break;
                    case R.id.menu_debug_mode:
                        this.isDebugMode = !this.isDebugMode;
                        mapUtils.setDebugMode(this.isDebugMode);
                        mapUtils.drawBounds(null);
                        if (!this.isDebugMode) {
                            if (this.gpsTracking.getRequestNode() != null) {
                                requestTopicMessage("x");
                                this.directionSignalFragment.setNodeCross(null, 10);
                                this.directionNaviSignalFragment.setNodeCross(null, 10);
                            }
                            if (this.directionSignalFragment.isVisible()) {
                                this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
                                this.fragmentManager.beginTransaction().hide(this.directionNaviSignalFragment).commit();
                            }
                        }
                        break;
                    default:
                        Log.e(TAG, "뭐가 클릭된거야?");
                        return false;
                }
                return true;
            });

            // show icons on popup menu
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                popup.setForceShowIcon(true);
            }

            popup.setOnDismissListener(menu -> {
                binding.fabMenu.setImageResource(R.drawable.ic_action_menu);
            });
            popup.show();
        });
    }
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        //https://developer.android.com/training/location/request-updates?hl=ko
        /*outState.putBoolean(REQUESTING_LOCATION_UPDATES_KEY,
                requestingLocationUpdates);
        // ...*/
        super.onSaveInstanceState(outState);
    }

    private void startNaviWebsocketClientService() {

        this.lastTopics = "";

        // start service
        Intent intent = new Intent(this.context, NaviWebsocketClientService.class);
        startService(intent);

        // bind service
        Intent bindIntent = new Intent(this.context, NaviWebsocketClientService.class);
        bindService(bindIntent, serviceConnection, BIND_AUTO_CREATE);

        // register receiver
        NaviWebsocketMessageListener naviClientMessageListener = new NaviWebsocketMessageListener();
        IntentFilter messageFilter = new IntentFilter("com.websocket.content");
        registerReceiver(naviClientMessageListener, messageFilter);

        NaviWebsocketEventListener naviClientEventListener = new NaviWebsocketEventListener();
        IntentFilter eventFilter = new IntentFilter("com.websocket.event");
        registerReceiver(naviClientEventListener, eventFilter);
    }

    //권한 받아오기
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (this.locationSource.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
            if (!this.locationSource.isActivated()) {
                // 권한 거부됨
                this.naverMap.setLocationTrackingMode(LocationTrackingMode.None);
            }
            else {
                this.naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @SuppressLint("SetTextI18n")
    @UiThread
    @Override
    public void onMapReady(@NonNull @NotNull NaverMap naverMap) {

        this.naverMap = naverMap;
        this.cameraUpdateParams = new CameraUpdateParams();

        this.mapUtils.setup(this.naverMap);

        // 네이버 맵에 locationSource 를 설정하면 위치 추적 기능을 사용 할 수 있다
        this.naverMap.setLocationSource(this.locationSource);
        ActivityCompat.requestPermissions(this, this.permissions, LOCATION_PERMISSION_REQUEST_CODE);

        setMapContentPadding();
        MainActivityUtils.initMap(this.naverMap);
        //TABS6(2560*1600). S105G(3040*1440)
        this.overlayResourceId = R.drawable.ic_car_red;//R.drawable.ic_navi_car_on;//TsiNaviApplication.getMapConfig().isNightMode() ? R.drawable.ic_navi_car_night_on : R.drawable.ic_navi_car_on;
        MainActivityUtils.initMapOverlay(this.naverMap, this.overlayResourceId, (1440 >= Math.min(this.width, this.height)));
        MainActivityUtils.setupMap(this.naverMap, this.trackingZoom);

        initNodeMarker();
        setTrackingMode(true);

        // 도로교통공단 서울지부로 위치 이동(최초 기본 위치)
        //LatLng initialPosition = new LatLng(37.465913939690815, 127.04432631214559);
        /*CameraUpdate cameraUpdate = CameraUpdate.scrollTo(initialPosition);
        this.naverMap.moveCamera(cameraUpdate);*/
        /*CameraPosition initialCameraPosition = new CameraPosition(
                initialPosition,            // 위치 지정
                this.trackingZoom,          // 줌 레벨
                this.tilt,                  // 기울임 각도
                45                  // 방향
        );
        this.naverMap.setCameraPosition(initialCameraPosition);*/

        // 카메라의 움직임이 끝나 대기 상태가 되면 카메라 대기 이벤트가 발생합니다.
        // 카메라가 애니메이션 없이 움직일 때. 단, 사용자가 제스처로 지도를 움직이는 경우
        // 제스처가 완전히 끝날 때까지(ACTION_UP이 발생할 때까지)는 연속적인 이동으로 간주되어 이벤트가 발생하지 않습니다.
        // 카메라 애니메이션이 완료될 때. 단, 카메라 애니메이션이 진행 중일 때 새로운 애니메이션이 발생하거나,
        // 기존 카메라 이동의 finishCallback() 또는 cancelCallback()으로 지정된 콜백 내에서 카메라 이동이 일어날 경우
        // 연속적인 이동으로 간주되어 이벤트가 발생하지 않습니다. NaverMap.cancelTransitions()가 호출되어 카메라 애니메이션이 명시적으로 취소될 때.

        this.naverMap.addOnCameraIdleListener(() -> {
            if (!this.cameraInfo.equals(this.naverMap.getCameraPosition())) {
                this.mapUtils.initMap();
                this.mapUtils.drawBounds(this.naverMap.getCameraPosition().target);
            }
            updateNodeMarker();
        });

        this.naverMap.addOnLocationChangeListener(location -> {
            //TODO Log.e(TAG, "LocationChangeListener");
            /*LatLng coord = new LatLng(location);
            LocationOverlay locationOverlay = map.getLocationOverlay();
            locationOverlay.setVisible(true);
            locationOverlay.setPosition(coord);
            locationOverlay.setBearing(location.getBearing());
            map.moveCamera(CameraUpdate.scrollTo(coord));*/

            this.moveByGps.set(false);

            this.currSpeed = location.hasSpeed() ? location.getSpeed() * 3.6F : 0.0F;   // 속도   meter/sec ==> km/sec
            this.currBearing = location.hasBearing() ? location.getBearing() : -1;      // 베어링
            this.moveDist = this.lastLocation == null ? 0 : this.lastLocation.distanceTo(location);
            this.lastLocation = location;

            this.binding.tvSpeed.setText(String.valueOf((int)this.currSpeed));

            // 속도값이 없거나 이동거리가 기준 이하이면 GPS 수신하지 않은것으로 처리한다.
            if (this.currSpeed <= 1 || this.moveDist <= 2) {
                this.naverMap.getLocationOverlay().setBearing(this.lastBearing);
                /*int disableResourceId = TsiNaviApplication.getMapConfig().isNightMode() ? R.drawable.ic_navi_car_night_off : R.drawable.ic_navi_car_off;
                if (overlayResourceId != disableResourceId) {
                    this.naverMap.getLocationOverlay().setIcon(OverlayImage.fromResource(disableResourceId));
                    overlayResourceId = disableResourceId;
                }*/
                //updateBounds(location);
                return;
            }

            /*int enabledResourceId = TsiNaviApplication.getMapConfig().isNightMode() ? R.drawable.ic_navi_car_night_on : R.drawable.ic_navi_car_on;
            if (overlayResourceId != enabledResourceId) {
                this.naverMap.getLocationOverlay().setIcon(OverlayImage.fromResource(enabledResourceId));
                overlayResourceId = enabledResourceId;
            }*/
            if (this.currBearing >= 0) {
                this.lastBearing = this.currBearing;
            }

            // GPS 이동한 것으로 판단.
            this.moveByGps.set(true);

            if (this.isTrackingMode) {
                CameraPosition gpsPosition = new CameraPosition(
                        new LatLng(location.getLatitude(), location.getLongitude()),    // 위치 지정
                        this.trackingZoom,          // 줌 레벨
                        this.tilt,                  // 기울임 각도
                        this.lastBearing            // 방향
                );
                this.naverMap.setCameraPosition(gpsPosition);
            }
        });

        this.naverMap.addOnCameraChangeListener((reason, animated) -> {
            switch(reason) {
                case CameraUpdate.REASON_DEVELOPER: //REASON_DEVELOPER:  0, 개발자가 API를 호출해 카메라가 움직였음을 나타냅니다. 기본값입니다.
                    break;
                case CameraUpdate.REASON_LOCATION:  //REASON_LOCATION:  -3, 위치 트래킹 기능으로 인해 카메라가 움직였음을 나타냅니다.
                    break;
                case CameraUpdate.REASON_GESTURE:   //REASON_GESTURE:   -1, 사용자의 제스처로 인해 카메라가 움직였음을 나타냅니다. // == CameraUpdate.DEFAULT_ANIMATION_DURATION
                case CameraUpdate.REASON_CONTROL:   //REASON_CONTROL:   -2, 사용자의 버튼 선택으로 인해 카메라가 움직였음을 나타냅니다.
                    setTrackingMode(false);
                    break;
            }
        });

        this.naverMap.setOnMapClickListener((point, coord) -> {
            if (this.infoWindow != null && this.infoWindow.isVisible()) {
                //마커클릭후에 지도클릭 이벤트가 먹기 때문에 여기서 처리하면 안된다.(마커클릭후에 true 리턴하면 된다)
                this.infoWindow.close();
            }
        });
    }

    private void updateBounds(Location location) {
        //if (location == null) return;

        if (location != null) {
            this.mapUtils.drawBounds(new LatLng(location.getLatitude(), location.getLongitude()));
        }
        else {
            this.mapUtils.drawBounds(this.naverMap.getCameraPosition().target);
        }
    }

    // 지도상에 표시되고있는 마커들 지도에서 삭제
    private void freeActiveMarkers() {
        if (this.activeMarkers == null) {
            this.activeMarkers = new Vector<>();
            return;
        }
        //TODO Log.e(TAG, "freeActiveMarkers: " + this.activeMarkers.size());
        for (Marker activeMarker: this.activeMarkers) {
            activeMarker.setMap(null);
        }
        this.activeMarkers = new Vector<>();
    }

    @SuppressLint("SetTextI18n")
    private void updateNodeMarkerInDrive(boolean trackingMode) {
        boolean isMovingByGps = this.moveByGps.getAndSet(false);
        //TODO Log.e(TAG, "updateNodeMarkerInDrive: " + trackingMode + ", " + isMovingByGps);
        //if (!trackingMode || !isMovingByGps) {
        if (!trackingMode && !this.isDebugMode) {
            //TODO Log.e(TAG, "isMovingByGps == false");
            return;
        }

        /*if (this.naverMap.getCameraPosition().zoom != this.trackingZoom) {
            if (this.directionSignalFragment.isVisible()) {
                requestTopicMessage("x");
                this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
            }
            if (this.areaCrossFragment.isVisible()) {
                this.fragmentManager.beginTransaction().hide(this.areaCrossFragment).commit();
            }
            Log.e(TAG, "updateNodeMarkerInDrive: zoom: " + this.naverMap.getCameraPosition().zoom + ", " + this.trackingZoom);
            return;
        }*/

        LatLngBounds carBounds = this.mapUtils.getCarBounds();
        PointF carPos = this.mapUtils.getCarPosition();
        //LatLng[] latLngBounds = this.mapUtils.getLatLngBounds();
        //LatLng carLatLng = this.naverMap.getLocationOverlay().getPosition();    // 현재 차량위치
        LatLng carLatLng = this.naverMap.getCameraPosition().target;    // 현재 차량위치

        //int signalDirectionCode = TsiNaviApplication.getDirection().getSignalDirectionCode(this.lastBearing == 0 ? this.naverMap.getCameraPosition().bearing : this.lastBearing);
        int signalDirectionCode = TsiNaviApplication.getDirection().getSignalDirectionCode(this.naverMap.getCameraPosition().bearing);
        if (signalDirectionCode == 0) {
            //TODO Log.e(TAG, "Direction Error: " + signalDirectionCode);
            return;
        }

        if (this.gpsTracking.getSignalDirCode() != signalDirectionCode) {
            //TODO Log.e(TAG, "Bearing different: " + this.gpsTracking.getSignalDirCode() + ", " + signalDirectionCode);
            this.gpsTracking.setSignalDirCode(signalDirectionCode);
            return;
        }

        this.gpsTracking.setSignalDirCode(signalDirectionCode);

        int calDirectionCode;

        //TODO String sLog = "--Dir: " + String.format("%3d", (int)this.lastBearing) + ", " + signalDirectionCode + ", " + String.format("%3d", (int)this.naverMap.getCameraPosition().bearing);

        // 진출입 노드에 대한 계산
        double calBearing;
        double calDistance;
        double nearDistance = Float.MAX_VALUE;
        double crossDistance = Float.MAX_VALUE;
        NodeVo inCrossNode = null;
        NodeVo targetNode = null;
        NodeVo nearNode = null;
        Set<Long> inCrossNodeSet= new HashSet<>();

        //TODO Log.e(TAG, "Boundary nodes: " + this.activeNodes.size() + " >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");

        boolean crossPassOffSignal = false;
        for (Map.Entry<Long, NodeVo> obj : this.activeNodes.entrySet()) {
            NodeVo node = obj.getValue();
            PointF posNode = this.naverMap.getProjection().toScreenLocation(node.getLatLng());
            Location.distanceBetween(carLatLng.latitude, carLatLng.longitude, node.getLatLng().latitude, node.getLatLng().longitude, this.resultDistance);
            calDistance = this.resultDistance[0];
            calBearing = (this.resultDistance[1] < 0) ? (360 + this.resultDistance[1]) : this.resultDistance[1];
            calDirectionCode = TsiNaviApplication.getDirection().getSignalDirectionCode(calBearing);
            //TODO Log.e(TAG, "calDirectionCode: " + calDirectionCode + ", " + node.getName());

            if (carBounds.contains(node.getLatLng())) {
                // 노드 통과 영역에 포함
                //TODO sLog += "\n-C: " + String.format("%3d, %d, %3d m, %s, %s", (int)calBearing, calDirectionCode, (int)calDistance, node.getDirCode(), node.getName());
                if (calDistance < crossDistance) {
                    inCrossNode = node;
                    crossDistance = calDistance;
                    //TODO sLog += "--Near";
                }
                //TODO if (!node.isReal()) sLog += "--Dummy";

                if (this.gpsTracking.getRequestNode() != null) {
                    if (this.gpsTracking.getRequestNode().getNodeId() == node.getNodeId()) {
                        if (posNode.y > carPos.y) {
                            // 요청 신호정보가 존재하고 지금 요청 신호등을 지났으면 신호 정보 요청 취소
                            crossPassOffSignal = true;
                            requestTopicMessage("x");
                            this.gpsTracking.setRequestNode(null);
                        }
                    }
                    else {
                        // 검지영역이 좁아서 검지역역내에 교차로가 2개 이상 검지되고 있는 경우
                        // 이전 요청 교차로가 현재 교차로가 아니면 요청을 취소한다.
                        requestTopicMessage("x");
                        this.gpsTracking.setRequestNode(null);
                    }
                }
            }

            if (this.mapUtils.contains((node.getLatLng()))) {
                // 진행방향 바운더리 영역내에 포함됨
                //TODO sLog += "\n-N: " + String.format("%3d, %d, %3d m, %s, %s", (int)calBearing, calDirectionCode, (int)calDistance, node.getDirCode(), node.getName());
                if (node.getInDirMap().contains(calDirectionCode)) {
                    //TODO sLog += "--Match";
                    if (node.getInDirMap().contains(calDirectionCode) && calDistance < nearDistance) {
                        nearNode = node;
                        nearDistance = calDistance;
                        //TODO sLog += "--Near";
                    }
                }
                //TODO if (!node.isReal()) sLog += "--Dummy";
            }
        }

        if (inCrossNode != null) {
            // 노드를 통과 중이면 처리하는게 없다. 노드를 통과한 후에 처리하도록 한다.
            this.gpsTracking.setInCrossNode(inCrossNode);
            if (crossPassOffSignal) {
                // 요청 신호 정보 교차로를 지나서 요청 신호를 취소한 경우
                // ====> 이부분 주석 처리하면 교차로 통과할 동안 신호정보 요청을 보내지 않는다.
/*
                if (nearNode != null && nearNode.isReal()) {
                    if (this.gpsTracking.getRequestNode() == null) {
                        requestNodeSignal(nearNode.getNodeId(), signalDirectionCode);
                        this.gpsTracking.setRequestNode(nearNode);
                    }
                }
*/
            }
        }
        else {
            // 노드를 통과하는 것이 아니라 도로를 주행 중인 상태
            if (this.gpsTracking.getInCrossNode() != null) {
                // 노드를 빠져 나온 상태. 시작 노드 정보로 설정한다.
                this.gpsTracking.setStartNode(inCrossNode);
                // 노드를 빠져 나올때 근접 노드정보가 있을 경우와 없을 경우에 따라 처리
                if (nearNode != null) {
                    if (nearNode.isReal()) {
                        if (this.gpsTracking.getRequestNode() == null || this.gpsTracking.getRequestNode().getNodeId() != nearNode.getId()) {
                            requestNodeSignal(nearNode.getNodeId(), signalDirectionCode);
                            this.gpsTracking.setRequestNode(nearNode);
                        }
                    }
                    else {
                        this.gpsTracking.setEndNode(nearNode);
                    }
                }
                else {
                    if (this.gpsTracking.getRequestNode() != null) {
                        // 더이상 요청할 노드가 진행 방향에 존재하지 않는다.
                        requestTopicMessage("x");
                        this.gpsTracking.setRequestNode(null);
                    }
                }
            }
            else {
                // 도로 주행
                if (nearNode != null) {
                    // 진행방향에 표출할 노드가 존재하면
                    if (nearNode.isReal()) {
                        if (this.gpsTracking.getRequestNode() == null || this.gpsTracking.getRequestNode().getNodeId() != nearNode.getId()) {
                            requestNodeSignal(nearNode.getNodeId(), signalDirectionCode);
                            this.gpsTracking.setRequestNode(nearNode);
                        }
                    }
                }
                else {
                    // 진행방향에 노드가 존재하지 않음.
                    if (this.gpsTracking.getRequestNode() != null) {
                        requestTopicMessage("x");
                        this.gpsTracking.setRequestNode(null);
                    }
                }
            }
            this.gpsTracking.setInCrossNode(null);
        }

        if (this.gpsTracking.getRequestNode() != null) {
            if (this.activeNodes.get(this.gpsTracking.getRequestNode().getNodeId()) == null) {
                // 요청 중인데 영역내에 없으면 취소 시킨다.
                requestTopicMessage("x");
                this.gpsTracking.setRequestNode(null);
            }
        }
        //TODO this.binding.tvLog.setText(sLog);
        //TODO Log.e(TAG, sLog);
    }

    private void updateNodeMarker() {
        //Log.e(TAG, "updateNodeMarker: zoom: " + this.naverMap.getCameraPosition().zoom);
        updateBounds(null);

        int signalDirectionCode = TsiNaviApplication.getDirection().getSignalDirectionCode(this.naverMap.getCameraPosition().bearing);
        int runDirectionCode = TsiNaviApplication.getDirection().getSignal2RunDirection(signalDirectionCode);
        //TODO String sLog = "Bearing: " + (int)this.naverMap.getCameraPosition().bearing + ", " + signalDirectionCode + ", " + runDirectionCode + ", zoom:" + (int)this.naverMap.getCameraPosition().zoom;
        //TODO this.binding.tvLog.setText(sLog);

        Marker infoMarker = null;
        if (this.infoWindow != null) {
            infoMarker = this.infoWindow.getMarker();
        }

        freeActiveMarkers();    // 메모리 관리를 위해 여기서 삭제해 준다.

        if (this.naverMap.getCameraPosition().zoom < 13) {
            /*if (this.isTrackingMode && this.directionSignalFragment.isVisible()) {
                this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
            }*/
            if (infoMarker != null) {
                this.infoWindow.close();
            }
            return;
        }

        LatLngBounds bounds = this.naverMap.getCoveringBounds();
        //LatLngBounds bounds = this.naverMap.getContentBounds();
        ConcurrentHashMap<Long, NodeVo> activeNodes = new ConcurrentHashMap<>();
        Vector<Marker> activeMarkers = new Vector<>();
        List<Long> nodes = MainActivityUtils.getBoundsNodes(this.dbQuery, bounds);
        if (nodes != null) {
            //TODO Log.e(TAG, "Bounds nodes: " + nodes.size() + " EA.");
            if (nodes.size() <= 300) {
                for (Long obj : nodes) {
                    try {
                        NodeVo node = TsiNaviApplication.getNodeMap().get(obj);
                        if (node != null) {
                            activeMarkers.add(node.getMarker());
                            activeNodes.put(node.getNodeId(), node);
                        }
                    } catch (Exception e) {
                        Log.e(TAG, "updateNodeMarker: " + e.getMessage());
                    }
                }
            }
        }

        this.activeMarkers = activeMarkers;
        for (Marker activeMarker: this.activeMarkers) {
            if (!activeMarker.getTag().equals("x")) {
                activeMarker.setMap(this.naverMap);
            }
        }
        if (infoMarker != null) {
            this.infoWindow.open(infoMarker, Align.TopLeft);
        }

        if (this.isTrackingMode) {
            // 트래킹(드라이빙) 모드이고 조건을 만족할 경우
            this.activeNodes = activeNodes;
            updateNodeMarkerInDrive(true);
        }
        else {
            // 사용자 움직임인 경우 바운더리 계산해서 영역을 다시 찾은 다음 영역에 속한
            // 교차로 정보를 다시 조회해서 처리해야함
            this.activeNodes = activeNodes;
            updateNodeMarkerInDrive(false);
        }
    }

    private void requestNodeSignal(long nodeId, int directionCode) {
        if (!this.isTrackingMode) {
            //return;
        }

        NodeVo node = TsiNaviApplication.getNodeMap().get(nodeId);
        //if (node == null) return;
        setRequestNode(node);

        this.userSignalFragment.setNodeCross(null);
        this.directionSignalFragment.setNodeCross(node, directionCode);
        this.directionNaviSignalFragment.setNodeCross(node, directionCode);

        requestTopicMessage(String.valueOf(nodeId));

        if (!this.directionSignalFragment.isVisible()) {
            this.fragmentManager.beginTransaction().show(this.directionSignalFragment).commit();
        }
        if (!this.directionNaviSignalFragment.isVisible()) {
            this.fragmentManager.beginTransaction().show(this.directionNaviSignalFragment).commit();
        }
        if (userSignalFragment.isVisible()) {
            this.fragmentManager.beginTransaction().hide(this.userSignalFragment).commit();
        }
    }

    private void setSelectedNode(NodeVo node) {
        this.gpsTracking.setRequestNode(null);
        if (this.selectedNode != null) {
            // 이전 선택한 아이콘은 기본 아이콘으로 변경
            this.selectedNode.marker.setIcon(OverlayImage.fromResource(R.drawable.ic_cross_normal));
        }

        if (this.infoWindow != null && this.infoWindow.isVisible()) {
            // 이전 인포 윈도우 닫음
            this.infoWindow.close();
        }

        this.selectedNode = node;
        if (this.selectedNode != null) {
            // 선택한 아이콘은 선택 아이콘으로 변경
            this.selectedNode.marker.setIcon(OverlayImage.fromResource(R.drawable.ic_cross_select));
            // 선택한 아이콘의 인포 윈도우 오픈
            this.infoWindow.open(this.selectedNode.marker, Align.TopLeft);
        }
    }
    private void setRequestNode(NodeVo node) {
        this.selectedNode = null;
        if (this.gpsTracking.getRequestNode() != null) {
            // 이전 선택한 아이콘은 기본 아이콘으로 변경
            this.gpsTracking.getRequestNode().marker.setIcon(OverlayImage.fromResource(R.drawable.ic_cross_normal));
        }

        if (this.infoWindow != null && this.infoWindow.isVisible()) {
            // 이전 인포 윈도우 닫음
            this.infoWindow.close();
        }

        this.gpsTracking.setRequestNode(node) ;
        if (this.gpsTracking.getRequestNode() != null) {
            // 선택한 아이콘은 선택 아이콘으로 변경
            this.gpsTracking.getRequestNode().marker.setIcon(OverlayImage.fromResource(R.drawable.ic_cross_select));
            // 선택한 아이콘의 인포 윈도우 오픈
            this.infoWindow.open(this.gpsTracking.getRequestNode().marker, Align.TopLeft);
        }
    }

    private void setTrackingMode(boolean trackingMode) {
        if (!this.isTrackingMode && trackingMode) {
            requestTopicMessage("x");
            this.moveByGps.set(true);
        }
        if (this.isTrackingMode && !trackingMode) {
            this.gpsTracking.init();
        }

        this.isTrackingMode = trackingMode;

        if (trackingMode) {
            if (this.naverMap.getLocationTrackingMode() != LocationTrackingMode.Follow) {
                this.naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);   // Tracking Mode 설정
                //binding.tvLog.setText("Follow");
                /*this.cameraUpdateParams//.scrollTo(this.cameraPosition.target)
                        .zoomTo(this.trackingZoom)
                        .rotateTo(this.lastBearing).tiltTo(this.tilt);
                this.naverMap.moveCamera(CameraUpdate.withParams(this.cameraUpdateParams));*/
            }
            this.cameraUpdateParams//.scrollTo(this.cameraPosition.target)
                    .zoomTo(this.trackingZoom)
                    .rotateTo(this.lastBearing).tiltTo(this.tilt);
            this.naverMap.moveCamera(CameraUpdate.withParams(this.cameraUpdateParams));

            if (this.naverMap.getUiSettings().isZoomControlEnabled()) {
                this.naverMap.getUiSettings().setZoomControlEnabled(false);
            }
            if (this.binding.tvSpeed.getVisibility() == View.INVISIBLE) {
                this.binding.tvSpeed.setVisibility(View.VISIBLE);
            }

            if (this.lastLocation != null) {
                CameraPosition initialCameraPosition = new CameraPosition(
                        new LatLng(this.lastLocation.getLatitude(), this.lastLocation.getLongitude()),            // 위치 지정
                        this.trackingZoom,          // 줌 레벨
                        this.tilt,                  // 기울임 각도
                        this.lastBearing            // 방향
                );
                this.naverMap.setCameraPosition(initialCameraPosition);
            }

            if (View.VISIBLE == this.binding.fabSetCurrent.getVisibility()) {
                this.binding.fabSetCurrent.setVisibility(View.INVISIBLE);
            }
            if (this.infoWindow != null && this.infoWindow.isVisible()) {
                //마커클릭후에 지도클릭 이벤트가 먹기 때문에 여기서 처리하면 안된다.
                this.infoWindow.close();
            }
        }
        else {
            if (this.naverMap.getLocationTrackingMode() != LocationTrackingMode.NoFollow) {
                this.naverMap.setLocationTrackingMode(LocationTrackingMode.NoFollow);
                this.cameraUpdateParams//.scrollTo(this.cameraPosition.target)
                        .zoomTo(this.trackingZoom)
                        .rotateTo(0).tiltTo(this.tilt);
                this.naverMap.moveCamera(CameraUpdate.withParams(this.cameraUpdateParams));
            }
            if (!this.naverMap.getUiSettings().isZoomControlEnabled()) {
                this.naverMap.getUiSettings().setZoomControlEnabled(true);
            }
            if (this.binding.tvSpeed.getVisibility() == View.VISIBLE) {
                this.binding.tvSpeed.setVisibility(View.INVISIBLE);
            }
            if (View.VISIBLE != this.binding.fabSetCurrent.getVisibility()) {
                this.binding.fabSetCurrent.setVisibility(View.VISIBLE);
            }

            if (!this.userSignalFragment.isVisible()) {
                if (this.selectedNode != null) this.fragmentManager.beginTransaction().show(this.userSignalFragment).commit();
            }

            if (this.dispAllSignal) {
                if (!this.directionSignalFragment.isVisible()) {
                    //TODO Log.e(TAG, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: " + this.selectedNode);
                    if (this.selectedNode != null) this.fragmentManager.beginTransaction().show(this.directionSignalFragment).commit();
                }
                if (!this.directionNaviSignalFragment.isVisible()) {
                    //TODO Log.e(TAG, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: " + this.selectedNode);
                    if (this.selectedNode != null) this.fragmentManager.beginTransaction().show(this.directionNaviSignalFragment).commit();
                }
            }
            else {
                if (this.directionSignalFragment.isVisible()) {
                    //this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
                }
                if (this.directionNaviSignalFragment.isVisible()) {
                    //this.fragmentManager.beginTransaction().hide(this.directionNaviSignalFragment).commit();
                }
            }
        }
    }

    private void requestSelectedNode() {
        if (this.selectedNode != null) {
            this.naverMap.moveCamera(CameraUpdate.scrollTo(this.selectedNode.getLatLng()));

            this.userSignalFragment.setNodeCross(this.selectedNode);
            this.directionSignalFragment.setNodeCross(this.selectedNode, 10);
            this.directionNaviSignalFragment.setNodeCross(this.selectedNode, 10);
            requestTopicMessage(String.valueOf(this.selectedNode.getNodeId()));
        }
    }
    private void initNodeMarker() {
        // 인포 윈도우 생성
        this.infoWindow = new InfoWindow();
        this.infoWindow.setAnchor(new PointF(0, 1));
        this.infoWindow.setOffsetX(getResources().getDimensionPixelSize(R.dimen.marker_info_window_offset_x));
        this.infoWindow.setOffsetY(getResources().getDimensionPixelSize(R.dimen.marker_info_window_offset_y));
        this.infoWindow.setAdapter(new InfoWindowAdapter(this));

        this.infoWindow.setOnClickListener(overlay -> {
            if (this.selectedNode != null) {
                if (!this.isTrackingMode) {
                    setTrackingMode(false);
                    requestSelectedNode();
                }
            }
            else {
                this.infoWindow.close();
            }
            return true;
        });

        // 교차로 마커 생성
        for (Map.Entry<Long, NodeVo> obj : TsiNaviApplication.getNodeMap().entrySet()) {
            NodeVo node = obj.getValue();
            //node.marker = MainActivityUtils.createNodeMarker(this.naverMap, node, R.drawable.ic_cross_normal, R.drawable.ic_node_small);
            node.marker = MainActivityUtils.createNodeMarker(null, node, R.drawable.ic_cross_normal, R.drawable.ic_node_small);
            if (node.isReal()) {
                node.marker.setOnClickListener(overlay -> {
                    setSelectedNode(node);
                    return true;    // false 리턴하면 마커클릭 후에 지도 클릭 이벤트가 동시에 발생한다.
                });
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (this.locationSource.isActivated()) {
            this.locationSource.deactivate();
        }
        if (this.naviClientService.getNaviWebsocketClient().isOpen()) {
            this.naviClientService.getNaviWebsocketClient().close();
        }
        this.dbQuery.close();
    }

    @Override
    public void onBackPressed() {
        if (this.userSignalFragment.isVisible() ||
            ( !this.isTrackingMode && this.directionSignalFragment.isVisible() || !this.isTrackingMode && this.directionSignalFragment.isVisible() )
        ) {
            requestTopicMessage("x");
            return;
        }

        /*if (this.directionSignalFragment.isVisible()) {
            this.fragmentManager.beginTransaction().hide(this.directionSignalFragment).commit();
            setTrackingMode(true);
            requestTopicMessage("x");
            return;
        }*/
        this.backPressedForFinish.onBackPressed(true);
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        /*if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
            Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
        }*/
        setMapContentPadding();
    }

    private void setMapContentPadding() {
        WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
        Rect rc = wm.getCurrentWindowMetrics().getBounds();
        float lWidth = rc.width();
        float lHeight = rc.height();
        /*@SuppressLint("WrongViewCast") FrameLayout frameMap = (FrameLayout) this.binding.getRoot().findViewById(R.id.map);
        if (frameMap != null) {
            this.width = frameMap.getWidth();
            this.height = frameMap.getHeight();
            Log.e(TAG, "width: " + this.width + ", height: " + this.height);
        }*/
        //TODO Log.e(TAG, "RECT: " + rc.width() + ", " + rc.height());

        float lCarX = lWidth / 2;
        float lCarY = (lHeight / 3) * 2;

        if (this.naverMap != null) {
            int padding = (int) (lHeight / 2);
            //TODO Log.e(TAG, "Rect: " + rc.toString());
            //TODO Log.e(TAG, "Padding: " + padding);
            this.naverMap.setContentPadding(0, 0, 0, -padding);
            //this.naverMap.setContentPadding(100, 100, 100, 100);

            lCarX = (float)(this.naverMap.getContentWidth() / 2);
            lCarY = (float)(this.naverMap.getContentHeight() / 2);
            if (this.isTrackingMode) {
                if (this.lastLocation != null) {
                    CameraPosition gpsPosition = new CameraPosition(
                            new LatLng(this.lastLocation.getLatitude(), this.lastLocation.getLongitude()),    // 위치 지정
                            this.trackingZoom,          // 줌 레벨
                            this.tilt,                  // 기울임 각도
                            this.lastBearing            // 방향
                    );
                    this.naverMap.setCameraPosition(gpsPosition);
                }
            }
            else {
                naverMap.moveCamera(CameraUpdate.scrollTo(this.naverMap.getCameraPosition().target));
            }
        }

        this.width = lWidth;
        this.height = lHeight;
        this.carX = lCarX;
        this.carY = lCarY;
        this.moveByGps.set(true);
    }

}
