/* eslint-disable */
/**
 * Inspired by 'PanResponder' from Facebook.
 */

'use strict';

import {InteractionManager} from 'react-native';
import TouchHistoryMath from './TouchHistoryMath'; // copied from react/lib/TouchHistoryMath.js
import {pinchDistance} from './TouchDistanceMath';
import TimerMixin from 'react-timer-mixin';

const currentCentroidXOfTouchesChangedAfter = TouchHistoryMath.currentCentroidXOfTouchesChangedAfter;
const currentCentroidYOfTouchesChangedAfter = TouchHistoryMath.currentCentroidYOfTouchesChangedAfter;
const previousCentroidXOfTouchesChangedAfter = TouchHistoryMath.previousCentroidXOfTouchesChangedAfter;
const previousCentroidYOfTouchesChangedAfter = TouchHistoryMath.previousCentroidYOfTouchesChangedAfter;
const currentCentroidX = TouchHistoryMath.currentCentroidX;
const currentCentroidY = TouchHistoryMath.currentCentroidY;

const TAP_UP_TIME_THRESHOLD = 400;
const TAP_MOVE_THRESHOLD = 10;
const MOVE_THRESHOLD = 3;

let DEV = false;

function initializeGestureState (gestureState) {
	gestureState.moveX = 0;
	gestureState.moveY = 0;
	gestureState.x0 = 0;
	gestureState.y0 = 0;
	gestureState.dx = 0;
	gestureState.dy = 0;
	gestureState.vx = 0;
	gestureState.vy = 0;
	gestureState.numberActiveTouches = 0;
  // All `gestureState` accounts for timeStamps up until:
	gestureState._accountsForMovesUpTo = 0;

	gestureState.previousMoveX = 0;
	gestureState.previousMoveY = 0;
	gestureState.pinch = undefined;
	gestureState.previousPinch = undefined;
	gestureState.singleTapUp = false;
	gestureState.doubleTapUp = false;
	gestureState._singleTabFailed = false;
}

function updateGestureStateOnMove (gestureState, touchHistory, e) {
	const movedAfter = gestureState._accountsForMovesUpTo;
	const prevX = previousCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
	const x = currentCentroidXOfTouchesChangedAfter(touchHistory, movedAfter);
	const prevY = previousCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
	const y = currentCentroidYOfTouchesChangedAfter(touchHistory, movedAfter);
	const dx = x - prevX;
	const dy = y - prevY;

	gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
	gestureState.moveX = x;
	gestureState.moveY = y;

  // TODO: This must be filtered intelligently.
  // const dt = touchHistory.mostRecentTimeStamp - movedAfter;
	const dt = convertToMillisecIfNeeded(touchHistory.mostRecentTimeStamp - movedAfter);
	gestureState.vx = dx / dt;
	gestureState.vy = dy / dt;
	gestureState.dx += dx;
	gestureState.dy += dy;
	gestureState._accountsForMovesUpTo = touchHistory.mostRecentTimeStamp;

	gestureState.previousMoveX = prevX;
	gestureState.previousMoveY = prevY;
	gestureState.pinch = pinchDistance(touchHistory, movedAfter, true);
	gestureState.previousPinch = pinchDistance(touchHistory, movedAfter, false);
}

function clearInteractionHandle (interactionState) {
	if (interactionState.handle) {
		InteractionManager.clearInteractionHandle(interactionState.handle);
		interactionState.handle = null;
	}
}

/**
 * Due to commit https://github.com/facebook/react-native/commit/f2c1868b56bdfc8b0d6f448733848eafed2cd440,
 * Android is using nanoseconds while iOS is using milliseconds.
 * @param interval
 * @returns {*}
 */
function convertToMillisecIfNeeded (interval) {
	if (interval > 1000000) {
		return interval / 1000000;
	}
	return interval;
}

function cancelSingleTapConfirm (gestureState) {
	if (typeof gestureState._singleTapConfirmId !== 'undefined') {
		TimerMixin.clearTimeout(gestureState._singleTapConfirmId);
		gestureState._singleTapConfirmId = undefined;
	}
}

/**
 * The config object contains same callbacks as the default gesture responder(https://facebook.github.io/react-native/docs/gesture-responder-system.html).
 * And every callback are called with an additional argument 'gestureState', like PanResponder.
 * @param config
 * @returns {{}}
 */

/**
 * The config object contains same callbacks as the default gesture responder(https://facebook.github.io/react-native/docs/gesture-responder-system.html).
 * And every callback are called with an additional argument 'gestureState', like PanResponder.
 * @param config
 * @param debug true to enable debug logs
 * @returns {{}}
 */
export default function create (config) {
	if (config.debug) {
		DEV = true;
	}

	const interactionState = {
		handle: null
	};
	const gestureState = {
	// Useful for debugging
		stateID: Math.random()
	};
	initializeGestureState(gestureState);

	const handlers = {
		onStartShouldSetResponder: function (e) {
			DEV && console.log('onStartShouldSetResponder...');
			cancelSingleTapConfirm(gestureState);
			return config.onStartShouldSetResponder ?
		config.onStartShouldSetResponder(e, gestureState) :
		false;
		},
		onMoveShouldSetResponder: function (e) {
			DEV && console.log('onMoveShouldSetResponder...');

			return config.onMoveShouldSetResponder && effectiveMove(config, gestureState) ?
			config.onMoveShouldSetResponder(e, gestureState) :
			false;
		},
		// onStartShouldSetResponderCapture: function (e) {
		// 	// DEV && console.log('onStartShouldSetResponderCapture...');
		// 	// cancelSingleTapConfirm(gestureState);
	 //  // // TODO: Actually, we should reinitialize the state any time
	 //  // // touches.length increases from 0 active to > 0 active.

		// 	if (e.nativeEvent.touches.length === 1) {
		// 		initializeGestureState(gestureState);
		// 	}
		// 	gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
		// 	// return config.onStartShouldSetResponderCapture ?
		// 	// config.onStartShouldSetResponderCapture(e, gestureState) :
		// 	// false;
		// 	return false;

		// },

		onMoveShouldSetResponderCapture: function (e) {
			DEV && console.log('onMoveShouldSetResponderCapture...');
			const touchHistory = e.touchHistory;
	  // Responder system incorrectly dispatches should* to current responder
	  // Filter out any touch moves past the first one - we would have
	  // already processed multi-touch geometry during the first event.
			if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
				return false;
			}
			updateGestureStateOnMove(gestureState, touchHistory, e);
			return config.onMoveShouldSetResponderCapture && effectiveMove(config, gestureState) ?
		config.onMoveShouldSetResponderCapture(e, gestureState) :
		false;
		},

		onResponderGrant: function (e) {
			DEV && console.log('onResponderGrant...');
			cancelSingleTapConfirm(gestureState);
			if (!interactionState.handle) {
				interactionState.handle = InteractionManager.createInteractionHandle();
			}
			gestureState._grantTimestamp = e.touchHistory.mostRecentTimeStamp;
			gestureState.x0 = currentCentroidX(e.touchHistory);
			gestureState.y0 = currentCentroidY(e.touchHistory);
			gestureState.dx = 0;
			gestureState.dy = 0;
			if (config.onResponderGrant) {
				config.onResponderGrant(e, gestureState);
			}
	  // TODO: t7467124 investigate if this can be removed
			return config.onShouldBlockNativeResponder === undefined ?
		true :
		config.onShouldBlockNativeResponder();
		},

		onResponderReject: function (e) {
			DEV && console.log('onResponderReject...');
			clearInteractionHandle(interactionState);
			config.onResponderReject && config.onResponderReject(e, gestureState);
		},

		onResponderRelease: function (e) {
			if (gestureState.singleTapUp) {
				if (gestureState._lastSingleTapUp) {
					if (convertToMillisecIfNeeded(e.touchHistory.mostRecentTimeStamp - gestureState._lastReleaseTimestamp) < TAP_UP_TIME_THRESHOLD) {
					  gestureState.doubleTapUp = true;
				  }
				}
				gestureState._lastSingleTapUp = true;

		// schedule to confirm single tap
				if (!gestureState.doubleTapUp) {
					const snapshot = Object.assign({}, gestureState);
					const timeoutId = TimerMixin.setTimeout(() => {
					  if (gestureState._singleTapConfirmId === timeoutId) {
						DEV && console.log('onResponderSingleTapConfirmed...');
						config.onResponderSingleTapConfirmed && config.onResponderSingleTapConfirmed(e, snapshot);
					}
				  }, TAP_UP_TIME_THRESHOLD);
					gestureState._singleTapConfirmId = timeoutId;
				}
			}
			gestureState._lastReleaseTimestamp = e.touchHistory.mostRecentTimeStamp;

			DEV && console.log('onResponderRelease...' + JSON.stringify(gestureState));
			clearInteractionHandle(interactionState);
			config.onResponderRelease && config.onResponderRelease(e, gestureState);
			initializeGestureState(gestureState);
		},

		onResponderStart: function (e) {
			DEV && console.log('onResponderStart...');
			const touchHistory = e.touchHistory;
			gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
			if (config.onResponderStart) {
				config.onResponderStart(e, gestureState);
			}
		},

		onResponderMove: function (e) {
			const touchHistory = e.touchHistory;
	  // Guard against the dispatch of two touch moves when there are two
	  // simultaneously changed touches.
			if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
				return;
			}
	  // Filter out any touch moves past the first one - we would have
	  // already processed multi-touch geometry during the first event.
			updateGestureStateOnMove(gestureState, touchHistory, e);

			DEV && console.log('onResponderMove...' + JSON.stringify(gestureState));
			if (config.onResponderMove && effectiveMove(config, gestureState)) {
				config.onResponderMove(e, gestureState);
			}
		},

		onResponderEnd: function (e) {
			const touchHistory = e.touchHistory;
			gestureState.numberActiveTouches = touchHistory.numberActiveTouches;

			if (touchHistory.numberActiveTouches > 0 ||
		convertToMillisecIfNeeded(touchHistory.mostRecentTimeStamp - gestureState._grantTimestamp) > TAP_UP_TIME_THRESHOLD ||
		Math.abs(gestureState.dx) >= TAP_MOVE_THRESHOLD ||
		Math.abs(gestureState.dy) >= TAP_MOVE_THRESHOLD
	  ) {
				gestureState._singleTabFailed = true;
			}
			if (!gestureState._singleTabFailed) {
				gestureState.singleTapUp = true;
			}

			DEV && console.log('onResponderEnd...' + JSON.stringify(gestureState));
			clearInteractionHandle(interactionState);
			config.onResponderEnd && config.onResponderEnd(e, gestureState);
		},

		onResponderTerminate: function (e) {
			DEV && console.log('onResponderTerminate...');
			clearInteractionHandle(interactionState);
			config.onResponderTerminate && config.onResponderTerminate(e, gestureState);
			initializeGestureState(gestureState);
		},

		onResponderTerminationRequest: function (e) {
			DEV && console.log('onResponderTerminationRequest...');
			return config.onResponderTerminationRequest ?
		config.onResponderTerminationRequest(e.gestureState) :
		true;
		}
	};
	return {...handlers};
}

/**
 * On Android devices, the default gesture responder is too sensitive that a single tap(no move intended) may trigger a move event.
 * We can use a moveThreshold config to avoid those unwanted move events.
 * @param config
 * @param gestureState
 * @returns {boolean}
 */
function effectiveMove (config, gestureState) {
	if (gestureState.numberActiveTouches > 1) {
	// on iOS simulator, a pinch gesture(move with alt pressed) will not change gestureState.dx(always 0)
		return true;
	}

	let moveThreshold = MOVE_THRESHOLD;
	if (typeof config.moveThreshold === 'number') {
		moveThreshold = config.minMoveDistance;
	}
	if (Math.abs(gestureState.dx) >= moveThreshold || Math.abs(gestureState.dy) >= moveThreshold) {
		return true;
	}
	return false;
}
/* eslint-enable */
