/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.uimanager.events;

import android.view.MotionEvent;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;

/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
public class TouchesHelper {

  public static final String TARGET_KEY = "target";
  public static final String CHANGED_TOUCHES_KEY = "changedTouches";
  public static final String TOUCHES_KEY = "touches";
  public static final String TOP_TOUCH_END_KEY = "topTouchEnd";
  public static final String TOP_TOUCH_CANCEL_KEY = "topTouchCancel";
  private static final String PAGE_X_KEY = "pageX";
  private static final String PAGE_Y_KEY = "pageY";
  private static final String TIMESTAMP_KEY = "timestamp";
  private static final String POINTER_IDENTIFIER_KEY = "identifier";

  private static final String LOCATION_X_KEY = "locationX";
  private static final String LOCATION_Y_KEY = "locationY";

  /**
   * Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from
   * given {@param event} instance. This method use {@param reactTarget} parameter to set as a
   * target view id associated with current gesture.
   */
  private static WritableArray createsPointersArray(int reactTarget, TouchEvent event) {
    WritableArray touches = Arguments.createArray();
    MotionEvent motionEvent = event.getMotionEvent();

    // Calculate the coordinates for the target view.
    // The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
    // The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
    // Subtracting them allows us to get the coordinates of the target view's top left corner
    // We then use this when computing the view specific touches below
    // Since only one view is actually handling even multiple touches, the values are all relative
    // to this one target view.
    float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
    float targetViewCoordinateY = motionEvent.getY() - event.getViewY();

    for (int index = 0; index < motionEvent.getPointerCount(); index++) {
      WritableMap touch = Arguments.createMap();
      // pageX,Y values are relative to the RootReactView
      // the motionEvent already contains coordinates in that view
      touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
      touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
      // locationX,Y values are relative to the target view
      // To compute the values for the view, we subtract that views location from the event X,Y
      float locationX = motionEvent.getX(index) - targetViewCoordinateX;
      float locationY = motionEvent.getY(index) - targetViewCoordinateY;
      touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
      touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
      touch.putInt(TARGET_KEY, reactTarget);
      touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs());
      touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
      touches.pushMap(touch);
    }

    return touches;
  }

  /**
   * Generate and send touch event to RCTEventEmitter JS module associated with the given {@param
   * context}. Touch event can encode multiple concurrent touches (pointers).
   *
   * @param rctEventEmitter Event emitter used to execute JS module call
   * @param type type of the touch event (see {@link TouchEventType})
   * @param reactTarget target view react id associated with this gesture
   * @param touchEvent native touch event to read pointers count and coordinates from
   */
  public static void sendTouchEvent(
      RCTEventEmitter rctEventEmitter,
      TouchEventType type,
      int reactTarget,
      TouchEvent touchEvent) {

    WritableArray pointers = createsPointersArray(reactTarget, touchEvent);
    MotionEvent motionEvent = touchEvent.getMotionEvent();

    // For START and END events send only index of the pointer that is associated with that event
    // For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
    WritableArray changedIndices = Arguments.createArray();
    if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
      for (int i = 0; i < motionEvent.getPointerCount(); i++) {
        changedIndices.pushInt(i);
      }
    } else if (type == TouchEventType.START || type == TouchEventType.END) {
      changedIndices.pushInt(motionEvent.getActionIndex());
    } else {
      throw new RuntimeException("Unknown touch type: " + type);
    }

    rctEventEmitter.receiveTouches(TouchEventType.getJSEventName(type), pointers, changedIndices);
  }
}
