import React from 'react';
import PropTypes from "prop-types";

import IDUtil from '../../../util/IDUtil';
import IconUtil from '../../../util/IconUtil';

import MediaEvents from '../_MediaEvents';

import { AnnotationEvents } from '../AnnotationClient';
import { ResourceViewerContext } from '../ResourceViewerContext';
import classNames from 'classnames';

//TODO only add a function to enforce adding a NEW annotation
//TODO test/implement highlighting the initial search result
//TODO prevent zooming when clicking on the overlay

export default class ImageViewer extends React.PureComponent {
	static contextType = ResourceViewerContext;

	constructor(props) {
		super(props);

		this.viewer = null;
		this.selector = null; //needed to access picturae selector
		this.annotationOverlays = [];

		this.CLASS_PREFIX = 'imv';
		this.SEARCH_TERM_OVERLAY_ID = 'search-term-highlight';
		this.highlightOverlays = [];
		this.OVERLAY_TIMEOUT = 300; //FIXME find the right event to know when it's ok to draw an overlay
	}

	componentDidMount = async() => {
		await this.initViewer();
		this.viewer.removeAllHandlers('open');
		this.__showSearchHighlight(this.context.activeMediaObject);
		this.props.annotationClient.events.bind(AnnotationEvents.ON_SET_ANNOTATION, this.onSetActiveAnnotation);
		this.props.annotationClient.events.bind(AnnotationEvents.ON_CHANGE_TARGET, this.onChangeTarget);
		this.props.annotationClient.events.bind(AnnotationEvents.ON_DELETE, this.onDeleteAnnotation);
		this.props.annotationClient.events.bind(AnnotationEvents.ON_SAVE, this.onSaveAnnotation);

		this.context.mediaEvents.bind(MediaEvents.ACTIVE_MEDIA_OBJECT, this.onSetMediaObject);
	};

	componentWillUnmount = () => {
		this.props.annotationClient.events.unbind(AnnotationEvents.ON_SET_ANNOTATION, this.onSetActiveAnnotation);
		this.props.annotationClient.events.unbind(AnnotationEvents.ON_CHANGE_TARGET, this.onChangeTarget);
		this.props.annotationClient.events.unbind(AnnotationEvents.ON_DELETE, this.onDeleteAnnotation);
		this.props.annotationClient.events.unbind(AnnotationEvents.ON_SAVE, this.onSaveAnnotation);

		this.context.mediaEvents.unbind(MediaEvents.ACTIVE_MEDIA_OBJECT, this.onSetMediaObject);
	};

	/* --------------------------------------------------------------
	-------------------------- VIEWER INITIALIZATION ----------------
	---------------------------------------------------------------*/

	__toOSDUrl = mediaObject => {
		const index = mediaObject.url.indexOf('.tif'); //FIXME very weak way to check if it's IIIF!
		let moClone = JSON.parse(JSON.stringify(mediaObject));
		if(index === -1) {
			moClone.infoUrl = mediaObject.url;
		} else {
			moClone.infoUrl = mediaObject.url.substring(0, index + 4) + '/info.json';
    	}
    	return moClone;
	};

	__showSearchHighlight = mediaObject => {
		//always remove the old highlights
		this.highlightOverlays.forEach(o => {
			this.viewer.removeOverlay(o);
		});
		this.highlightOverlays = [];

		//only continue if there is data
		if(!mediaObject || !mediaObject.mediaFragments) return;

		//draw the highlight overlays (and store their ids for reference)
        for( let i = 0; i < mediaObject.mediaFragments.length; i++){
            const r = this.viewer.viewport.imageToViewportRectangle(
                parseInt(mediaObject.mediaFragments[i].x),
                parseInt(mediaObject.mediaFragments[i].y),
                parseInt(mediaObject.mediaFragments[i].w),
                parseInt(mediaObject.mediaFragments[i].h)
            );
            const elt = document.createElement("div");
            elt.id = IDUtil.guid();
            elt.className = IDUtil.cssClassName('highlight', this.CLASS_PREFIX);
            this.highlightOverlays.push(elt.id + '');
            this.viewer.addOverlay(elt, r);
        }
	};

	__getPageNumberForMediaObject = mediaObject => mediaObject ? this.props.mediaObjects.findIndex(
		mo => mo.contentId == mediaObject.contentId
	) : 0;

	//triggered  only whenever the media object was changed in the playlist dropdown
	onSetMediaObject = mediaObject => {
		this.viewer.goToPage(this.__getPageNumberForMediaObject(this.context.activeMediaObject));
	};

	//first load the viewer using the provided this.props.mediaObjects
	initViewer = () => {
		//map the media objects to sources OpenSeaDragon likes
		const sources = this.props.mediaObjects.map(mo => {
			return this.__toOSDUrl(mo);
		});

		this.viewer = OpenSeadragon({
			id: 'img_viewer' ,
			prefixUrl: '/static/node_modules/openseadragon/build/openseadragon/images/',
			showSelectionControl: true,
			showRotationControl: true,
			sequenceMode : true,
			preserveViewport: true,
			autoHideControls: false,
			height: '100px',
			ajaxWithCredentials : this.props.useCredentials,
			tileSources: sources.map(s => s.infoUrl),
			//Note: the initial active media object is determined in ResourceViewer.determineInitialMediaObject()
			initialPage : this.__getPageNumberForMediaObject(this.context.activeMediaObject)
		});

		//make sure the selection button tooltips have translations (otherwise annoying debug messages)
		OpenSeadragon.setString('Tooltips.SelectionToggle', 'Toggle selection');
		OpenSeadragon.setString('Tooltips.SelectionConfirm', 'Confirm selection');

		//add the selection (rectangle) support (Picturae plugin)
		if(this.props.annotationClient.config.editOptions.Segment) { //TODO change this check later on ()
			this.selector = this.viewer.selection({
				showConfirmDenyButtons: true,
				styleConfirmDenyButtons: true,
				returnPixelCoordinates: true,
				//crossOriginPolicy: 'Anonymous',
				keyboardShortcut: 'c', // key to toggle selection mode
				rect: null, // initial selection as an OpenSeadragon.SelectionRect object
				startRotated: false, // alternative method for drawing the selection; useful for rotated crops
				startRotatedHeight: 0.1, // only used if startRotated=true; value is relative to image height
				restrictToImage: false, // true = do not allow any part of the selection to be outside the image
				onSelection: this.onSelection,
				prefixUrl: '/static/vendor/openseadragonselection-master/images/',
				navImages: { // overwrites OpenSeadragon's options
					selection: {
						REST:   'selection_rest.png',
						GROUP:  'selection_grouphover.png',
						HOVER:  'selection_hover.png',
						DOWN:   'selection_pressed.png'
					},
					selectionConfirm: {
						REST:   'selection_confirm_rest.png',
						GROUP:  'selection_confirm_grouphover.png',
						HOVER:  'selection_confirm_hover.png',
						DOWN:   'selection_confirm_pressed.png'
					},
					selectionCancel: {
						REST:   'selection_cancel_rest.png',
						GROUP:  'selection_cancel_grouphover.png',
						HOVER:  'selection_cancel_hover.png',
						DOWN:   'selection_cancel_pressed.png'
					},
				}
			});

			const onPage = async(e) => {
				const mediaObjectPage = this.__getPageNumberForMediaObject(this.context.activeMediaObject);
				//only set the media object if navigating via the OSD arrows
				//when navigating via the PlayListDropDown, the active media object is already set
				if(e.page !== mediaObjectPage) {
					await this.context.setActiveMediaObject(this.props.mediaObjects[e.page]);
				}
				//FIXME wait for the image to be loaded instead of this ugly timout
				setTimeout( () => {
					this.__showSearchHighlight(this.context.activeMediaObject);
				}, this.OVERLAY_TIMEOUT);

				// The imageLabelRef hack is due to a bug in OpenSeadragon see: https://github.com/openseadragon/openseadragon/issues/774
				//this.props.imageLabelRef.current.style.display = '';
				//this.viewer.addOverlay("html-overlay", new OpenSeadragon.Point(0.5, -0.05), OpenSeadragon.Placement.CENTER);
			};

			//make sure the highlights are updated per page/image, annotations are triggered via onChangeTarget
			this.viewer.addHandler('page', onPage);

            //Disable zooming on mouse click
            this.viewer.gestureSettingsMouse.clickToZoom = false;

            //Add the overlay box
		    //this.viewer.addOverlay("html-overlay", new OpenSeadragon.Point(0.5, -0.05), OpenSeadragon.Placement.CENTER);

			//return a promise, after the viewer has opened its first image, so an initial overlay can be drawn
			return new Promise(resolve => {
				this.viewer.addHandler('open', (target, info) => {
					this.renderAnnotations();
    				resolve("viewer loaded");
				});
			});

			//for debugging only
			/*this.viewer.addHandler('canvas-click', (target, info) => {
		        // The canvas-click event gives us a position in web coordinates.
		        const webPoint = target.position;
		        // Convert that to viewport coordinates, the lingua franca of OpenSeadragon coordinates.
		        const viewportPoint = this.viewer.viewport.pointFromPixel(webPoint);
		        // Convert from viewport coordinates to image coordinates.
		        const imagePoint = this.viewer.viewport.viewportToImageCoordinates(viewportPoint);
		        // Show the results.
		        console.log(webPoint.toString(), viewportPoint.toString(), imagePoint.toString());
		    });*/
		}
	};

	//whenever the annotation target (possibly the viewed image here) has changed
	onChangeTarget = target => setTimeout( () => {this.renderAnnotations()}, this.OVERLAY_TIMEOUT);

	onSaveAnnotation = annotation => {
		this.renderAnnotations();
		if(this.selector) this.selector.toggleState();
	};

	clearAnnotationOverlays = () => {
		this.annotationOverlays.forEach(overlayId => {
			this.viewer.removeOverlay(overlayId);
		});
		this.annotationOverlays = [];
	};

	renderAnnotations = () => {
	   	this.clearAnnotationOverlays();

		if(this.context.activeProject === null) return; //if no project is selected don't draw the annotations

		const annotations = this.props.annotationClient.annotations || [];
		annotations.forEach(annotation => {
			const overlayId = this.renderAnnotationOverlay(annotation);
			if(overlayId) {
				this.annotationOverlays.push(overlayId);
			}
		});
	};

	//TODO figure out how to do bind with extra param in ES6 way
	deleteAnnotation(annotation, e) {
		if(e) { //FIXME prevent zoom (this solution does not work...)
			e.preventDefault();
			e.stopPropagation();
		}
		this.props.annotationClient.delete(annotation);
	}

	onDeleteAnnotation = annotation => { //remove the overlay, once the annotation was deleted from the server
		this.viewer.removeOverlay(annotation.id);
	};

	/* --------------------------------------------------------------
	-------------------------- ANNOTATION CRUD ----------------------
	---------------------------------------------------------------*/

	onSelection = async(rect) => { //first set the active selection in the annotation client
		if(this.context.activeProject === null) return; //you cannot make a segment (annotation) without an active project

		await this.props.annotationClient.setActiveSelection({
            rect : {
                x : rect.x,
                y : rect.y,
                w : rect.width,
                h : rect.height
            },
            rotation : rect.rotation
		});
		
		// layer id
		const activeAnnotation = this.props.annotationClient.activeAnnotation;
		const layerId = activeAnnotation && activeAnnotation.target && activeAnnotation.target.layerId ? activeAnnotation.target.layerId : 0;
        this.props.annotationClient.saveSelection(null, false, true, layerId);
	};

	onSetActiveAnnotation = eventData => {
		this.renderAnnotations();
	};

	renderAnnotationOverlay = annotation => {
		//make sure the annotation has  a spatial segment in the target
		if(annotation.target && annotation.target.selector && annotation.target.selector.refinedBy) {
			const selectedArea = annotation.target.selector.refinedBy.rect;
			const translatedArea = this.viewer.viewport.imageToViewportRectangle(
				parseInt(selectedArea.x),
				parseInt(selectedArea.y),
				parseInt(selectedArea.w),
				parseInt(selectedArea.h)
			);

			//the actual overlay div
			const overlay = document.createElement('div');
			const classNames = [
				IDUtil.cssClassName('overlay', this.CLASS_PREFIX)
			]
			if(this.props.annotationClient.activeAnnotation && annotation.id == this.props.annotationClient.activeAnnotation.id) {
				classNames.push('active');
			}
			overlay.className = classNames.join(' ');
			overlay.onclick= this.props.annotationClient.setActiveAnnotation.bind(this, annotation);
			overlay.ondblclick = this.props.annotationClient.edit.bind(this, annotation);
			overlay.id = annotation.id;

			//add the remove button
			const removeBtn = document.createElement('button');
			removeBtn.onclick = this.deleteAnnotation.bind(this, annotation);
			const removeIcon = document.createElement('span');
			removeIcon.className = IconUtil.getUserActionIcon('remove');
			removeBtn.appendChild(removeIcon);

			overlay.appendChild(removeBtn);

			this.viewer.addOverlay({
				element: overlay,
				location: translatedArea
			});
			return annotation.id;
		}
		return null;
	}

	render = () => <div id="img_viewer" className={classNames(IDUtil.cssClassName('image-viewer'),{ multiple: this.props.mediaObjects.length > 1 })}></div>;

}

ImageViewer.propTypes = {
    useCredentials: PropTypes.bool,
    mediaObjects: PropTypes.array,
    annotationClient: PropTypes.object
};
