package com.mapbox.mapboxsdk.plugins.annotation;

import android.annotation.SuppressLint;
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.View;

import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.gestures.MoveDistancesObject;
import com.mapbox.android.gestures.MoveGestureDetector;
import com.mapbox.geojson.Geometry;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;

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

final class DraggableAnnotationController<T extends Annotation, D extends OnAnnotationDragListener<T>> {
  private final MapboxMap mapboxMap;
  private AnnotationManager<?, T, ?, D, ?, ?> annotationManager;

  private final int touchAreaShiftX;
  private final int touchAreaShiftY;
  private final int touchAreaMaxX;
  private final int touchAreaMaxY;

  @Nullable
  private T draggedAnnotation;

  @SuppressLint("ClickableViewAccessibility")
  DraggableAnnotationController(MapView mapView, MapboxMap mapboxMap) {
    this(mapView, mapboxMap, new AndroidGesturesManager(mapView.getContext(), false),
      mapView.getScrollX(), mapView.getScrollY(), mapView.getMeasuredWidth(), mapView.getMeasuredHeight());
  }

  @VisibleForTesting
  public DraggableAnnotationController(MapView mapView, MapboxMap mapboxMap,
                                       final AndroidGesturesManager androidGesturesManager,
                                       int touchAreaShiftX, int touchAreaShiftY,
                                       int touchAreaMaxX, int touchAreaMaxY) {
    this.mapboxMap = mapboxMap;
    this.touchAreaShiftX = touchAreaShiftX;
    this.touchAreaShiftY = touchAreaShiftY;
    this.touchAreaMaxX = touchAreaMaxX;
    this.touchAreaMaxY = touchAreaMaxY;

    androidGesturesManager.setMoveGestureListener(new AnnotationMoveGestureListener());

    mapView.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        androidGesturesManager.onTouchEvent(event);
        // if drag is started, don't pass motion events further
        return draggedAnnotation != null;
      }
    });
  }

  void injectAnnotationManager(AnnotationManager<?, T, ?, D, ?, ?> annotationManager) {
    this.annotationManager = annotationManager;
  }

  void onSourceUpdated() {
    stopDragging(draggedAnnotation);
  }

  boolean onMoveBegin(MoveGestureDetector detector) {
    if (detector.getPointersCount() == 1) {
      T annotation = annotationManager.queryMapForFeatures(detector.getFocalPoint());
      if (annotation != null) {
        return startDragging(annotation);
      }
    }
    return false;
  }

  boolean onMove(MoveGestureDetector detector) {
    if (draggedAnnotation != null && (detector.getPointersCount() > 1 || !draggedAnnotation.isDraggable())) {
      // Stopping the drag when we don't work with a simple, on-pointer move anymore
      stopDragging(draggedAnnotation);
      return true;
    }

    // Updating symbol's position
    if (draggedAnnotation != null) {
      MoveDistancesObject moveObject = detector.getMoveObject(0);

      float x = moveObject.getCurrentX() - touchAreaShiftX;
      float y = moveObject.getCurrentY() - touchAreaShiftY;

      PointF pointF = new PointF(x, y);

      if (pointF.x < 0 || pointF.y < 0 || pointF.x > touchAreaMaxX || pointF.y > touchAreaMaxY) {
        stopDragging(draggedAnnotation);
        return true;
      }

      Geometry shiftedGeometry = draggedAnnotation.getOffsetGeometry(
        mapboxMap.getProjection(), moveObject, touchAreaShiftX, touchAreaShiftY
      );

      if (shiftedGeometry != null) {
        draggedAnnotation.setGeometry(
          shiftedGeometry
        );
        annotationManager.internalUpdateSource();
        if (!annotationManager.getDragListeners().isEmpty()) {
          for (D d : annotationManager.getDragListeners()) {
            d.onAnnotationDrag(draggedAnnotation);
          }
        }
        return true;
      }
    }

    return false;
  }

  void onMoveEnd() {
    // Stopping the drag when move ends
    stopDragging(draggedAnnotation);
  }

  boolean startDragging(@NonNull T annotation) {
    if (annotation.isDraggable()) {
      if (!annotationManager.getDragListeners().isEmpty()) {
        for (D d : annotationManager.getDragListeners()) {
          d.onAnnotationDragStarted(annotation);
        }
      }
      draggedAnnotation = annotation;
      return true;
    }
    return false;
  }

  void stopDragging(@Nullable T annotation) {
    if (annotation != null) {
      if (!annotationManager.getDragListeners().isEmpty()) {
        for (D d : annotationManager.getDragListeners()) {
          d.onAnnotationDragFinished(annotation);
        }
      }
    }
    draggedAnnotation = null;
  }

  private class AnnotationMoveGestureListener implements MoveGestureDetector.OnMoveGestureListener {

    @Override
    public boolean onMoveBegin(MoveGestureDetector detector) {
      return DraggableAnnotationController.this.onMoveBegin(detector);
    }

    @Override
    public boolean onMove(MoveGestureDetector detector, float distanceX, float distanceY) {
      return DraggableAnnotationController.this.onMove(detector);
    }

    @Override
    public void onMoveEnd(MoveGestureDetector detector, float velocityX, float velocityY) {
      DraggableAnnotationController.this.onMoveEnd();
    }
  }
}