// ------------------------------------------
// Rellax.js - v1.0.0
// Buttery smooth parallax library
// Copyright (c) 2016 Moe Amaya (@moeamaya)
// MIT license
//
// Thanks to Paraxify.js and Jaime Cabllero
// for parallax concepts
// ------------------------------------------

import { getCurrentScreen } from '../helpers/current-screen'
import innerHeight from './ios-inner-height'

// Ahh a pure function, gets new transform value
// based on scrollPostion and speed
// Allow for decimal pixel values
const updatePosition = (percentage, speed) => speed * (100 * (1 - percentage))

// We want to cache the parallax blocks'
// values: base, top, height, speed
// el: is dom object, return: el cache values
const createBlock = ({
	el = null,
	speed = null,
	fitInsideContainer = null,
	isVisible = false,
	shouldSetHeightToIncrease = true,
	parallaxBehavior = 'desktop:tablet:mobile',
}) => {
	// Optional individual block speed as data attr, otherwise global speed
	// Check if has percentage attr, and limit speed to 5, else limit it to 10
	// The function is named clamp
	speed = speed <= -5 ? -5 : speed >= 5 ? 5 : speed

	// We need to guess the position the background will be, when the section
	// will reach the top of the viewport. This calculation will be based on the
	// speed for sure
	if (fitInsideContainer && shouldSetHeightToIncrease) {
		let heightWeWantToIncrease = 0

		if (speed > 0) {
			heightWeWantToIncrease = updatePosition(0.5, speed)
		} else {
			heightWeWantToIncrease =
				updatePosition(
					innerHeight() /
						(fitInsideContainer.clientHeight + innerHeight()),
					speed
				) - updatePosition(0.5, speed)
		}

		heightWeWantToIncrease = Math.abs(heightWeWantToIncrease) * 2

		if (!isVisible) {
			el.parentNode.removeAttribute('style')
		} else {
			el.parentNode.style.height = `calc(100% + ${heightWeWantToIncrease}px)`
		}
	}

	// initializing at scrollY = 0 (top of browser)
	// ensures elements are positioned based on HTML layout.

	let { top, height } = nullifyTransforms(
		fitInsideContainer ? fitInsideContainer : el
	)

	var blockTop = pageYOffset + top

	return {
		parallaxBehavior,
		shouldSetHeightToIncrease,
		fitInsideContainer,
		el,
		top: blockTop,
		height,
		speed,
		isVisible,
	}
}

function elementInViewport(el) {
	var rect = el.getBoundingClientRect()

	return (
		rect.bottom > -450 &&
		rect.top - 450 <
			(innerHeight() ||
				document.documentElement
					.clientHeight) /* or $(window).height() */
	)
}

function nullifyTransforms(el) {
	if (!el) return null

	//add sanity checks and default values

	let { top, left, right, width, height } = el.getBoundingClientRect()

	let transformArr = window
		.getComputedStyle(el)
		.transform.split(/\(|,|\)/)
		.slice(1, -1)
		.map((v) => parseFloat(v))

	if (transformArr.length != 6) {
		return el.getBoundingClientRect()
	}

	// 2D matrix
	// need some math to apply inverse of matrix
	// That is the matrix of the transformation of the element
	var t = transformArr
	let det = t[0] * t[3] - t[1] * t[2]

	/*if (transformArr.length > 6)*/
	//3D matrix
	//haven't done the calculation to apply inverse of 4x4 matrix

	return {
		width: width / t[0],
		height: height / t[3],
		left: (left * t[3] - top * t[2] + t[2] * t[5] - t[4] * t[3]) / det,
		right: (right * t[3] - top * t[2] + t[2] * t[5] - t[4] * t[3]) / det,
		top: (-left * t[1] + top * t[0] + t[4] * t[1] - t[0] * t[5]) / det,
	}
}

export class Rellax {
	constructor() {
		this.blocks = []
		this.oldPosY = false

		this.intersectionObserver = new IntersectionObserver(
			(entries) => {
				entries.map(
					({ target: el, isIntersecting, intersectionRatio }) => {
						let blocks = this.blocks.filter(
							({ fitInsideContainer, el: blockEl }) =>
								blockEl.closest('svg')
									? blockEl.closest('svg') === el
									: fitInsideContainer === el ||
									  blockEl === el
						)

						let hasNewBlock = false

						this.blocks = this.blocks.map((block) => {
							const isThisBlock = block.el.closest('svg')
								? block.el.closest('svg') === el
								: block.fitInsideContainer === el ||
								  block.el === el

							if (!isThisBlock) {
								return block
							}

							hasNewBlock = true

							return createBlock({
								...block,
								isVisible:
									isIntersecting &&
									block.parallaxBehavior.indexOf(
										getCurrentScreen({ withTablet: true })
									) > -1,
							})
						})

						if (hasNewBlock) {
							this.oldPosY = false
							this.animate()
						}
					}
				)
			},
			{
				rootMargin: '450px',
			}
		)

		// Start the loop
		this.update()

		// The loop does nothing if the scrollPosition did not change
		// so call animate to make sure every element has their transforms
		this.animate()
	}

	removeEl({ el }) {
		el.removeAttribute('style')
		this.blocks = this.blocks.filter(({ el: e }) => e !== el)
	}

	addEl({
		el,
		speed,
		fitInsideContainer = null,
		shouldSetHeightToIncrease = true,
		parallaxBehavior = 'desktop:tablet:mobile',
	}) {
		if (fitInsideContainer) {
			this.intersectionObserver.observe(fitInsideContainer)
		} else {
			this.intersectionObserver.observe(
				el.closest('svg') ? el.closest('svg') : el
			)
		}

		this.blocks.push(
			createBlock({
				el,
				speed,
				fitInsideContainer,
				isVisible:
					elementInViewport(
						fitInsideContainer ? fitInsideContainer : el
					) &&
					parallaxBehavior.indexOf(
						getCurrentScreen({ withTablet: true })
					) > -1,

				shouldSetHeightToIncrease,
				parallaxBehavior,
			})
		)
	}

	update() {
		if (!this.oldPosY && this.oldPosY !== 0) {
			this.animate()
		}

		if (this.setPosition()) {
			this.animate()
		}

		requestAnimationFrame(this.update.bind(this))
	}

	setPosition() {
		if (this.blocks.length === 0) return false

		let old = this.oldPosY
		this.oldPosY = pageYOffset

		return old != pageYOffset
	}

	animate() {
		this.blocks.map((block) => {
			if (!block.isVisible) {
				block.el.removeAttribute('style')
				return
			}

			var percentage =
				(pageYOffset - block.top + innerHeight()) /
				(block.height + innerHeight())

			let { top, height } = nullifyTransforms(
				block.fitInsideContainer ? block.fitInsideContainer : block.el
			)

			if (!height) {
				height = (
					block.fitInsideContainer
						? block.fitInsideContainer
						: block.el
				).getBoundingClientRect().height
			}

			const newPercentage =
				1 -
				(top +
					(block.el.dataset.percentage &&
					parseInt(block.el.dataset.percentage, 10) === 0
						? 0
						: height / 2)) /
					innerHeight()

			// Subtracting initialize value, so element stays in same spot as HTML
			var position =
				updatePosition(
					block.fitInsideContainer ? percentage : newPercentage,
					block.speed
				) -
				updatePosition(
					block.el.dataset.percentage
						? parseInt(block.el.dataset.percentage, 10)
						: 0.5,
					block.speed
				)

			// Move that element
			block.el.style.transform = `translate3d(0, ${position}px, 0)`
		})
	}
}
