/*
 * Copyright (c) 2016-present, lovebing.net.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package org.lovebing.reactnative.baidumap.module;

import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.location.LocationClientOption.LocationMode;
import com.baidu.mapapi.CoordType;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.model.LatLng;
import com.baidu.mapapi.search.core.PoiInfo;
import com.baidu.mapapi.search.core.SearchResult;
import com.baidu.mapapi.search.geocode.GeoCodeOption;
import com.baidu.mapapi.search.geocode.GeoCodeResult;
import com.baidu.mapapi.search.geocode.GeoCoder;
import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult;
import com.baidu.mapapi.utils.CoordinateConverter;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;

import org.lovebing.reactnative.baidumap.R;
import org.lovebing.reactnative.baidumap.battery.BatteryOptimizationHelper;
import org.lovebing.reactnative.baidumap.support.AppUtils;
import org.lovebing.reactnative.baidumap.util.JsonUtils;
import org.lovebing.reactnative.baidumap.util.LatLngUtil;
import org.lovebing.reactnative.baidumap.util.NotificationUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Created by lovebing on 2016/10/28.
 */
public class GeolocationModule extends BaseModule
        implements OnGetGeoCoderResultListener {

    private LocationClient locationClient;
    private static GeoCoder geoCoder;
    private volatile boolean locating = false;
    private volatile boolean locateOnce = false;
    private BDAbstractLocationListener myLocationListener;
    private Notification mNotification;
    private String taskId = null;
    private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = null;
    private boolean isBackend = false;
    private final String taskDir = "location";

    public GeolocationModule(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
        this.initNotification();
        activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(@NonNull Activity activity) {

            }

            @Override
            public void onActivityResumed(@NonNull Activity activity) {
                isBackend = false;
            }

            @Override
            public void onActivityPaused(@NonNull Activity activity) {
                isBackend = true;
            }

            @Override
            public void onActivityStopped(@NonNull Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(@NonNull Activity activity) {

            }
        };
    }

    public String getName() {
        return "BaiduGeolocationModule";
    }

    private void initLocationClient(String coorType) {
        if (context.getCurrentActivity() != null) {
            AppUtils.checkPermission(context.getCurrentActivity(),
                    Manifest.permission.ACCESS_FINE_LOCATION);
        }
        LocationClientOption option = new LocationClientOption();
        option.setLocationMode(LocationMode.Hight_Accuracy);
        option.setCoorType(coorType);
        option.setIsNeedAddress(true);
        option.setIsNeedAltitude(true);
        option.setScanSpan(5000); //243添加
        option.setIsNeedLocationDescribe(true);
        option.setOpenGps(true);

        try {
            LocationClient.setAgreePrivacy(true);
            locationClient = new LocationClient(context.getApplicationContext());
            locationClient.setLocOption(option);
            myLocationListener = new BDAbstractLocationListener() {
                @Override
                public void onReceiveLocation(BDLocation bdLocation) {
                    WritableMap params = Arguments.createMap();
                    params.putDouble("latitude", bdLocation.getLatitude());
                    params.putDouble("longitude", bdLocation.getLongitude());
                    params.putDouble("speed", bdLocation.getSpeed());
                    params.putDouble("direction", bdLocation.getDirection());
                    params.putDouble("altitude", bdLocation.getAltitude());
                    params.putDouble("radius", bdLocation.getRadius());
                    params.putString("address", bdLocation.getAddrStr());
                    params.putString("countryCode", bdLocation.getCountryCode());
                    params.putString("country", bdLocation.getCountry());
                    params.putString("province", bdLocation.getProvince());
                    params.putString("cityCode", bdLocation.getCityCode());
                    params.putString("city", bdLocation.getCity());
                    params.putString("district", bdLocation.getDistrict());
                    params.putString("street", bdLocation.getStreet());
                    params.putString("streetNumber", bdLocation.getStreetNumber());
                    params.putString("buildingId", bdLocation.getBuildingID());
                    params.putString("buildingName", bdLocation.getBuildingName());
                    params.putInt("locType", bdLocation.getLocType()); //243添加
                    params.putString("locationDescribe", bdLocation.getLocationDescribe()); //243添加
                    params.putString("town", bdLocation.getTown()); //243添加
                    params.putString("floor", bdLocation.getFloor()); //243添加
                    params.putString("time", bdLocation.getTime());
                    // 此定位点作弊概率，3代表高概率，2代表中概率，1代表低概率，0代表概率为0
                    params.putInt("mockGpsProbability", bdLocation.getMockGpsProbability()); //243添加
                    // 防作弊策略识别码，用于辅助分析排查问题
                    params.putInt("mockGpsStrategy", bdLocation.getMockGpsStrategy()); //243添加
                    BDLocation realLoc = bdLocation.getReallLocation();
                    if (bdLocation.getMockGpsStrategy() > 0 && null != realLoc) {
                        double dis = bdLocation.getDisToRealLocation(); // 虚假位置和真实位置之间的距离
                        params.putDouble("disToRealLocation", dis); //243添加
                        int realLocType = realLoc.getLocType(); // 真实定位结果类型
                        params.putInt("realLocType", realLocType); //243添加
                        String realLocTime = realLoc.getTime(); // 真实位置定位时间

                        double realLat = realLoc.getLatitude(); // 真实纬度
                        params.putDouble("realLatitude", realLat); //243添加
                        double realLng = realLoc.getLongitude();  // 真实经度
                        params.putDouble("realLongitude", realLng); //243添加
                        String realLocCoorType = realLoc.getCoorType(); // 真实位置坐标系

                    }

                    if (locateOnce) {
                        locating = false;
                        sendEvent("onGetCurrentLocationPosition", params);
                        locationClient.stop();
                        locationClient = null;
                    } else {
                        if (isBackend && taskId != null) {
                            File filesDir = getReactApplicationContext().getFilesDir();
                            File locationDir = new File(filesDir, "location");
                            if (!locationDir.exists()) {
                                boolean dirCreated = locationDir.mkdirs();
                                if (!dirCreated) {
                                    Log.e("GeolocationModule", "创建目录失败: " + locationDir.getAbsolutePath());
                                    return;
                                }
                            }
                            File file = new File(locationDir, taskId);
                            try {
                                FileOutputStream fos = new FileOutputStream(file, true);
                                OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
                                BufferedWriter bw = new BufferedWriter(osw);
                                bw.write(JsonUtils.writableMapToJsonString(params));
                                bw.newLine();
                                bw.flush();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        } else
                            sendEvent("onLocationUpdate", params);
                    }
                }
            };
            locationClient.registerLocationListener(myLocationListener);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @return
     */
    protected GeoCoder getGeoCoder() {
        if (geoCoder != null) {
            geoCoder.destroy();
        }
        geoCoder = GeoCoder.newInstance();
        geoCoder.setOnGetGeoCodeResultListener(this);
        return geoCoder;
    }

    /**
     * @param sourceLatLng
     * @return
     */
    protected LatLng getBaiduCoorFromGPSCoor(LatLng sourceLatLng) {
        CoordinateConverter converter = new CoordinateConverter();
        converter.from(CoordinateConverter.CoordType.GPS);
        converter.coord(sourceLatLng);
        LatLng desLatLng = converter.convert();
        return desLatLng;

    }

    @ReactMethod
    public void convertGPSCoor(double lat, double lng, Promise promise) {
        Log.i("convertGPSCoor", "convertGPSCoor");
        LatLng latLng = getBaiduCoorFromGPSCoor(new LatLng(lat, lng));
        WritableMap map = Arguments.createMap();
        map.putDouble("latitude", latLng.latitude);
        map.putDouble("longitude", latLng.longitude);
        promise.resolve(map);
    }

    @ReactMethod
    public void getCurrentPosition(String coorType) {
        if (locating) {
            return;
        }
        locateOnce = true;
        locating = true;
        if (locationClient == null) {
            initLocationClient(coorType);
        }
        Log.i("getCurrentPosition", "getCurrentPosition");
        locationClient.start();
    }

    @ReactMethod
    public void startLocating(String coorType) {
        if (locating) {
            return;
        }
        locateOnce = false;
        locating = true;
        requestPermission();
        initLocationClient(coorType);
        // 将定位SDK的SERVICE设置成为前台服务, 提高定位进程存活率
        locationClient.enableLocInForeground(1, mNotification);
        locationClient.start();
        ((Application) getReactApplicationContext().getApplicationContext()).registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
    }

    @ReactMethod
    public void stopLocating() {
        locating = false;
        if (locationClient != null) {
            locationClient.unRegisterLocationListener(myLocationListener);
            locationClient.disableLocInForeground(true);
            locationClient.stop();
            locationClient = null;
            taskId = null;
            ((Application) getReactApplicationContext().getApplicationContext()).unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
        }
    }

    /**
     * 初始化前台服务
     */
    private void initNotification() {
        //设置后台定位
        //android8.0及以上使用NotificationUtils
        if (Build.VERSION.SDK_INT >= 26) {
            NotificationUtils notificationUtils = new NotificationUtils(context);
            Notification.Builder builder = notificationUtils.getAndroidChannelNotification
                    ("后台定位功能", "正在后台定位");
            mNotification = builder.build();
        } else {
            //获取一个Notification构造器
            Notification.Builder builder = new Notification.Builder(context);
            Intent nfIntent = new Intent(context, Objects.requireNonNull(getCurrentActivity()).getClass());

            builder.setContentIntent(PendingIntent.
                            getActivity(context, 0, nfIntent, 0)) // 设置PendingIntent
                    .setContentTitle("后台定位功能") // 设置下拉列表里的标题
                    .setSmallIcon(R.mipmap.icon_gcoding) // 设置状态栏内的小图标
                    .setContentText("正在后台定位") // 设置上下文内容
                    .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
            mNotification = builder.build(); // 获取构建好的Notification
        }
        mNotification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
    }

    @ReactMethod
    public void geocode(String city, String addr) {
        getGeoCoder().geocode(new GeoCodeOption()
                .city(city).address(addr));
    }

    @ReactMethod
    public void reverseGeoCode(double lat, double lng) {
        getGeoCoder().reverseGeoCode(new ReverseGeoCodeOption()
                .location(new LatLng(lat, lng)));
    }

    @ReactMethod
    public void reverseGeoCodeGPS(double lat, double lng) {
        getGeoCoder().reverseGeoCode(new ReverseGeoCodeOption()
                .location(getBaiduCoorFromGPSCoor(new LatLng(lat, lng))));
    }

//    @Override
//    public void onReceiveLocation(BDLocation bdLocation) {
//        WritableMap params = Arguments.createMap();
//        params.putDouble("latitude", bdLocation.getLatitude());
//        params.putDouble("longitude", bdLocation.getLongitude());
//        params.putDouble("speed", bdLocation.getSpeed());
//        params.putDouble("direction", bdLocation.getDirection());
//        params.putDouble("altitude", bdLocation.getAltitude());
//        params.putDouble("radius", bdLocation.getRadius());
//        params.putString("address", bdLocation.getAddrStr());
//        params.putString("countryCode", bdLocation.getCountryCode());
//        params.putString("country", bdLocation.getCountry());
//        params.putString("province", bdLocation.getProvince());
//        params.putString("cityCode", bdLocation.getCityCode());
//        params.putString("city", bdLocation.getCity());
//        params.putString("district", bdLocation.getDistrict());
//        params.putString("street", bdLocation.getStreet());
//        params.putString("streetNumber", bdLocation.getStreetNumber());
//        params.putString("buildingId", bdLocation.getBuildingID());
//        params.putString("buildingName", bdLocation.getBuildingName());
//        params.putInt("locType", bdLocation.getLocType()); //243添加
//        params.putString("locationDescribe", bdLocation.getLocationDescribe()); //243添加
//        params.putString("town", bdLocation.getTown()); //243添加
//        params.putString("floor", bdLocation.getFloor()); //243添加
//        Log.i("onReceiveLocation", "onGetCurrentLocationPosition");
//
//        if (locateOnce) {
//            locating = false;
//            sendEvent("onGetCurrentLocationPosition", params);
//            locationClient.stop();
//            locationClient = null;
//        } else {
//            sendEvent("onLocationUpdate", params);
//        }
//    }

    @Override
    public void onGetGeoCodeResult(GeoCodeResult result) {
        WritableMap params = Arguments.createMap();
        if (result == null || result.error != SearchResult.ERRORNO.NO_ERROR) {
            params.putInt("errcode", -1);
            params.putString("errmsg", result.error.name());
        } else {
            params.putDouble("latitude", result.getLocation().latitude);
            params.putDouble("longitude", result.getLocation().longitude);
        }
        sendEvent("onGetGeoCodeResult", params);
    }

    @Override
    public void onGetReverseGeoCodeResult(ReverseGeoCodeResult result) {
        WritableMap params = Arguments.createMap();
        if (result == null || result.error != SearchResult.ERRORNO.NO_ERROR) {
            params.putInt("errcode", -1);
        } else {
            ReverseGeoCodeResult.AddressComponent addressComponent = result.getAddressDetail();
            params.putString("address", result.getAddress());
            params.putString("province", addressComponent.province);
            params.putString("city", addressComponent.city);
            params.putString("district", addressComponent.district);
            params.putString("street", addressComponent.street);
            params.putString("streetNumber", addressComponent.streetNumber);

            WritableArray list = Arguments.createArray();
            List<PoiInfo> poiList = result.getPoiList();
            for (PoiInfo info : poiList) {
                WritableMap attr = Arguments.createMap();
                attr.putString("name", info.name);
                attr.putString("address", info.address);
                attr.putString("city", info.city);
                attr.putDouble("latitude", info.location.latitude);
                attr.putDouble("longitude", info.location.longitude);
                list.pushMap(attr);
            }
            params.putArray("poiList", list);
        }
        sendEvent("onGetReverseGeoCodeResult", params);
    }


    @ReactMethod
    public void setCoordType(String coordType) {
        //自4.3.0起，百度地图SDK所有接口均支持百度坐标和国测局坐标，用此方法设置您使用的坐标类型.
        //包括BD09LL和GCJ02两种坐标，默认是BD09LL坐标。
        SDKInitializer.setCoordType(CoordType.valueOf(coordType.toUpperCase()));
    }

    @ReactMethod
    public void getCoordType(Promise promise) {
        promise.resolve(SDKInitializer.getCoordType().name());//BD09LL或者GCJ02坐标
    }

    @ReactMethod
    public void convertCoordinate(String from, ReadableMap sourceLatLng, Promise promise) {
        //初始化左边转换工具类，指定源坐标类型和坐标数据
        //sourceLatLng 待转换坐标
        CoordinateConverter converter = new CoordinateConverter()
                .from(CoordinateConverter.CoordType.valueOf(from.toUpperCase()))
                .coord(LatLngUtil.fromReadableMap(sourceLatLng));
        //转换坐标
        LatLng desLatLng = converter.convert();
        WritableMap writableMap = Arguments.createMap();
        writableMap.putDouble("latitude", desLatLng.latitude);
        writableMap.putDouble("longitude", desLatLng.longitude);
        promise.resolve(writableMap);
    }

    @Override
    public void onCatalystInstanceDestroy() {
        super.onCatalystInstanceDestroy();
        stopLocating();
    }


    /**
     * Android6.0之后需要动态申请权限
     */
    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            ArrayList<String> permissionsList = new ArrayList<>();
            String[] permissions = {
                    Manifest.permission.ACCESS_NETWORK_STATE,
                    Manifest.permission.INTERNET,
                    Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_WIFI_STATE,
                    Manifest.permission.WRITE_SETTINGS,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION,
            };

            for (String perm : permissions) {
                if (getCurrentActivity() != null && PackageManager.PERMISSION_GRANTED != getCurrentActivity().checkSelfPermission(perm)) {
                    permissionsList.add(perm);
                    // 进入到这里代表没有权限.
                }
            }

            if (getCurrentActivity() != null && !permissionsList.isEmpty()) {
                String[] strings = new String[permissionsList.size()];
                getCurrentActivity().requestPermissions(permissionsList.toArray(strings), 0);
            }
        }
    }

    @ReactMethod
    public void startBackendLocating(ReadableMap options, String uid) {
        if (!BatteryOptimizationHelper.isIgnoringBatteryOptimizations(Objects.requireNonNull(getCurrentActivity()))) {
            BatteryOptimizationHelper.requestIgnoreBatteryOptimizations(getCurrentActivity(), 1001);
        }
        taskId = uid;
        String coorType = options.getString("coorType");
        startLocating(coorType);
    }

    @ReactMethod
    public void getBackendLocations(String uid, Promise promise) {
        WritableArray writableArray = Arguments.createArray();
        File file = new File(context.getFilesDir() + "/" + taskDir, uid);
        try {
            FileInputStream fis = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);
            BufferedReader br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null) {
                writableArray.pushMap(JsonUtils.jsonStringToWritableMap(line));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        promise.resolve(writableArray);
    }

    @ReactMethod
    public void cleanBackendLocations(String uid) {
        File file = new File(context.getFilesDir() + "/" + taskDir, uid);
        if (file.exists()) {
            boolean result = file.delete();
        }
    }
}
