package com.tsi.navi.util;

import android.annotation.SuppressLint;
import android.location.Location;
import android.os.SystemClock;

import com.naver.maps.geometry.LatLng;

import java.text.DecimalFormat;

public class LocationUtils {

    private static boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);
    }

    @SuppressLint("ObsoleteSdkInt")
    private static long getLocationAge(Location newLocation) {
        long locationAge;
        if (android.os.Build.VERSION.SDK_INT >= 17) {
            long currentTimeInMilli = (long)(SystemClock.elapsedRealtimeNanos() / 1000000);
            long locationTimeInMilli = (long)(newLocation.getElapsedRealtimeNanos() / 1000000);
            locationAge = currentTimeInMilli - locationTimeInMilli;
        }
        else {
            locationAge = System.currentTimeMillis() - newLocation.getTime();
        }
        return locationAge;
    }
    public static boolean filterLocation(Location currLocation, Location oldLocation) {

        if (oldLocation == null) {
            return true;
        }

        long age = getLocationAge(currLocation);
        if (age > 10 * 1000) {
            //more than 10 seconds
            // Log.d(TAG, "Location is old");
            // oldLocationList.add(currLocation);
            return false;
        }

        float horizontalAccuracy = currLocation.getAccuracy();
        if (horizontalAccuracy <= 0) {
            // Log.d(TAG, "Latitidue and longitude values are invalid.");
            // noAccuracyLocationList.add(currLocation);
            return false;
        }

        if (horizontalAccuracy > 100) {
            // Log.d(TAG, "Accuracy is too low.");
            // inaccurateLocationList.add(location);
            return false;
        }

        /* Kalman Filter */
        float currentSpeed = currLocation.hasSpeed() ? currLocation.getSpeed() : 0.0F;
        float qValue;

        long locationTimeInMillis = (long)(currLocation.getElapsedRealtimeNanos() / 1000000);
        long elapsedTimeInMillis = locationTimeInMillis - (long)(oldLocation.getElapsedRealtimeNanos() / 1000000);

        if (currentSpeed == 0.0f){
            qValue = 3.0f; //3 meters per second
        }
        else {
            qValue = currentSpeed; // meters per second
        }

        //kalmanFilter.Process(currLocation.getLatitude(), currLocation.getLongitude(), currLocation.getAccuracy(), elapsedTimeInMillis, qValue);
        double predictedLat = 0;//kalmanFilter.get_lat();
        double predictedLng = 0;//kalmanFilter.get_lng();

        Location predictedLocation = new Location("");      //provider name is unnecessary
        predictedLocation.setLatitude(predictedLat);                //your coords of course
        predictedLocation.setLongitude(predictedLng);
        float predictedDeltaInMeters =  predictedLocation.distanceTo(currLocation);

        if (predictedDeltaInMeters > 60) {
            // Log.d(TAG, "Kalman Filter detects mal GPS, we should probably remove this from track");
            //kalmanFilter.consecutiveRejectCount += 1;

            /*if (kalmanFilter.consecutiveRejectCount > 3) {
                kalmanFilter = new KalmanLatLong(3); //reset Kalman Filter if it rejects more than 3 times in raw.
            }*/

            //kalmanNGLocationList.add(currLocation);
            return false;
        }
        else {
            //kalmanFilter.consecutiveRejectCount = 0;
        }

        // Log.d(TAG, "Location quality is good enough.");
        currentSpeed = currLocation.getSpeed();

        return true;
    }

    public static boolean isValidLocation(Location currLocation, Location oldLocation) {
        if (oldLocation == null) {
            return true;    // 첫번째 위치 수신
        }

        final int TWO_MINUTES = 1000 * 60 * 2;

        // 현재 위치 시간 체크
        long timeDelta = currLocation.getTime() - oldLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;
        if (isSignificantlyNewer) {
            return true;    // 이전 위치 보다 2분이상 경과하였으니 그냥 현재위치 사용
        }
        else if (isSignificantlyOlder) {
            return false;   // 2분 이전 데이터이므로 현재위치를 사용하지 않음.
        }

        // 위치 정확도 확인
        int accuracyDelta = (int)(currLocation.getAccuracy() - oldLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;
        // 위치제공 공급자 확인
        boolean isFromSameProvider = isSameProvider(currLocation.getProvider(), oldLocation.getProvider());
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }
        return false;
    }

    public static float getSpeed(Location currLocation, Location oldLocation) {

        float speed = 0;
        if (currLocation.hasSpeed()){
            speed = currLocation.getSpeed() * 3.6F;
        }
        else {
            if (oldLocation != null) {
                try {
                    double oldTime = oldLocation.getTime();
                    double curTime = currLocation.getTime();
                    double distance = oldLocation.distanceTo(currLocation);
                    double timeDifferent = (oldTime - curTime) / 1000; // seconds
                    speed = (float) (distance / timeDifferent) * 3.6F;
                }
                catch (Exception e) {

                }
            }
        }
        return speed;
    }

    /**
     * 두 좌표 거리 구하기
     */
    public static double geoDistance(double latitude1, double longitude1, double latitude2, double longitude2) {

        DecimalFormat df = new DecimalFormat("#.#####");

        if ((latitude1 == latitude2) && (longitude1 == longitude2)) {
            return 0;
        } else {
            double theta = longitude1 - longitude2;
            double distance = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
                    Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * Math.cos(Math.toRadians(theta));
            distance = Math.acos(distance);
            distance = Math.toDegrees(distance);
            distance = distance * 60 * 1.1515;
            distance = distance * 1.609344;

            //distance --> 0보다 클 경우
            if (0 < distance) {
                distance = Double.valueOf(df.format(distance));    //소숫점 다섯째 자리에서 반올림

                // distance --> 0일 경우
            } else {
                distance = 0;
            }

            distance = distance * 1000; //km --> m으로 변환

            return distance;
        }

    }

    /**
     * 두 좌표 방위각 구하기
     * @return bearing
     */
    public static int getBearing(double latitude1, double longitude1, double latitude2, double longitude2) {
        // 현재 위치 : 위도나 경도는 지구 중심을 기반으로 하는 각도이기 때문에 라디안 각도로 변환한다.
        double Cur_Lat_radian = latitude1 * (Math.PI / 180);
        double Cur_Lon_radian = longitude1 * (Math.PI / 180);

        // 목표 위치 : 위도나 경도는 지구 중심을 기반으로 하는 각도이기 때문에 라디안 각도로 변환한다.
        double Dest_Lat_radian = latitude2 * (Math.PI / 180);
        double Dest_Lon_radian = longitude2 * (Math.PI / 180);

        // radian distance
        double radian_distance = 0;
        radian_distance = Math.acos(Math.sin(Cur_Lat_radian) * Math.sin(Dest_Lat_radian)
                + Math.cos(Cur_Lat_radian) * Math.cos(Dest_Lat_radian) * Math.cos(Cur_Lon_radian - Dest_Lon_radian));

        // 목적지 이동 방향을 구한다.(현재 좌표에서 다음 좌표로 이동하기 위해서는 방향을 설정해야 한다. 라디안값이다.
        double radian_bearing = Math.acos((Math.sin(Dest_Lat_radian) - Math.sin(Cur_Lat_radian)
                * Math.cos(radian_distance)) / (Math.cos(Cur_Lat_radian) * Math.sin(radian_distance)));// acos의 인수로 주어지는 x는 360분법의 각도가 아닌 radian(호도)값이다.

        double true_bearing = 0;
        if (Math.sin(Dest_Lon_radian - Cur_Lon_radian) < 0) {
            true_bearing = radian_bearing * (180 / Math.PI);
            true_bearing = 360 - true_bearing;
        } else {
            true_bearing = radian_bearing * (180 / Math.PI);
        }

        return (int) true_bearing;
    }

    /**
     * 특정 좌표에서 방위각, 거리를 가지고 원하는 좌표 값 획득
     */
    public static LatLng getRotationLatLng(LatLng base, double direction_degree, double length_degree) {
        double lat = base.latitude + length_degree * Math.sin(direction_degree * Math.PI / 180);
        double lng = base.longitude + length_degree * Math.cos(direction_degree * Math.PI / 180);

        return new LatLng(lat, lng);
    }
    /**
     * 특정 좌표에서 방위각, 거리를 가지고 원하는 좌표 값 획득
     * @param latitude latitude
     * @param longitude longitude
     * @param direction_degree direction
     * @param length_degree length
     * @return double[2] location = {longitude, latitude}
     */
    public static double[] geoMove(double latitude, double longitude, double direction_degree, double length_degree) {
        double[] location = new double[2];

        double x = longitude + length_degree * Math.cos(direction_degree * Math.PI / 180);
        double y = latitude + length_degree * Math.sin(direction_degree * Math.PI / 180);

        location[0] = x;
        location[1] = y;

        return location;
    }
}
