/* eslint-disable react/no-did-update-set-state */

import React from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { color } from 'nixplay-ui-kit';
import { PanGestureHandler, PinchGestureHandler, TapGestureHandler, State } from 'react-native-gesture-handler';
import Reanimated, { Easing } from 'react-native-reanimated';
import {
	ActivityIndicator,
	View,
	StyleSheet,
	Dimensions,
} from 'react-native';

import PhotoContent from './PhotoContent';
import VideoContent from './VideoContent';
// import Debug from './Debug';

const {
	abs,
	add,
	and,
	block,
	call,
	cond,
	clockRunning,
	divide,
	event,
	eq,
	interpolate,
	set,
	sub,
	or,
	neq,
	not,
	greaterThan,
	greaterOrEq,
	lessThan,
	multiply,
	startClock,
	stopClock,
	timing,
	Clock,
	Value,
	Extrapolate,
} = Reanimated;

const { width: screenWidth, height: screenHeight } = Dimensions.get('window');

const halfScreenWidth = screenWidth / 2;
const halfScreenHeight = screenHeight / 2;

const styles = StyleSheet.create({
	container: {
		height: screenHeight,
		width: screenWidth,
		position: 'absolute',
		top: 0,
		left: 0,
	},
});

// contains dimensions of loaded images
const imageDimensions = {};

const getSwipeOffset = (index) => index * -screenWidth;

export default class ContentPager extends React.Component {
	static propTypes = {
		initialIndex: PropTypes.number,
		photos: PropTypes.array,
		onPageChange: PropTypes.func,
		onEndReached: PropTypes.func,
		onZoomStart: PropTypes.func,
		onZoomEnd: PropTypes.func,
		onTap: PropTypes.func,
		onDoubleTap: PropTypes.func,
		hasNext: PropTypes.bool,
	}

	static defaultProps = {
		initialIndex: 0,
		photos: [],
		hasNext: false,
		onPageChange: () => {},
		onEndReached: () => {},
		onZoomStart: () => {},
		onZoomEnd: () => {},
		onTap: () => {},
		onDoubleTap: () => {},
	}

	constructor(props) {
		super(props);

		this.state = {
			index: props.initialIndex,
		};

		this.nextIndexValue = props.initialIndex;

		// this.renderingPhotos = new Value(1);
		this.renderingPhotosStarted = new Value(0);

		this.index = new Value(props.initialIndex);
		this.nextIndex = new Value(props.initialIndex);
		this.photosCount = new Value(props.photos.length);
		this.hasNext = new Value(props.hasNext ? 1 : 0);

		this.imageContainerWidth = new Value(0);
		this.imageContainerHeight = new Value(0);
		this.imageOriginalWidth = new Value(0);
		this.imageOriginalHeight = new Value(0);
		this.maxScale = new Value(0);

		this.isZooming = new Value(0);
		this.isSwipingLeft = new Value(0);
		this.isSwipingRight = new Value(0);
		this.allowSwipeWhileZooming = new Value(0);

		// this.testValue = new Value(0);
		const initialSwipeOffset = getSwipeOffset(props.initialIndex);

		this.currentPageSwipeOffset = new Value(initialSwipeOffset);
		this.nextPageSwipeOffset = new Value(initialSwipeOffset);

		this.animData = {
			isPanning: new Value(0),
			isPinching: new Value(0),
			isScrolling: new Value(0),
			swipePos: new Value(initialSwipeOffset),
			swipePosOffset: new Value(initialSwipeOffset),
			panOffsetX: new Value(0),
			panOffsetY: new Value(0),
			translateX: new Value(0),
			translateY: new Value(0),
			tempTranslateX: new Value(0),
			tempTranslateY: new Value(0),
			panExcessX: new Value(0),
			panExcessY: new Value(0),
			xBound: new Value(0),
			xBoundLeft: new Value(0),
			xBoundRight: new Value(0),
			yBound: new Value(0),
			yBoundTop: new Value(0),
			yBoundBottom: new Value(0),
			imageScale: new Value(1),
			imageScaleOffset: new Value(1),
			imageScaleHeight: new Value(0),
			imageScaleWidth: new Value(0),
			imageScaleHalfHeight: new Value(0),
			imageScaleHalfWidth: new Value(0),
			imageScaleHeightDiff: new Value(0),
			imageScaleWidthDiff: new Value(0),
			imageScaleScreenHeightDiff: new Value(0),
			imageScaleScreenWidthDiff: new Value(0),
			lastImageScaleHeight: new Value(0),
			lastImageScaleWidth: new Value(0),
			scaleFocusX: new Value(0),
			scaleFocusY: new Value(0),
			centerFocusX: new Value(0),
			centerFocusY: new Value(0),
			scaleTranslateX: new Value(0),
			scaleTranslateY: new Value(0),
			focalCenterY: new Value(0),
			tX: new Value(0),
			tY: new Value(0),
			focalOffsetX: new Value(0),
			focalOffsetY: new Value(0),
		};

		const {
			swipePos,
			swipePosOffset,
			isPanning,
			isPinching,
			imageScale,
			imageScaleOffset,
			panOffsetX,
			panOffsetY,
			translateX,
			translateY,
			panExcessX,
			panExcessY,
			tempTranslateX,
			tempTranslateY,
			xBound,
			xBoundLeft,
			xBoundRight,
			yBound,
			yBoundTop,
			yBoundBottom,
			tX,
			tY,
			focalOffsetX,
			focalOffsetY,
			imageScaleHeight,
			imageScaleWidth,
			imageScaleHeightDiff,
			imageScaleWidthDiff,
			imageScaleHalfHeight,
			imageScaleHalfWidth,
			imageScaleScreenHeightDiff,
			imageScaleScreenWidthDiff,
			lastImageScaleHeight,
			lastImageScaleWidth,
			scaleFocusX,
			scaleFocusY,
			centerFocusX,
			centerFocusY,
			scaleTranslateX,
			scaleTranslateY,
		} = this.animData;

		this.swipeClock = new Clock();
		this.swipeClockState = {
			finished: new Value(0),
			// position: swipePos,
			position: new Value(0),
			time: new Value(0),
			frameTime: new Value(0),
		};
		this.swipeClockConfig = {
			duration: 800,
			toValue: new Value(1),
			easing: Easing.out(Easing.exp),
		};

		this.panFromX = new Value(0);
		this.panToX = new Value(0);
		this.panFromY = new Value(0);
		this.panToY = new Value(0);

		this.panClock = new Clock();
		this.panClockState = {
			finished: new Value(0),
			position: new Value(0),
			time: new Value(0),
			frameTime: new Value(0),
		};
		this.panClockConfig = {
			duration: 150,
			toValue: new Value(1),
			easing: Easing.inOut(Easing.ease),
		};

		this.translateBounceX = interpolate(this.panClockState.position, {
			inputRange: [0, 1],
			outputRange: [this.panFromX, this.panToX],
			extrapolate: Extrapolate.CLAMP,
		});

		this.translateBounceY = interpolate(this.panClockState.position, {
			inputRange: [0, 1],
			outputRange: [this.panFromY, this.panToY],
			extrapolate: Extrapolate.CLAMP,
		});

		this.pinchClock = new Clock();
		this.pinchClockState = {
			finished: new Value(0),
			position: new Value(0),
			time: new Value(0),
			frameTime: new Value(0),
		};
		this.pinchClockConfig = {
			duration: 150,
			toValue: new Value(1),
			easing: Easing.inOut(Easing.ease),
		};

		this.zoomFrom = new Value(0);
		this.zoomTo = new Value(0);

		this.scaleBounce = interpolate(this.pinchClockState.position, {
			inputRange: [0, 1],
			outputRange: [this.zoomFrom, this.zoomTo],
			extrapolate: Extrapolate.CLAMP,
		});

		this.setPageRight = block([
			set(this.nextIndex, add(this.index, 1)),
			set(this.nextPageSwipeOffset, multiply(this.nextIndex, -screenWidth)),
			set(this.swipeClockConfig.toValue, this.nextPageSwipeOffset),
			call([], () => { this.nextIndexValue = this.state.index + 1; }),
		]);

		this.setPageLeft = block([
			set(this.nextIndex, sub(this.index, 1)),
			set(this.nextPageSwipeOffset, multiply(this.nextIndex, -screenWidth)),
			set(this.swipeClockConfig.toValue, this.nextPageSwipeOffset),
			call([], () => { this.nextIndexValue = this.state.index - 1; }),
		]);

		this.setPageCenter = block([
			set(this.nextPageSwipeOffset, this.currentPageSwipeOffset),
			set(this.swipeClockConfig.toValue, this.nextPageSwipeOffset),
			set(this.nextIndex, this.index),
			call([], () => { this.nextIndexValue = this.state.index; }),
		]);

		this.triggerTranslateBounce = block([
			set(this.panClockState.finished, 0),
			set(this.panClockState.frameTime, 0),
			set(this.panClockState.time, 0),
			set(this.panClockState.position, 0),
			set(this.panFromX, translateX),
			set(this.panFromY, translateY),
			cond(greaterThan(translateX, xBoundLeft), [
				set(this.panToX, xBoundLeft),
				set(translateX, xBoundLeft),
				set(this.isSwipingLeft, 1),
			], cond(lessThan(translateX, xBoundRight), [
				set(this.panToX, xBoundRight),
				set(translateX, xBoundRight),
				set(this.isSwipingRight, 1),
			], [
				set(this.panToX, translateX),
			])),
			cond(greaterThan(translateY, yBoundTop), [
				set(this.panToY, yBoundTop),
				set(translateY, yBoundTop),
			], cond(lessThan(translateY, yBoundBottom), [
				set(this.panToY, yBoundBottom),
				set(translateY, yBoundBottom),
			], [
				set(this.panToY, translateY),
			])),
			set(panOffsetX, translateX),
			set(panOffsetY, translateY),
			startClock(this.panClock),
		]);

		this.pinchEvent = event([{
			nativeEvent: ({ scale, focalX, focalY }) => block([
				// cond(isPinching, greaterOrEq(multiply(imageScaleOffset, scale), 1)), [
				cond(isPinching, [
					set(imageScale, multiply(imageScaleOffset, scale)),

					set(imageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
					set(imageScaleWidth, multiply(imageScale, this.imageContainerWidth)),
					set(imageScaleHalfHeight, divide(imageScaleHeight, 2)),
					set(imageScaleHalfWidth, divide(imageScaleWidth, 2)),

					set(imageScaleWidthDiff, sub(imageScaleWidth, this.imageContainerWidth)),
					set(imageScaleHeightDiff, sub(imageScaleHeight, this.imageContainerHeight)),

					set(imageScaleScreenWidthDiff, sub(imageScaleWidth, screenWidth)),
					set(imageScaleScreenHeightDiff, sub(imageScaleHeight, screenHeight)),

					set(xBound, cond(greaterThan(imageScaleScreenWidthDiff, 0), divide(imageScaleScreenWidthDiff, 2), 0)),
					set(yBound, cond(greaterThan(imageScaleScreenHeightDiff, 0), divide(imageScaleScreenHeightDiff, 2), 0)),

					set(xBoundLeft, xBound),
					set(xBoundRight, multiply(xBound, -1)),
					set(yBoundTop, yBound),
					set(yBoundBottom, multiply(yBound, -1)),

					set(scaleFocusX, multiply(divide(centerFocusX, lastImageScaleWidth), imageScaleWidth)),
					set(scaleFocusY, multiply(divide(centerFocusY, lastImageScaleHeight), imageScaleHeight)),

					set(
						scaleTranslateX,
						multiply(
							divide(sub(scaleFocusX, imageScaleHalfWidth), imageScaleWidth),
							sub(lastImageScaleWidth, imageScaleWidth),
						),
					),
					set(
						scaleTranslateY,
						multiply(
							divide(sub(scaleFocusY, imageScaleHalfHeight), imageScaleHeight),
							sub(lastImageScaleHeight, imageScaleHeight),
						),
					),

					set(translateX, add(add(scaleTranslateX, panOffsetX), sub(focalX, focalOffsetX))),
					set(translateY, add(add(scaleTranslateY, panOffsetY), sub(focalY, focalOffsetY))),
				]),
			]),
		}], {
			useNativeDriver: true,
		});

		this.pinchChangeState = event([{
			nativeEvent: ({ state, focalX, focalY }) => block([
				cond(and(this.imageOriginalHeight, this.imageOriginalWidth), [
					cond(eq(state, State.ACTIVE), [
						cond(and(not(isPinching), this.imageContainerWidth, this.imageContainerHeight), [
							cond(not(this.isZooming), set(this.isZooming, 1)),
							set(focalOffsetX, focalX),
							set(focalOffsetY, focalY),

							set(lastImageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
							set(lastImageScaleWidth, multiply(imageScale, this.imageContainerWidth)),

							set(centerFocusX, add(sub(sub(divide(lastImageScaleWidth, 2), panOffsetX), halfScreenWidth), focalOffsetX)),
							set(centerFocusY, add(sub(sub(divide(lastImageScaleHeight, 2), panOffsetY), halfScreenHeight), focalOffsetY)),

							set(isPinching, 1),
							call([add(imageScale)], ([s]) => { this.props.onZoomStart(s); }),
						]),
					]),
					cond(and(eq(state, State.END), isPinching), [
						set(panOffsetX, translateX),
						set(panOffsetY, translateY),
						set(this.pinchClockState.finished, 0),
						set(this.pinchClockState.frameTime, 0),
						set(this.pinchClockState.time, 0),
						set(this.pinchClockState.position, 0),
						set(this.zoomFrom, imageScale),
						cond(greaterThan(imageScale, this.maxScale), [
							set(this.zoomTo, this.maxScale),

							set(lastImageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
							set(lastImageScaleWidth, multiply(imageScale, this.imageContainerWidth)),

							set(centerFocusX, add(sub(sub(divide(lastImageScaleWidth, 2), panOffsetX), halfScreenWidth), focalOffsetX)),
							set(centerFocusY, add(sub(sub(divide(lastImageScaleHeight, 2), panOffsetY), halfScreenHeight), focalOffsetY)),

							set(imageScale, this.maxScale),

							set(imageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
							set(imageScaleWidth, multiply(imageScale, this.imageContainerWidth)),
							set(imageScaleHalfHeight, divide(imageScaleHeight, 2)),
							set(imageScaleHalfWidth, divide(imageScaleWidth, 2)),

							set(imageScaleWidthDiff, sub(imageScaleWidth, this.imageContainerWidth)),
							set(imageScaleHeightDiff, sub(imageScaleHeight, this.imageContainerHeight)),

							set(imageScaleScreenWidthDiff, sub(imageScaleWidth, screenWidth)),
							set(imageScaleScreenHeightDiff, sub(imageScaleHeight, screenHeight)),

							set(xBound, cond(greaterThan(imageScaleScreenWidthDiff, 0), divide(imageScaleScreenWidthDiff, 2), 0)),
							set(yBound, cond(greaterThan(imageScaleScreenHeightDiff, 0), divide(imageScaleScreenHeightDiff, 2), 0)),

							set(xBoundLeft, xBound),
							set(xBoundRight, multiply(xBound, -1)),
							set(yBoundTop, yBound),
							set(yBoundBottom, multiply(yBound, -1)),

							set(scaleFocusX, multiply(divide(centerFocusX, lastImageScaleWidth), imageScaleWidth)),
							set(scaleFocusY, multiply(divide(centerFocusY, lastImageScaleHeight), imageScaleHeight)),

							set(
								scaleTranslateX,
								multiply(
									divide(sub(scaleFocusX, imageScaleHalfWidth), imageScaleWidth),
									sub(lastImageScaleWidth, imageScaleWidth),
								),
							),
							set(
								scaleTranslateY,
								multiply(
									divide(sub(scaleFocusY, imageScaleHalfHeight), imageScaleHeight),
									sub(lastImageScaleHeight, imageScaleHeight),
								),
							),

							set(tempTranslateX, add(scaleTranslateX, panOffsetX)),
							set(tempTranslateY, add(scaleTranslateY, panOffsetY)),

							set(this.panFromX, translateX),
							set(this.panFromY, translateY),

							cond(greaterThan(tempTranslateX, xBoundLeft), [
								set(this.panToX, xBoundLeft),
								set(tempTranslateX, xBoundLeft),
								set(this.isSwipingLeft, 1),
							], cond(lessThan(tempTranslateX, xBoundRight), [
								set(this.panToX, xBoundRight),
								set(tempTranslateX, xBoundRight),
								set(this.isSwipingRight, 1),
							], [
								set(this.panToX, tempTranslateX),
							])),
							cond(greaterThan(tempTranslateY, yBoundTop), [
								set(this.panToY, yBoundTop),
								set(tempTranslateY, yBoundTop),
							], cond(lessThan(tempTranslateY, yBoundBottom), [
								set(this.panToY, yBoundBottom),
								set(tempTranslateY, yBoundBottom),
							], [
								set(this.panToY, tempTranslateY),
							])),

							set(this.panClockState.finished, 0),
							set(this.panClockState.frameTime, 0),
							set(this.panClockState.time, 0),
							set(this.panClockState.position, 0),

							startClock(this.panClock),

							set(translateX, tempTranslateX),
							set(translateY, tempTranslateY),
							set(panOffsetX, tempTranslateX),
							set(panOffsetY, tempTranslateY),
						], cond(lessThan(imageScale, 1), [
							set(this.zoomTo, 1),
							set(imageScale, 1),
							set(xBoundLeft, 0),
							set(xBoundRight, 0),
							set(yBoundTop, 0),
							set(yBoundBottom, 0),
							this.triggerTranslateBounce,
						], [
							set(this.zoomTo, imageScale),
							this.triggerTranslateBounce,
						])),
						set(imageScaleOffset, imageScale),
						startClock(this.pinchClock),
						set(isPinching, 0),
						call([add(imageScale)], ([s]) => { this.props.onZoomEnd(s); }),
					]),
				]),
			]),
		}], {
			useNativeDriver: true,
		});

		this.panEvent = event([{
			nativeEvent: ({
				translationX, translationY, numberOfPointers,
			}) => block([
				// cond(and(isPanning, not(this.renderingPhotos)), [
				cond(isPanning, [
					set(tempTranslateX, add(translationX, panOffsetX)),
					set(tempTranslateY, add(translationY, panOffsetY)),
					// cond(or(lessOrEq(imageScale, 1)), [
					cond(eq(imageScale, 1), [
						cond(eq(numberOfPointers, 1), set(swipePos, add(translationX, swipePosOffset))),
						set(this.isSwipingLeft, 1),
						set(this.isSwipingRight, 1),
					], [
						cond(greaterThan(tempTranslateX, xBoundLeft), [
							cond(this.isSwipingLeft, [
								set(swipePos, add(translationX, swipePosOffset)),
							], [
								set(panExcessX, divide(sub(tempTranslateX, xBoundLeft), 3)),
								set(translateX, add(xBoundLeft, panExcessX)),
							]),
						], cond(lessThan(tempTranslateX, xBoundRight), [
							cond(this.isSwipingRight, [
								set(swipePos, add(translationX, swipePosOffset)),
							], [
								set(panExcessX, divide(sub(tempTranslateX, xBoundRight), 3)),
								set(translateX, add(xBoundRight, panExcessX)),
							]),
						], [
							cond(this.isSwipingLeft, set(this.isSwipingLeft, 0)),
							cond(this.isSwipingRight, set(this.isSwipingRight, 0)),
							set(translateX, tempTranslateX),
						])),
						cond(greaterThan(tempTranslateY, yBoundTop), [
							set(panExcessY, divide(sub(tempTranslateY, yBoundTop), 3)),
							set(translateY, add(yBoundTop, panExcessY)),
						], cond(lessThan(tempTranslateY, yBoundBottom), [
							set(panExcessY, divide(sub(tempTranslateY, yBoundBottom), 3)),
							set(translateY, add(yBoundBottom, panExcessY)),
						], [
							set(translateY, tempTranslateY),
						])),
					]),
				]),
			]),
		}], {
			useNativeDriver: true,
		});
		this.panChangeState = event([{
			nativeEvent: ({
				state, translationX, translationY, velocityX,
			}) => block([
				cond(eq(state, State.ACTIVE), [
					cond(not(isPanning), [
						// cond(and(this.renderingPhotosStarted, not(this.renderingPhotos)), [
						cond(this.renderingPhotosStarted, [
							set(this.swipeClockState.finished, 1),
						]),
						set(isPanning, 1),
					]),
				], cond(and(or(eq(state, State.END), eq(state, State.CANCELLED)), isPanning), [
					set(isPanning, 0),
					set(swipePosOffset, swipePos),
					set(panOffsetX, translateX),
					set(panOffsetY, translateY),
					set(tX, translationX),
					set(tY, translationY),
					cond(and(or(this.isSwipingLeft, this.isSwipingRight), not(clockRunning(this.swipeClock))), [
						// set(this.swipeClockConfig.duration, multiply(divide(1000, add(abs(velocityX), 100))), 1000),
						// set(this.swipeClockConfig.duration, multiply(divide(1000, add(abs(velocityX), 100)), 1000)),
						set(this.swipeClockState.finished, 0),
						set(this.swipeClockState.frameTime, 0),
						set(this.swipeClockState.time, 0),
						set(this.swipeClockState.position, swipePos),
						cond(and(
							greaterThan(swipePos, add(this.currentPageSwipeOffset, halfScreenWidth)),
							greaterOrEq(sub(this.index, 1), 0),
						), [
							this.setPageLeft,
						], cond(and(
							lessThan(swipePos, sub(this.currentPageSwipeOffset, halfScreenWidth)),
							cond(this.hasNext, [
								lessThan(add(this.index, 1), add(this.photosCount, 1)),
							], lessThan(add(this.index, 1), this.photosCount)),
						), [
							this.setPageRight,
						], cond(
							greaterThan(abs(velocityX), 50), [
								cond(
									greaterThan(translationX, 0),
									cond(greaterOrEq(sub(this.index, 1), 0), this.setPageLeft, this.setPageCenter),
									cond([
										cond(this.hasNext, [
											lessThan(add(this.index, 1), add(this.photosCount, 1)),
										], lessThan(add(this.index, 1), this.photosCount)),
									], this.setPageRight, [
										call([], () => { this.props.onEndReached(); }),
										this.setPageCenter,
									]),
								),
							],
							this.setPageCenter,
						))),
						startClock(this.swipeClock),
						set(this.renderingPhotosStarted, 1),
						// set(this.renderingPhotos, 1),
						cond(neq(this.nextIndex, this.index), [
							set(panOffsetX, 0),
							set(panOffsetY, 0),
							set(translateX, 0),
							set(translateY, 0),
							set(xBoundLeft, 0),
							set(xBoundRight, 0),
							set(imageScale, 1),
							set(imageScaleOffset, 1),
							set(this.isZooming, 0),
							set(this.imageContainerWidth, 0),
							set(this.imageContainerHeight, 0),
							set(this.imageOriginalWidth, 0),
							set(this.imageOriginalHeight, 0),
							set(this.maxScale, 1),
						]),
						cond(this.isSwipingLeft, set(this.isSwipingLeft, 0)),
						cond(this.isSwipingRight, set(this.isSwipingRight, 0)),
						set(this.index, this.nextIndex),
						call([], () => {
							if (typeof this.nextIndexValue !== 'undefined') {
								const currentIndex = this.nextIndexValue;
								this.setState({ index: currentIndex }, () => {
									// this.renderingPhotos.setValue(0);
									this.setupCurrentImageDimensions(currentIndex);
									this.props.onPageChange(currentIndex);
								});
							}
						}),
					], this.triggerTranslateBounce),
				])),

			]),
		}], {
			useNativeDriver: true,
		});

		this.doubleTapChangeState = event([{
			nativeEvent: ({
				state, x, y,
			}) => block([
				cond(and(eq(state, State.END), this.imageContainerWidth, this.imageContainerHeight), [
					set(this.pinchClockState.finished, 0),
					set(this.pinchClockState.frameTime, 0),
					set(this.pinchClockState.time, 0),
					set(this.pinchClockState.position, 0),
					set(this.zoomFrom, imageScale),
					call([add(imageScale)], ([s]) => { this.props.onDoubleTap(s); }),
					cond(eq(imageScale, 1), [
						set(this.zoomTo, this.maxScale),

						set(lastImageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
						set(lastImageScaleWidth, multiply(imageScale, this.imageContainerWidth)),

						set(imageScale, this.maxScale),

						set(imageScaleHeight, multiply(imageScale, this.imageContainerHeight)),
						set(imageScaleWidth, multiply(imageScale, this.imageContainerWidth)),
						set(imageScaleHalfHeight, divide(imageScaleHeight, 2)),
						set(imageScaleHalfWidth, divide(imageScaleWidth, 2)),

						set(imageScaleWidthDiff, sub(imageScaleWidth, this.imageContainerWidth)),
						set(imageScaleHeightDiff, sub(imageScaleHeight, this.imageContainerHeight)),

						set(imageScaleScreenWidthDiff, sub(imageScaleWidth, screenWidth)),
						set(imageScaleScreenHeightDiff, sub(imageScaleHeight, screenHeight)),

						set(centerFocusX, add(sub(divide(lastImageScaleWidth, 2), halfScreenWidth), x)),
						set(centerFocusY, add(sub(divide(lastImageScaleHeight, 2), halfScreenHeight), y)),

						set(xBound, cond(greaterThan(imageScaleScreenWidthDiff, 0), divide(imageScaleScreenWidthDiff, 2), 0)),
						set(yBound, cond(greaterThan(imageScaleScreenHeightDiff, 0), divide(imageScaleScreenHeightDiff, 2), 0)),

						set(xBoundLeft, xBound),
						set(xBoundRight, multiply(xBound, -1)),
						set(yBoundTop, yBound),
						set(yBoundBottom, multiply(yBound, -1)),

						set(scaleFocusX, multiply(divide(centerFocusX, lastImageScaleWidth), imageScaleWidth)),
						set(scaleFocusY, multiply(divide(centerFocusY, lastImageScaleHeight), imageScaleHeight)),

						set(
							scaleTranslateX,
							multiply(
								divide(sub(scaleFocusX, imageScaleHalfWidth), imageScaleWidth),
								sub(lastImageScaleWidth, imageScaleWidth),
							),
						),
						set(
							scaleTranslateY,
							multiply(
								divide(sub(scaleFocusY, imageScaleHalfHeight), imageScaleHeight),
								sub(lastImageScaleHeight, imageScaleHeight),
							),
						),

						set(tempTranslateX, scaleTranslateX),
						set(tempTranslateY, scaleTranslateY),

						set(this.panFromX, translateX),
						set(this.panFromY, translateY),

						cond(greaterThan(tempTranslateX, xBoundLeft), [
							set(this.panToX, xBoundLeft),
							set(tempTranslateX, xBoundLeft),
						], cond(lessThan(tempTranslateX, xBoundRight), [
							set(this.panToX, xBoundRight),
							set(tempTranslateX, xBoundRight),
						], [
							set(this.panToX, tempTranslateX),
						])),
						cond(greaterThan(tempTranslateY, yBoundTop), [
							set(this.panToY, yBoundTop),
							set(tempTranslateY, yBoundTop),
						], cond(lessThan(tempTranslateY, yBoundBottom), [
							set(this.panToY, yBoundBottom),
							set(tempTranslateY, yBoundBottom),
						], [
							set(this.panToY, tempTranslateY),
						])),

						set(this.panClockState.finished, 0),
						set(this.panClockState.frameTime, 0),
						set(this.panClockState.time, 0),
						set(this.panClockState.position, 0),

						startClock(this.panClock),

						set(translateX, tempTranslateX),
						set(translateY, tempTranslateY),
						set(panOffsetX, tempTranslateX),
						set(panOffsetY, tempTranslateY),
					], [
						set(this.zoomTo, 1),
						set(imageScale, 1),
						set(xBoundLeft, 0),
						set(xBoundRight, 0),
						set(yBoundTop, 0),
						set(yBoundBottom, 0),
						this.triggerTranslateBounce,
					]),
					set(imageScaleOffset, imageScale),
					startClock(this.pinchClock),
				]),
			]),
		}]);

		this.eventListener = () => block([
			timing(this.swipeClock, this.swipeClockState, this.swipeClockConfig),
			timing(this.panClock, this.panClockState, this.panClockConfig),
			timing(this.pinchClock, this.pinchClockState, this.pinchClockConfig),

			cond(and(clockRunning(this.swipeClock), this.swipeClockState.finished), [
				stopClock(this.swipeClock),
				set(swipePos, this.swipeClockState.position),
				set(swipePosOffset, this.swipeClockState.position),
				set(this.currentPageSwipeOffset, multiply(this.index, -screenWidth)),
				set(this.renderingPhotosStarted, 0),
				// set(this.renderingPhotos, 0),
			]),
			cond(and(clockRunning(this.panClock), this.panClockState.finished), [
				stopClock(this.panClock),
			]),
			cond(and(clockRunning(this.pinchClock), this.pinchClockState.finished), [
				stopClock(this.pinchClock),
			]),
			// call([this.swipeClockState.position], ([pos]) => { console.log('clock pos', pos); }),
			// call([imageScale, imageScaleOffset], ([is, io]) => { console.log(`imageScale: ${is}, offset: ${io}, left: ${((is * screenWidth) - screenWidth) * -1}, right: ${(is * screenWidth) - screenWidth}`); }),
			// call([
			// 	panOffsetX,
			// 	translateX,
			// 	tempTranslateX,
			// 	xBoundLeft,
			// 	xBoundRight,
			// 	imageScale,
			// ], ([x0, x1, x2, x3, x4, x5]) => { console.log(`panOffsetX: ${x0}, translateX: ${x1}, tempTranslateX: ${x2} xBoundLeft: ${x3}, xBoundRight: ${x4}, scale: ${x5}, screenWidth: ${screenWidth}`); }),
			// call([tempFocalX, imageScaleWidth, multiply(sub(tempFocalX, divide(screenWidth, 2)), -1)], ([f, w, x]) => { console.log(`tempFocalX: ${f}, imageScaleWidth: ${w}, test: ${x}`); }),
			// call([this.testValue], ([testv]) => { console.log(testv); }),
			// call([scaleFocusX, scaleFocusY, imageScale, imageScaleWidth, imageScaleHeight], ([sx, sy, s, sw, sh]) => { console.log(`scaleFocusX: ${sx}, scaleFocusY: ${sy}, scale: ${s}, imageScaleWidth: ${sw}, imageScaleHeight: ${sh}`); }),
			// call([swipePos, swipePosOffset], ([s]) => { console.log(`swipePos: ${s}, swipePosOffset: ${s}`); }),
			// call([swipePos, this.index, this.renderingPhotos], ([s, i, R]) => { console.log(`swipePos: ${s}, rendering: ${R},  nativeIndex: ${i}, jsIndex: ${this.state.index}`); }),
			// call([translateX, translateY, panOffsetX, panOffsetY], ([x, y, a, b]) => { console.log(`translateX: ${x}, translateY: ${y}, panOffsetX: ${a}, panOffsetY: ${b}`); }),
		]);

		// this.debugInfo = {
		// 	imageScale,
		// 	focalOffsetX,
		// 	focalOffsetY,
		// 	centerFocusX,
		// 	centerFocusY,
		// 	scaleFocusX,
		// 	scaleFocusY,
		// 	scaleTranslateX,
		// 	scaleTranslateY,
		// 	imageScaleWidth,
		// 	imageScaleHeight,
		// 	translateX,
		// 	translateY,
		// 	ratioX: divide(scaleFocusX, imageScaleWidth),
		// 	ratioY: divide(scaleFocusY, imageScaleHeight),
		// };
	}

	componentDidMount() {
		_.defer(() => {
			this.setupCurrentImageDimensions(this.props.initialIndex);
			// this.renderingPhotos.setValue(0);
		});
	}

	componentDidUpdate(prevProps) {
		const { photos, hasNext } = this.props;
		const { index } = this.state;
		if (prevProps.photos.length !== photos.length) {
			const {
				translateX, translateY, xBoundLeft, xBoundRight, imageScale, imageScaleOffset,
			} = this.animData;
			this.photosCount.setValue(photos.length);
			if (index >= photos.length) {
				const newIndex = photos.length - 1;
				const newPosOffset = getSwipeOffset(newIndex);
				this.setState({ index: newIndex }, () => {
					translateX.setValue(0);
					translateY.setValue(0);
					xBoundLeft.setValue(0);
					xBoundRight.setValue(0);
					imageScale.setValue(1);
					imageScaleOffset.setValue(1);
					this.isZooming.setValue(0);
					this.isSwipingLeft.setValue(0);
					this.isSwipingRight.setValue(0);
					this.index.setValue(newIndex);
					this.nextIndex.setValue(newIndex);
					this.currentPageSwipeOffset.setValue(newPosOffset);
					this.nextPageSwipeOffset.setValue(newPosOffset);
					this.props.onPageChange(newIndex);
				});
			}
		}
		if (prevProps.hasNext !== hasNext) {
			this.hasNext.setValue(hasNext ? 1 : 0);
		}
	}

	onImageLoad = ({ width, height, index }) => {
		const photo = this.props.photos[index];
		if (photo && !imageDimensions[photo.id]) {
			const { rotation } = photo;
			let adjustedWidth = screenWidth;
			let adjustedHeight;
			let originalWidth;
			let originalHeight;
			if (rotation === 0 || rotation % 180 === 0) {
				const aspectRatio = screenWidth / width;
				adjustedHeight = aspectRatio * height;
				originalWidth = width;
				originalHeight = height;
			} else {
				const aspectRatio = screenWidth / height;
				adjustedHeight = aspectRatio * width;
				originalWidth = height;
				originalHeight = width;
			}
			if (adjustedHeight > screenHeight) {
				const diffRatio = (adjustedHeight - screenHeight) / adjustedHeight;
				adjustedWidth -= (adjustedWidth * diffRatio);
				adjustedHeight = screenHeight;
			}
			const dimensions = {
				originalWidth,
				originalHeight,
				adjustedWidth,
				adjustedHeight,
			};
			imageDimensions[photo.id] = dimensions;
			if (index === this.state.index) {
				this.setImageDimensionValues(dimensions);
			}
		}
	}

	setupCurrentImageDimensions = (index) => {
		const currentPhoto = this.props.photos[index];
		const currentImageDimensions = currentPhoto && imageDimensions[currentPhoto.id];
		if (currentImageDimensions) {
			this.setImageDimensionValues(currentImageDimensions);
		}
	}

	setImageDimensionValues = ({
		adjustedWidth, adjustedHeight, originalWidth, originalHeight,
	}) => {
		let maxScale;
		if (originalHeight > originalWidth) {
			maxScale = originalHeight / screenHeight;
		} else {
			maxScale = originalWidth / screenWidth;
		}
		if (maxScale < 1) maxScale = 1;
		maxScale += 0.5;
		this.imageContainerWidth.setValue(adjustedWidth);
		this.imageContainerHeight.setValue(adjustedHeight);
		this.imageOriginalWidth.setValue(originalWidth);
		this.imageOriginalHeight.setValue(originalHeight);
		this.maxScale.setValue(maxScale);
	}

	renderContent = (index) => {
		const { photos, hasNext } = this.props;
		const { index: currentIndex } = this.state;
		const photo = photos[index];
		const {
			translateX, translateY, imageScale,
		} = this.animData;
		if (photo && index >= 0 && index < photos.length && photos[index] && photos[index].previewUrl) {
			let content;
			if (photo.videoUrl) {
				content = (<VideoContent
					item={photo}
					index={index}
					onImageLoad={this.onImageLoad}
					onVideoPlay={this.onVideoPlay}
					onVideoPause={() => { this.pauseVideoFn = null; }}
					onVideoError={this.onVideoError}
				/>);
			} else {
				content = (
					<PhotoContent
						item={photo}
						index={index}
						onImageLoad={this.onImageLoad}
					/>
				);
			}
			return (
				<Reanimated.View
					key={index}
					style={{
						position: 'absolute',
						// alignItems: 'center',
						// justifyContent: 'center',
						left: screenWidth * index,
						...(index === currentIndex ? {
							transform: [{
								translateX: cond(clockRunning(this.panClock), this.translateBounceX, translateX),
								translateY: cond(clockRunning(this.panClock), this.translateBounceY, translateY),
							}, { scale: cond(clockRunning(this.pinchClock), this.scaleBounce, imageScale) }],
						} : {}),
					}}
				>
					{content}
					{/* <View
						style={{
							position: 'absolute',
							zIndex: 1000,
							backgroundColor: '#00ffaa',
							borderRadius: 10,
							height: 10,
							width: 10,
						}}
					/> */}
				</Reanimated.View>
			);
		} else if (hasNext && index >= photos.length) {
			return (
				<View
					style={{
						position: 'absolute',
						left: screenWidth * index,
						width: screenWidth,
						height: screenHeight,
						alignItems: 'center',
						justifyContent: 'center',
					}}
				>
					<ActivityIndicator size="large" color={color.blue} />
				</View>
			);
		}
		return <View style={{ width: screenWidth, height: screenHeight }} />;
	}

	render() {
		const {
			swipePos,
			// focalOffsetX,
			// focalOffsetY,
			// scaleFocusX,
			// scaleFocusY,
		} = this.animData;
		const { index } = this.state;
		const prevIndex = index - 1;
		const nextIndex = index + 1;
		const pinchRef = React.createRef();
		const panRef = React.createRef();
		const tapRef = React.createRef();
		const doubleTapRef = React.createRef();
		return (
			<>
				<Reanimated.Code>{this.eventListener}</Reanimated.Code>
				<PanGestureHandler
					ref={panRef}
					onHandlerStateChange={this.panChangeState}
					onGestureEvent={this.panEvent}
					minDist={10}
					minPointers={1}
					maxPointers={1}
				>
					<Reanimated.View style={styles.container}>

						<PinchGestureHandler
							ref={pinchRef}
							onGestureEvent={this.pinchEvent}
							onHandlerStateChange={this.pinchChangeState}
						>
							<Reanimated.View style={styles.container}>
								<TapGestureHandler
									ref={tapRef}
									waitFor={doubleTapRef}
									onHandlerStateChange={({ nativeEvent }) => {
										if (nativeEvent.state === State.ACTIVE) {
											this.props.onTap(this.state.index);
										}
									}}
								>
									<Reanimated.View style={styles.container}>
										<TapGestureHandler
											ref={doubleTapRef}
											numberOfTaps={2}
											onHandlerStateChange={this.doubleTapChangeState}
										>
											<Reanimated.View style={styles.container}>
												<Reanimated.View
													style={{
														position: 'absolute',
														top: 0,
														left: 0,
														flexDirection: 'row',
														width: 1000,
														transform: [{ translateX: cond(clockRunning(this.swipeClock), this.swipeClockState.position, swipePos) }],
													}}
												>
													{this.renderContent(prevIndex)}
													{this.renderContent(index)}
													{this.renderContent(nextIndex)}
												</Reanimated.View>
												{/* <Reanimated.View
													style={{
														position: 'absolute',
														transform: [{ translateX: focalOffsetX, translateY: focalOffsetY }],
														borderWidth: 1,
														borderColor: '#00ffaa',
														borderRadius: 5,
														backgroundColor: 'white',
														top: -5,
														left: -5,
														height: 10,
														width: 10,
													}}
												/> */}
												{/* <Reanimated.View
													style={{
														position: 'absolute',
														transform: [{ translateX: scaleFocusX, translateY: scaleFocusY }],
														borderWidth: 1,
														borderColor: '#00ffaa',
														borderRadius: 16,
														backgroundColor: 'white',
														top: -8,
														left: -8,
														height: 16,
														width: 16,
													}}
												/> */}
												{/* <Debug
													info={this.debugInfo}
												/> */}
											</Reanimated.View>
										</TapGestureHandler>
									</Reanimated.View>
								</TapGestureHandler>
							</Reanimated.View>
						</PinchGestureHandler>
					</Reanimated.View>
				</PanGestureHandler>
			</>
		);
	}
}
