package com.ctpodp;

import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;

import com.ctpodp.objects.fromReact.CenterMapOnPathObj;
import com.ctpodp.objects.fromReact.CenterMapOnPoint;
import com.ctpodp.objects.fromReact.CenterMapOnUserObj;
import com.ctpodp.objects.fromReact.DrawNavigationPathObj;
import com.ctpodp.objects.fromReact.HighlightUnitObj;
import com.ctpodp.objects.fromReact.MapCredentials;
import com.ctpodp.objects.fromReact.MapImageObj;
import com.ctpodp.objects.fromReact.ObjectStyle;
import com.ctpodp.objects.fromReact.SetFollowBlueDotObj;
import com.ctpodp.objects.fromReact.SetMapVenueObj;
import com.ctpodp.objects.fromReact.SetMapViewBoundsObj;
import com.ctpodp.objects.fromReact.SetUserPositionObj;
import com.ctpodp.objects.fromReact.WayfindBetweenWaypointsObj;
import com.ctpodp.objects.fromReact.WayfindToWaypointObj;
import com.ctpodp.objects.fromReact.mapOptions.MapOptionsObj;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.google.gson.Gson;
import com.jibestream.jmapandroidsdk.rendering_engine.MapView;

import java.util.HashMap;
import java.util.Map;

import kotlin.NotImplementedError;

public class MapViewManager extends ViewGroupManager<RelativeLayout> implements MapView.OnTapListener {
    public static final String REACT_CLASS = "MapViewManager";

    public final int COMMAND_CREATE = 1;
    public final int COMMAND_CENTER = 2;
    public final int COMMAND_SET_MAP_VENUE = 3;
    public final int COMMAND_CENTER_MAP_ON_POINT = 4;
    public final int COMMAND_SET_FOLLOW_BLUE_DOT = 5;
    public final int COMMAND_SET_USER_POSITION = 6;
    public final int COMMAND_SET_USER_POSITION_XY = 7;
    public final int COMMAND_CLEAR_WAYFINDING_PATH = 8;
    public final int COMMAND_WAYFIND_TO_WAYPOINT = 9;
    public final int COMMAND_WAYFIND_BETWEEN_WAYPOINTS = 10;
    public final int COMMAND_DRAW_NAVIGATION_PATH = 11;
    public final int COMMAND_STYLE_UNITS_FOR_DESTINATION = 12;
    public final int COMMAND_STYLE_SHAPE = 13;
    public final int COMMAND_ADD_MAP_ICON = 14;
  public final int COMMAND_START_ODP = 15;
  public final int COMMAND_STOP_ODP = 16;
  public final int COMMAND_UPLOAD_ODP_LOGS = 17;
  public final int COMMAND_SET_MAP_VIEW_BOUNDS = 18;
  public final int COMMAND_CENTER_MAP_ON_PATH = 19;

    private int propWidth;
    private int propHeight;

    MapFragment mapFragment;

  ThemedReactContext reactContext;

    public MapViewManager(ReactApplicationContext reactContext) {
      Thread.setDefaultUncaughtExceptionHandler(new MapCanvasCrashHandler());
    }

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

    /**
     * Return a RelativeLayout which will later hold the Fragment
     */
    @Override
    public RelativeLayout createViewInstance(ThemedReactContext reactContext) {
      this.reactContext = reactContext;
        return new RelativeLayout(reactContext);
    }

  @Override
  protected RelativeLayout prepareToRecycleView(@NonNull ThemedReactContext reactContext, RelativeLayout view) {
      Log.d("STATE_CHANGE", "prepareToRecycleView");
    return super.prepareToRecycleView(reactContext, view);
  }

  @Override
  public void onDropViewInstance(@NonNull RelativeLayout view) {
      mapFragment.removeMap();
      super.onDropViewInstance(view);
  }

  /**
     * Map the "create" command to an integer
     */
    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return new HashMap<String, Integer>() {
            {
                put("create", COMMAND_CREATE);
                put("center", COMMAND_CENTER);
                put("setMapVenue", COMMAND_SET_MAP_VENUE);
                put("centerMapOnPoint", COMMAND_CENTER_MAP_ON_POINT);
                put("setFollowBlueDot", COMMAND_SET_FOLLOW_BLUE_DOT);
                put("setUserPosition", COMMAND_SET_USER_POSITION);
                put("setUserPositionXY", COMMAND_SET_USER_POSITION_XY);
                put("clearWayfindingPath", COMMAND_CLEAR_WAYFINDING_PATH);
                put("wayfindToWaypoint", COMMAND_WAYFIND_TO_WAYPOINT);
                put("wayfindBetweenWaypoints", COMMAND_WAYFIND_BETWEEN_WAYPOINTS);
                put("drawNavigationPath", COMMAND_DRAW_NAVIGATION_PATH);
                put("styleUnitsForDestination", COMMAND_STYLE_UNITS_FOR_DESTINATION);
                put("styleShape", COMMAND_STYLE_SHAPE);
                put("addMapIcon", COMMAND_ADD_MAP_ICON);
                put("startOdp", COMMAND_START_ODP);
                put("stopOdp", COMMAND_STOP_ODP);
                put("uploadLogs", COMMAND_UPLOAD_ODP_LOGS);
                put("commandSetMapViewBounds", COMMAND_SET_MAP_VIEW_BOUNDS);
                put("centerMapOnPath", COMMAND_CENTER_MAP_ON_PATH);
            }
        };
    }

    /**
     * Handle "create" command (called from JS) and call createFragment method
     */
    @Override
    public void receiveCommand(
            @NonNull RelativeLayout root,
            String commandId,
            @Nullable ReadableArray args) {
        super.receiveCommand(root, commandId, args);
        int commandIdInt = Integer.parseInt(commandId);
        Gson gson = new Gson();
        Log.d("Received Command", "Command " + commandId + ", " + args.toString());

        switch (commandIdInt) {
            case COMMAND_CREATE:
                createFragment(root, args.getInt(0));
                break;
            case COMMAND_CENTER:
              mapFragment.centerMapOnUser(gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(0)), CenterMapOnUserObj.class));
                break;
            case COMMAND_SET_MAP_VENUE:
              SetMapVenueObj setMapVenueObj = new SetMapVenueObj(
                gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(0)), MapCredentials.class),
                gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(1)), MapOptionsObj.class)
              );
                mapFragment.initializeMap(setMapVenueObj);
                break;
          case COMMAND_CENTER_MAP_ON_POINT:
            mapFragment.centerMapOnPoint(gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(0)), CenterMapOnPoint.class));
            break;
          case COMMAND_SET_FOLLOW_BLUE_DOT:
            SetFollowBlueDotObj setFollowBlueDotObj = new SetFollowBlueDotObj(
              args.getBoolean(0),
              args.getBoolean(1),
              args.getInt(2)
            );
            mapFragment.setFollowBlueDot(setFollowBlueDotObj);
            break;
          case COMMAND_SET_USER_POSITION:
            SetUserPositionObj setUserPositionObj = new SetUserPositionObj(
              args.getDouble(0),
              args.getDouble(1),
              args.getDouble(2),
              args.getDouble(3),
              args.getString(4)
            );
            mapFragment.setUserPosition(setUserPositionObj);
            break;
          case COMMAND_CLEAR_WAYFINDING_PATH:
            mapFragment.clearWayfindingPath();
            break;
          case COMMAND_WAYFIND_TO_WAYPOINT:
            WayfindToWaypointObj wayfindToWaypointObj = new WayfindToWaypointObj(
              args.getInt(0)
            );
            mapFragment.wayfindToWaypoint(wayfindToWaypointObj);
            break;
          case COMMAND_WAYFIND_BETWEEN_WAYPOINTS:
            WayfindBetweenWaypointsObj wayfindBetweenWaypointsObj = new WayfindBetweenWaypointsObj(
              args.getInt(0),
              args.getInt(1)
            );
            mapFragment.wayfindBetweenWaypoints(wayfindBetweenWaypointsObj);
            break;
          case COMMAND_DRAW_NAVIGATION_PATH:
            DrawNavigationPathObj drawNavigationPathObj = new DrawNavigationPathObj(
              args.getString(0),
              args.getBoolean(1),
              gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(2)), ObjectStyle.class),
              gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(3)), MapImageObj.class),
              gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(4)), MapImageObj.class)
            );
            mapFragment.drawWayfindingPath(drawNavigationPathObj);
            break;
          case COMMAND_STYLE_UNITS_FOR_DESTINATION:
            HighlightUnitObj highlightUnitObj = new HighlightUnitObj(
              args.getInt(0),
              gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(1)), ObjectStyle.class)
            );
            mapFragment.styleUnitForDestination(highlightUnitObj);
            break;
          case COMMAND_ADD_MAP_ICON:
          case COMMAND_START_ODP:
          case COMMAND_STOP_ODP:
          case COMMAND_UPLOAD_ODP_LOGS:
              throw new NotImplementedError();
          case COMMAND_SET_MAP_VIEW_BOUNDS:
            SetMapViewBoundsObj setMapViewBoundsObj = gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(0)), SetMapViewBoundsObj.class);
            mapFragment.setMapViewBounds(setMapViewBoundsObj);
            break;
          case COMMAND_CENTER_MAP_ON_PATH:
            CenterMapOnPathObj centerMapOnPathObj = gson.fromJson(ReactUtilities.mapToJsonString(args.getMap(0)), CenterMapOnPathObj.class);
            mapFragment.centerMapOnPath(centerMapOnPathObj);
            break;
          default: {

            }
        }
    }

    @ReactPropGroup(names = { "width", "height" }, customType = "Style")
    public void setStyle(RelativeLayout view, int index, Integer value) {
        if (index == 0) {
            propWidth = value;
        }

        if (index == 1) {
            propHeight = value;
        }
    }


    /**
     * Replace your React Native view with a custom fragment
     */
    public void createFragment(RelativeLayout root, int reactNativeViewId) {
        ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId);
        setupLayout(parentView);

        mapFragment = new MapFragment(reactContext);
        FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
        activity.getSupportFragmentManager()
                .beginTransaction()
                .replace(reactNativeViewId, mapFragment, String.valueOf(reactNativeViewId))
                .commit();
    }

    public void setupLayout(View view) {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                manuallyLayoutChildren(view);
                view.getViewTreeObserver().dispatchOnGlobalLayout();
                Choreographer.getInstance().postFrameCallback(this);
            }
        });
    }

    /**
     * Layout all children properly
     */
    public void manuallyLayoutChildren(View view) {
        // propWidth and propHeight coming from react-native props
        int width = propWidth;
        int height = propHeight;

        view.measure(
                View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

        view.layout(0, 0, width, height);
    }

  @Override
  public void onTap(MotionEvent motionEvent, Object o) {
    Log.d("EVENT onTap", motionEvent.toString());
  }
}
