package com.laoqiu.amap;

import android.os.StrictMode;
import android.location.Location;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.os.Environment;
import android.net.Uri;
import java.text.SimpleDateFormat;

import com.amap.api.maps.AMap;
import com.amap.api.maps.AMapOptions;
import com.amap.api.maps.MapView;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.Projection;
import com.amap.api.maps.model.CameraPosition;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.LatLngBounds;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.Polyline;
import com.amap.api.maps.model.BitmapDescriptor;
import com.amap.api.maps.model.BitmapDescriptorFactory;
import com.amap.api.maps.model.PolylineOptions;
import com.amap.api.maps.model.PolygonOptions;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.Callback;
//import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.common.MapBuilder;
import javax.annotation.Nullable;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.Date;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;


public class AMapViewManager extends ViewGroupManager<MapView> {
    public static final String RCT_CLASS = "AMapView";

    private MapView mapView;
    private AMap map;
    private ThemedReactContext context;
    private EventDispatcher eventDispatcher;
    private LatLngBounds boundsToMove;

    //private final AMap map;
    private final ReactApplicationContext appContext;
    private final Map<Marker, AMapMarker> markerMap = new HashMap<>();
    private final Map<Polyline, AMapPolyline> polylineMap = new HashMap<>();
    private final List<AMapFeature> features = new ArrayList<>();

    public static final int ANIMATE_TO_REGION = 1;
    public static final int GET_MAP_SCREENSHOT = 2;
    private final Map<String, Integer> MAP_TYPES = MapBuilder.of(
            "standard", AMap.MAP_TYPE_NORMAL,
            "satellite", AMap.MAP_TYPE_SATELLITE,
            "night", AMap.MAP_TYPE_NIGHT,
            "navi", AMap.MAP_TYPE_NAVI,
            "none", AMap.MAP_TYPE_BUS
    );

    public AMapViewManager(ReactApplicationContext context) {
        this.appContext = context;
    }

    @Override
    public String getName() {
        return RCT_CLASS;
    }

    @Override
    protected void addEventEmitters(ThemedReactContext content, final MapView mapView) {
        final AMap map = mapView.getMap();
        map.setOnCameraChangeListener(new AMap.OnCameraChangeListener() {
            @Override
            public void onCameraChange(CameraPosition position) {
                WritableMap event = Arguments.createMap();
                WritableMap region = Arguments.createMap();
                LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
                region.putDouble("latitude", position.target.latitude);
                region.putDouble("longitude", position.target.longitude);
                region.putDouble("latitudeDelta", bounds.northeast.latitude - bounds.southwest.latitude);
                region.putDouble("longitudeDelta", bounds.northeast.longitude - bounds.southwest.longitude);
                event.putInt("continuous", 1);
                event.putMap("region", region);
                ReactContext reactContext = (ReactContext) mapView.getContext();
                pushEvent(reactContext, mapView, "topChange", event);
            }
            @Override
            public void onCameraChangeFinish(CameraPosition position) {
                WritableMap event = Arguments.createMap();
                WritableMap region = Arguments.createMap();
                LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
                region.putDouble("latitude", position.target.latitude);
                region.putDouble("longitude", position.target.longitude);
                region.putDouble("latitudeDelta", bounds.northeast.latitude - bounds.southwest.latitude);
                region.putDouble("longitudeDelta", bounds.northeast.longitude - bounds.southwest.longitude);
                event.putInt("continuous", 0);
                event.putMap("region", region);
                ReactContext reactContext = (ReactContext) mapView.getContext();
                pushEvent(reactContext, mapView, "topChange", event);
            }
        });
        map.setOnMapClickListener(new AMap.OnMapClickListener() {
            @Override
            public void onMapClick(LatLng point) {
                WritableMap event = Arguments.createMap();
                event.putDouble("latitude", point.latitude);
                event.putDouble("longitude", point.longitude);
                ReactContext reactContext = (ReactContext) mapView.getContext();
                pushEvent(reactContext, mapView, "onMapClick", event);
            }
        });
        map.setOnMarkerClickListener(new AMap.OnMarkerClickListener() {
            @Override
            public boolean onMarkerClick(Marker marker) {
                WritableMap event;
                AMapMarker markerView = markerMap.get(marker);
                ReactContext reactContext = (ReactContext) mapView.getContext();

                event = makeClickEventData(marker.getPosition());
                event.putString("action", "marker-press");
                event.putString("id", markerView.getIdentifier());
                pushEvent(reactContext, mapView, "onMarkerPress", event);

                event = makeClickEventData(marker.getPosition());
                event.putString("action", "marker-press");
                event.putString("id", markerView.getIdentifier());
                pushEvent(reactContext, markerMap.get(marker), "onPress", event);
                return true;
            }
        });
        //map.setOnMyLocationChangeListener(new AMap.OnMyLocationChangeListener() {
        //    @Override
        //    public void onMyLocationChange(Location location) {
        //        if (location != null) {
        //            WritableMap event = Arguments.createMap();
        //            event.putDouble("latitude", location.getLatitude());
        //            event.putDouble("longitude", location.getLongitude());
        //            event.putDouble("accuracy", (double) location.getAccuracy());
        //            ReactContext reactContext = (ReactContext) mapView.getContext();
        //            pushEvent(reactContext, mapView, "onMyLocationChange", event);
        //        }
        //    }
        //});
    }

    @Nullable
    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("onMapClick", MapBuilder.of("registrationName", "onMapClick"))
                .put("onMarkerPress", MapBuilder.of("registrationName", "onMarkerPress"))
                //.put("onMyLocationChange", MapBuilder.of("registrationName", "onMyLocationChange"))
                .put("onMapScreenShot", MapBuilder.of("registrationName", "onMapScreenShot"))
                .build();
    }

    @Override
    public @Nullable Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(
            "animateToRegion", ANIMATE_TO_REGION,
            "getMapScreenShot", GET_MAP_SCREENSHOT
        );
    }

    @Override
    public void receiveCommand(final MapView mapView, int commandId, @Nullable ReadableArray args) {
        switch (commandId){
            case ANIMATE_TO_REGION:
                ReadableMap region = args.getMap(0);
                Double lat = region.getDouble("latitude");
                Double lng = region.getDouble("longitude");
                Double lngDelta = region.getDouble("longitudeDelta");
                Double latDelta = region.getDouble("latitudeDelta");
                LatLngBounds bounds = new LatLngBounds(
                  new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
                  new LatLng(lat + latDelta / 2, lng + lngDelta / 2)  // northeast
                );
                mapView.getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
                return;
            case GET_MAP_SCREENSHOT:
                mapView.getMap().getMapScreenShot(new AMap.OnMapScreenShotListener() {
                    @Override
                    public void onMapScreenShot(Bitmap snapshot) {
                    }
                    @Override
                    public void onMapScreenShot(Bitmap snapshot, int status) {
                        if(snapshot == null){
                            return;
                        }
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
                        File tempFile;
                        FileOutputStream fos;
                        try {
                            tempFile = new File(appContext.getCacheDir(), "higo_"+sdf.format(new Date()) + ".png");
                            fos = new FileOutputStream(tempFile);
                            boolean b = snapshot.compress(Bitmap.CompressFormat.PNG, 100, fos);
                            try {
                                fos.flush();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            try {
                                fos.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            if (b) {
                                // 返回数据给前端js
                                String uri = Uri.fromFile(tempFile).toString();
                                WritableMap event = Arguments.createMap();
                                event.putString("uri", uri);
                                ReactContext reactContext = (ReactContext) mapView.getContext();
                                pushEvent(reactContext, mapView, "onMapScreenShot", event);
                            }
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                });
                return;
        }
    }

    @Override
    protected MapView createViewInstance(ThemedReactContext reactContent) {
        AMapOptions options = new AMapOptions();
        options.zoomControlsEnabled(false);
        //options.zoomGesturesEnabled(false);
        //options.rotateGesturesEnabled(false);
        mapView = new MapView(reactContent, options);
        mapView.onCreate(null);
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
        map = mapView.getMap();
        eventDispatcher = reactContent.getNativeModule(UIManagerModule.class).getEventDispatcher();
        return mapView;
    }

    @ReactProp(name = "mapType")
    public void setMapType(MapView mapView, @Nullable String mapType) {
        int typeId = MAP_TYPES.get(mapType);
        mapView.getMap().setMapType(typeId);
    }

    @ReactProp(name = "region")
    public void setRegion(MapView mapView, @Nullable ReadableMap region) {
        if (region == null) return;
        AMap map = mapView.getMap();
        Double lat = region.getDouble("latitude");
        Double lng = region.getDouble("longitude");
        Double lngDelta = region.getDouble("longitudeDelta");
        Double latDelta = region.getDouble("latitudeDelta");
        LatLngBounds bounds = new LatLngBounds(
            new LatLng(lat - latDelta / 2, lng - lngDelta / 2), // southwest
            new LatLng(lat + latDelta / 2, lng + lngDelta / 2)  // northeast
        );
        if (mapView.getHeight() <= 0 || mapView.getWidth() <= 0) {
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lng), 10));
            boundsToMove = bounds;
        } else {
            map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
        }
    }

    @Override
    public void addView(MapView parent, View child, int index) {
        this.addFeature(parent, child, index);
    }

    public void addFeature(MapView parent, View child, int index) {
        AMap map = parent.getMap();
        if (child instanceof AMapMarker) {
            AMapMarker annotation = (AMapMarker) child;
            annotation.addToMap(map);
            features.add(index, annotation);
            Marker marker = (Marker) annotation.getFeature();
            markerMap.put(marker, annotation);
        } else if (child instanceof AMapPolyline) {
            AMapPolyline polylineView = (AMapPolyline) child;
            polylineView.addToMap(map);
            features.add(index, polylineView);
            Polyline polyline = (Polyline) polylineView.getFeature();
            polylineMap.put(polyline, polylineView);
        } else {
            ViewGroup children = (ViewGroup) child;
            for (int i = 0; i < children.getChildCount(); i++) {
              addFeature(parent, children.getChildAt(i), index);
            }
        }
    }

    @Override
    public int getChildCount(MapView view) {
        return features.size();
    }

    @Override
    public View getChildAt(MapView view, int index) {
        return features.get(index);
    }

    @Override
    public void removeViewAt(MapView parent, int index) {
        AMap map = parent.getMap();
        AMapFeature feature = features.remove(index);
        if (feature instanceof AMapMarker) {
            markerMap.remove(feature.getFeature());
        } else if (feature instanceof AMapPolyline) {
            polylineMap.remove(feature.getFeature());
        }
        feature.removeFromMap(map);
    }

    @Override
    public void updateExtraData(MapView view, Object extraData) {
        // if boundsToMove is not null, we now have the MapView's width/height, so we can apply
        // a proper camera move
        if (boundsToMove != null) {
          HashMap<String, Float> data = (HashMap<String, Float>) extraData;
          float width = data.get("width");
          float height = data.get("height");
          view.getMap().moveCamera(
              CameraUpdateFactory.newLatLngBounds(
                  boundsToMove,
                  (int) width,
                  (int) height,
                  0
              )
          );
          boundsToMove = null;
        }
    }

    public void pushEvent(ReactContext context, View view, String name, WritableMap data) {
        context.getJSModule(RCTEventEmitter.class)
            .receiveEvent(view.getId(), name, data);
    }

    @Override
    public void onDropViewInstance(MapView mapView) {
        mapView.onDestroy();
    }

    public WritableMap makeClickEventData(LatLng point) {
        WritableMap event = new WritableNativeMap();

        WritableMap coordinate = new WritableNativeMap();
        coordinate.putDouble("latitude", point.latitude);
        coordinate.putDouble("longitude", point.longitude);
        event.putMap("coordinate", coordinate);

        Projection projection = map.getProjection();
        Point screenPoint = projection.toScreenLocation(point);

        WritableMap position = new WritableNativeMap();
        position.putDouble("x", screenPoint.x);
        position.putDouble("y", screenPoint.y);
        event.putMap("position", position);

        return event;
    }


}