/**
 * Handles the display of reveal.js' overlay elements used
 * to preview iframes, images & videos.
 */
export default class Overlay {

	constructor( Reveal ) {

		this.Reveal = Reveal;

		this.onSlidesClicked = this.onSlidesClicked.bind( this );

		this.iframeTriggerSelector = null;
		this.mediaTriggerSelector = '[data-preview-image], [data-preview-video]';

		this.stateProps = ['previewIframe', 'previewImage', 'previewVideo', 'previewFit'];
		this.state = {};

	}

	update() {

		// Enable link previews globally
		if( this.Reveal.getConfig().previewLinks ) {
			this.iframeTriggerSelector = 'a[href]:not([data-preview-link=false]), [data-preview-link]:not(a):not([data-preview-link=false])';
		}
		// Enable link previews for individual elements
		else {
			this.iframeTriggerSelector = '[data-preview-link]:not([data-preview-link=false])';
		}

		const hasLinkPreviews = this.Reveal.getSlidesElement().querySelectorAll( this.iframeTriggerSelector ).length > 0;
		const hasMediaPreviews = this.Reveal.getSlidesElement().querySelectorAll( this.mediaTriggerSelector ).length > 0;

		// Only add the listener when there are previewable elements in the slides
		if( hasLinkPreviews || hasMediaPreviews ) {
			this.Reveal.getSlidesElement().addEventListener( 'click', this.onSlidesClicked, false );
		}
		else {
			this.Reveal.getSlidesElement().removeEventListener( 'click', this.onSlidesClicked, false );
		}

	}

	createOverlay( className ) {

		this.dom = document.createElement( 'div' );
		this.dom.classList.add( 'r-overlay' );
		this.dom.classList.add( className );

		this.viewport = document.createElement( 'div' );
		this.viewport.classList.add( 'r-overlay-viewport' );

		this.dom.appendChild( this.viewport );
		this.Reveal.getRevealElement().appendChild( this.dom );

	}

	/**
	 * Opens a lightbox that previews the target URL.
	 *
	 * @param {string} url - url for lightbox iframe src
	 */
	previewIframe( url ) {

		this.close();

		this.state = { previewIframe: url };

		this.createOverlay( 'r-overlay-preview' );
		this.dom.dataset.state = 'loading';

		this.viewport.innerHTML =
			`<header class="r-overlay-header">
				<a class="r-overlay-button r-overlay-external" href="${url}" target="_blank"><span class="icon"></span></a>
				<button class="r-overlay-button r-overlay-close"><span class="icon"></span></button>
			</header>
			<div class="r-overlay-spinner"></div>
			<div class="r-overlay-content">
				<iframe src="${url}"></iframe>
				<small class="r-overlay-content-inner">
					<span class="r-overlay-error x-frame-error">Unable to load iframe. This is likely due to the site's policy (x-frame-options).</span>
				</small>
			</div>`;

		this.dom.querySelector( 'iframe' ).addEventListener( 'load', event => {
			this.dom.dataset.state = 'loaded';
		}, false );

		this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', event => {
			this.close();
			event.preventDefault();
		}, false );

		this.dom.querySelector( '.r-overlay-external' ).addEventListener( 'click', event => {
			this.close();
		}, false );

		this.Reveal.dispatchEvent({ type: 'previewiframe', data: { url } });

	}

	/**
	 * Opens a lightbox window that provides a larger view of the
	 * given image/video.
	 *
	 * @param {string} url - url to the image/video to preview
	 * @param {image|video} mediaType
	 * @param {string} [fitMode] - the fit mode to use for the preview
	 */
	previewMedia( url, mediaType, fitMode ) {

		if( mediaType !== 'image' && mediaType !== 'video' ) {
			console.warn( 'Please specify a valid media type to preview (image|video)' );
			return;
		}

		this.close();

		fitMode = fitMode || 'scale-down';

		this.createOverlay( 'r-overlay-preview' );
		this.dom.dataset.state = 'loading';
		this.dom.dataset.previewFit = fitMode;

		this.viewport.innerHTML =
			`<header class="r-overlay-header">
				<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
			</header>
			<div class="r-overlay-spinner"></div>
			<div class="r-overlay-content"></div>`;

		const contentElement = this.dom.querySelector( '.r-overlay-content' );

		if( mediaType === 'image' ) {

			this.state = { previewImage: url, previewFit: fitMode }

			const img = document.createElement( 'img', {} );
			img.src = url;
			contentElement.appendChild( img );

			img.addEventListener( 'load', () => {
				this.dom.dataset.state = 'loaded';
			}, false );

			img.addEventListener( 'error', () => {
				this.dom.dataset.state = 'error';
				contentElement.innerHTML =
						`<span class="r-overlay-error">Unable to load image.</span>`
			}, false );

			// Hide image overlays when clicking outside the overlay
			this.dom.style.cursor = 'zoom-out';
			this.dom.addEventListener( 'click', ( event ) => {
				this.close();
			}, false );

			this.Reveal.dispatchEvent({ type: 'previewimage', data: { url } });

		}
		else if( mediaType === 'video' ) {

			this.state = { previewVideo: url, previewFit: fitMode }

			const video = document.createElement( 'video' );
			video.autoplay = this.dom.dataset.previewAutoplay === 'false' ? false : true;
			video.controls = this.dom.dataset.previewControls === 'false' ? false : true;
			video.loop = this.dom.dataset.previewLoop === 'true' ? true : false;
			video.muted = this.dom.dataset.previewMuted === 'true' ? true : false;
			video.playsInline = true;
			video.src = url;
			contentElement.appendChild( video );

			video.addEventListener( 'loadeddata', () => {
				this.dom.dataset.state = 'loaded';
			}, false );

			video.addEventListener( 'error', () => {
				this.dom.dataset.state = 'error';
				contentElement.innerHTML =
					`<span class="r-overlay-error">Unable to load video.</span>`;
			}, false );

			this.Reveal.dispatchEvent({ type: 'previewvideo', data: { url } });

		}
		else {
			throw new Error( 'Please specify a valid media type to preview' );
		}

		this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', ( event ) => {
			this.close();
			event.preventDefault();
		}, false );

	}

	previewImage( url, fitMode ) {

		this.previewMedia( url, 'image', fitMode );

	}

	previewVideo( url, fitMode ) {

		this.previewMedia( url, 'video', fitMode );

	}

	/**
	 * Open or close help overlay window.
	 *
	 * @param {Boolean} [override] Flag which overrides the
	 * toggle logic and forcibly sets the desired state. True means
	 * help is open, false means it's closed.
	 */
	toggleHelp( override ) {

		if( typeof override === 'boolean' ) {
			override ? this.showHelp() : this.close();
		}
		else {
			if( this.dom ) {
				this.close();
			}
			else {
				this.showHelp();
			}
		}
	}

	/**
	 * Opens an overlay window with help material.
	 */
	showHelp() {

		if( this.Reveal.getConfig().help ) {

			this.close();

			this.createOverlay( 'r-overlay-help' );

			let html = '<p class="title">Keyboard Shortcuts</p>';

			let shortcuts = this.Reveal.keyboard.getShortcuts(),
				bindings = this.Reveal.keyboard.getBindings();

			html += '<table><th>KEY</th><th>ACTION</th>';
			for( let key in shortcuts ) {
				html += `<tr><td>${key}</td><td>${shortcuts[ key ]}</td></tr>`;
			}

			// Add custom key bindings that have associated descriptions
			for( let binding in bindings ) {
				if( bindings[binding].key && bindings[binding].description ) {
					html += `<tr><td>${bindings[binding].key}</td><td>${bindings[binding].description}</td></tr>`;
				}
			}

			html += '</table>';

			this.viewport.innerHTML = `
				<header class="r-overlay-header">
					<button class="r-overlay-button r-overlay-close">Esc <span class="icon"></span></button>
				</header>
				<div class="r-overlay-content">
					<div class="r-overlay-help-content">${html}</div>
				</div>
			`;

			this.dom.querySelector( '.r-overlay-close' ).addEventListener( 'click', event => {
				this.close();
				event.preventDefault();
			}, false );

			this.Reveal.dispatchEvent({ type: 'showhelp' });

		}

	}

	isOpen() {

		return !!this.dom;

	}

	/**
	 * Closes any currently open overlay.
	 */
	close() {

		if( this.dom ) {
			this.dom.remove();
			this.dom = null;

			this.state = {};

			this.Reveal.dispatchEvent({ type: 'closeoverlay' });

			return true;
		}

		return false;

	}

	getState() {

		return this.state;

	}

	setState( state ) {

		// Ignore the incoming state if none of the preview related
		// props have changed
		if( this.stateProps.every( key => this.state[ key ] === state[ key ] ) ) {
			return;
		}

		if( state.previewIframe ) {
			this.previewIframe( state.previewIframe );
		}
		else if( state.previewImage ) {
			this.previewImage( state.previewImage, state.previewFit );
		}
		else if( state.previewVideo ) {
			this.previewVideo( state.previewVideo, state.previewFit );
		}
		else {
			this.close();
		}

	}

	onSlidesClicked( event ) {

		const target = event.target;

		const linkTarget = target.closest( this.iframeTriggerSelector );
		const mediaTarget = target.closest( this.mediaTriggerSelector );

		// Was an iframe lightbox trigger clicked?
		if( linkTarget ) {
			if( event.metaKey || event.shiftKey || event.altKey ) {
				// Let the browser handle meta keys naturally so users can cmd+click
				return;
			}
			let url = linkTarget.getAttribute( 'href' ) || linkTarget.getAttribute( 'data-preview-link' );
			if( url ) {
				this.previewIframe( url );
				event.preventDefault();
			}
		}
		// Was a media lightbox trigger clicked?
		else if( mediaTarget ) {
			if( mediaTarget.hasAttribute( 'data-preview-image' ) ) {
				let url = mediaTarget.dataset.previewImage || mediaTarget.getAttribute( 'src' );
				if( url ) {
					this.previewImage( url, mediaTarget.dataset.previewFit );
					event.preventDefault();
				}
			}
			else if( mediaTarget.hasAttribute( 'data-preview-video' ) ) {
				let url = mediaTarget.dataset.previewVideo || mediaTarget.getAttribute( 'src' );
				if( !url ) {
					let source = mediaTarget.querySelector( 'source' );
					if( source ) {
						url = source.getAttribute( 'src' );
					}
				}
				if( url ) {
					this.previewVideo( url, mediaTarget.dataset.previewFit );
					event.preventDefault();
				}
			}
		}

	}

	destroy() {

		this.close();

	}

}