/*!
 * Jodit Editor (https://xdsoft.net/jodit/)
 * Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
 * For GPL see LICENSE-GPL.txt in the project root for license information.
 * For MIT see LICENSE-MIT.txt in the project root for license information.
 * For commercial licenses see https://xdsoft.net/jodit/commercial/
 * Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
 */

import { Config } from '../Config';
import * as consts from '../constants';
import { IS_IE } from '../constants';
import { IBound } from '../types/types';
import { Dom } from '../modules/Dom';
import { $$ } from '../modules/helpers/selector';
import { debounce, setTimeout } from '../modules/helpers/async';
import { offset, innerWidth } from '../modules/helpers/size';
import { css } from '../modules/helpers';
import { IJodit } from '../types';

/**
 * The module creates a supporting frame for resizing of the elements img and table
 * @module Resizer
 * @params {Object} parent Jodit main object
 */
/**
 * @property{boolean} useIframeResizer=true Use true frame for editing iframe size
 */
declare module '../Config' {
	interface Config {
		useIframeResizer: boolean;
		useTableResizer: boolean;
		useImageResizer: boolean;

		resizer: {
			showSize: boolean;
			hideSizeTimeout: number;
			min_width: number;
			min_height: number;
		};
	}
}
Config.prototype.useIframeResizer = true;

/**
 * @property{boolean} useTableResizer=true Use true frame for editing table size
 */
Config.prototype.useTableResizer = true;

/**
 * @property{boolean} useImageResizer=true Use true image editing frame size
 */
Config.prototype.useImageResizer = true;

/**
 * @property {object} resizer
 * @property {int} resizer.min_width=10 The minimum width for the editable element
 * @property {int} resizer.min_height=10 The minimum height for the item being edited
 * @property {boolean} resizer.showSize=true Show size
 */
Config.prototype.resizer = {
	showSize: true,
	hideSizeTimeout: 1000,
	min_width: 10,
	min_height: 10
};

/**
 * Resize table and img
 * @param {Jodit} editor
 */
export function resizer(editor: IJodit) {
	const LOCK_KEY = 'resizer';

	let
		handle: HTMLElement,
		currentElement: null | HTMLElement,
		resizeElementClicked: boolean = false,
		isResizing: boolean = false,
		start_x: number,
		start_y: number,
		width: number,
		height: number,
		ratio: number,
		new_h: number,
		new_w: number,
		diff_x: number,
		diff_y: number,
		resizerIsVisible: boolean = false,
		timeoutSizeViewer: number = 0;

	const
		resizerElm: HTMLElement = editor.create.fromHTML(
			'<div data-editor_id="' +
				editor.id +
				'" style="display:none" class="jodit_resizer">' +
				'<i class="jodit_resizer-topleft"></i>' +
				'<i class="jodit_resizer-topright"></i>' +
				'<i class="jodit_resizer-bottomright"></i>' +
				'<i class="jodit_resizer-bottomleft"></i>' +
				'<span>100x100</span>' +
				'</div>'
		),

		sizeViewer: HTMLSpanElement = resizerElm.getElementsByTagName(
			'span'
		)[0],

		hideResizer = () => {
			isResizing = false;
			resizerIsVisible = false;
			currentElement = null;
			resizerElm.style.display = 'none';
		},

		hideSizeViewer = () => {
			sizeViewer.style.opacity = '0';
		},

		showSizeViewer = (w: number, h: number) => {
			if (!editor.options.resizer.showSize) {
				return;
			}

			if (w < sizeViewer.offsetWidth || h < sizeViewer.offsetHeight) {
				hideSizeViewer();
				return;
			}

			sizeViewer.style.opacity = '1';
			sizeViewer.innerHTML = `${w} x ${h}`;

			clearTimeout(timeoutSizeViewer);
			timeoutSizeViewer = setTimeout(
				hideSizeViewer,
				editor.options.resizer.hideSizeTimeout
			);
		},

		updateSize = () => {
			if (resizerIsVisible && currentElement && resizerElm) {
				const
					workplacePosition: IBound = offset(
						(resizerElm.parentNode ||
							editor.ownerDocument
								.documentElement) as HTMLElement,
						editor,
						editor.ownerDocument,
						true
					),
					pos: IBound = offset(
						currentElement,
						editor,
						editor.editorDocument
					),
					left: number = parseInt(resizerElm.style.left || '0', 10),
					top: number = parseInt(resizerElm.style.top || '0', 10),
					w: number = resizerElm.offsetWidth,
					h: number = resizerElm.offsetHeight;

				// 1 - because need move border higher and toWYSIWYG the left than the picture
				// 2 - in box-sizing: border-box mode width is real width indifferent by border-width.

				const newTop: number = pos.top - 1 - workplacePosition.top,
					newLeft: number = pos.left - 1 - workplacePosition.left;

				if (
					top !== newTop ||
					left !== newLeft ||
					w !== currentElement.offsetWidth ||
					h !== currentElement.offsetHeight
				) {
					resizerElm.style.top = newTop + 'px';
					resizerElm.style.left = newLeft + 'px';
					resizerElm.style.width = currentElement.offsetWidth + 'px';
					resizerElm.style.height =
						currentElement.offsetHeight + 'px';

					if (editor.events) {
						editor.events.fire(currentElement, 'changesize');

						// check for first init. Ex. inlinePopup hides when it was fired
						if (!isNaN(left)) {
							editor.events.fire('resize');
						}
					}
				}
			}
		},

		showResizer = () => {
			if (editor.options.readonly) {
				return;
			}

			if (!resizerElm.parentNode) {
				editor.workplace.appendChild(resizerElm);
			}

			resizerIsVisible = true;
			resizerElm.style.display = 'block';

			if (editor.isFullSize()) {
				resizerElm.style.zIndex = css(
					editor.container,
					'zIndex'
				).toString();
			}

			updateSize();
		},

		/**
		 * Bind an edit element toWYSIWYG element
		 * @param {HTMLElement} element The element that you want toWYSIWYG add a function toWYSIWYG resize
		 */
		bind = (element: HTMLElement) => {
			let wrapper: HTMLElement;
			if (element.tagName === 'IFRAME') {
				const iframe = element;

				if (
					element.parentNode &&
					(element.parentNode as HTMLElement).getAttribute(
						'data-jodit_iframe_wrapper'
					)
				) {
					element = element.parentNode as HTMLElement;
				} else {
					wrapper = editor.create.inside.fromHTML(
						'<jodit ' +
							'data-jodit-temp="1" ' +
							'contenteditable="false" ' +
							'draggable="true" ' +
							'data-jodit_iframe_wrapper="1"' +
							'></jodit>'
					);

					wrapper.style.display =
						element.style.display === 'inline-block'
							? 'inline-block'
							: 'block';
					wrapper.style.width = element.offsetWidth + 'px';
					wrapper.style.height = element.offsetHeight + 'px';

					if (element.parentNode) {
						element.parentNode.insertBefore(wrapper, element);
					}

					wrapper.appendChild(element);

					element = wrapper;
				}

				editor.events
					.off(element, 'mousedown.select touchstart.select')
					.on(element, 'mousedown.select touchstart.select', () => {
						editor.selection.select(element);
					});

				editor.events
					.off(element, 'changesize')
					.on(element, 'changesize', () => {
						iframe.setAttribute(
							'width',
							element.offsetWidth + 'px'
						);
						iframe.setAttribute(
							'height',
							element.offsetHeight + 'px'
						);
					});
			}

			let timer: number;

			editor.events
				.on(element, 'dragstart', hideResizer)
				.on(element, 'mousedown', (event: MouseEvent) => {
					// for IE don't show native resizer
					if (IS_IE && element.nodeName === 'IMG') {
						event.preventDefault();
					}
				})
				.on(element, 'mousedown touchstart', () => {
					if (!resizeElementClicked) {
						resizeElementClicked = true;
						currentElement = element;

						showResizer();

						if (
							currentElement.tagName === 'IMG' &&
							!(currentElement as HTMLImageElement).complete
						) {
							currentElement.addEventListener(
								'load',
								function ElementOnLoad() {
									updateSize();
									if (currentElement) {
										currentElement.removeEventListener(
											'load',
											ElementOnLoad
										);
									}
								}
							);
						}
						clearTimeout(timer);
					}

					timer = setTimeout(() => {
						resizeElementClicked = false;
					}, 400);
				});
		};

	// resizeElement = {};

	$$('i', resizerElm).forEach((resizeHandle: HTMLElement) => {
		editor.events.on(
			resizeHandle,
			'mousedown touchstart',
			(e: MouseEvent): false | void => {
				if (!currentElement || !currentElement.parentNode) {
					hideResizer();
					return false;
				}

				// resizeElementClicked = false;
				handle = resizeHandle;

				e.preventDefault();
				e.stopImmediatePropagation();

				width = currentElement.offsetWidth;
				height = currentElement.offsetHeight;
				ratio = width / height;

				// clicked = true;
				isResizing = true;
				// resized = false;

				start_x = e.clientX;
				start_y = e.clientY;
				editor.events.fire('hidePopup');
				editor.lock(LOCK_KEY);
			}
		);
	});

	editor.events
		.on('readonly', (isReadOnly: boolean) => {
			if (isReadOnly) {
				hideResizer();
			}
		})
		.on('beforeDestruct', () => {
			Dom.safeRemove(resizerElm);
		})
		.on('afterInit', () => {
			editor.events
				.on(editor.editor, 'keydown', (e: KeyboardEvent) => {
					if (
						resizerIsVisible &&
						e.which === consts.KEY_DELETE &&
						currentElement &&
						currentElement.tagName.toLowerCase() !== 'table'
					) {
						if (currentElement.tagName !== 'JODIT') {
							editor.selection.select(currentElement);
						} else {
							Dom.safeRemove(currentElement);

							hideResizer();

							e.preventDefault();
						}
					}
				})
				.on(
					editor.ownerWindow,
					'mousemove touchmove',
					(e: MouseEvent) => {
						if (isResizing) {
							diff_x = e.clientX - start_x;
							diff_y = e.clientY - start_y;

							if (!currentElement) {
								return;
							}

							const className: string = handle.className;

							if ('IMG' === currentElement.tagName) {
								if (diff_x) {
									new_w =
										width +
										(className.match(/left/) ? -1 : 1) *
											diff_x;
									new_h = Math.round(new_w / ratio);
								} else {
									new_h =
										height +
										(className.match(/top/) ? -1 : 1) *
											diff_y;
									new_w = Math.round(new_h * ratio);
								}

								if (
									new_w >
									innerWidth(
										editor.editor,
										editor.ownerWindow
									)
								) {
									new_w = innerWidth(
										editor.editor,
										editor.ownerWindow
									);
									new_h = Math.round(new_w / ratio);
								}
							} else {
								new_w =
									width +
									(className.match(/left/) ? -1 : 1) * diff_x;
								new_h =
									height +
									(className.match(/top/) ? -1 : 1) * diff_y;
							}

							if (new_w > editor.options.resizer.min_width) {
								if (
									new_w <
									(resizerElm.parentNode as HTMLElement)
										.offsetWidth
								) {
									currentElement.style.width = new_w + 'px';
								} else {
									currentElement.style.width = '100%';
								}
							}

							if (new_h > editor.options.resizer.min_height) {
								currentElement.style.height = new_h + 'px';
							}

							updateSize();

							showSizeViewer(
								currentElement.offsetWidth,
								currentElement.offsetHeight
							);

							e.stopImmediatePropagation();
						}
					}
				)
				.on(editor.ownerWindow, 'resize', () => {
					if (resizerIsVisible) {
						updateSize();
					}
				})
				.on(
					editor.ownerWindow,
					'mouseup keydown touchend',
					(e: MouseEvent) => {
						if (resizerIsVisible && !resizeElementClicked) {
							if (isResizing) {
								editor.unlock();
								isResizing = false;
								editor.setEditorValue();
								e.stopImmediatePropagation();
							} else {
								hideResizer();
							}
						}
					}
				)
				.on([editor.ownerWindow, editor.editor], 'scroll', () => {
					if (resizerIsVisible && !isResizing) {
						hideResizer();
					}
				});
		})
		.on('afterGetValueFromEditor', (data: { value: string }) => {
			const rgx = /<jodit[^>]+data-jodit_iframe_wrapper[^>]+>(.*?<iframe[^>]+>[\s\n\r]*<\/iframe>.*?)<\/jodit>/gi;

			if (rgx.test(data.value)) {
				data.value = data.value.replace(rgx, '$1');
			}
		})
		.on('hideResizer', hideResizer)
		.on(
			'change afterInit afterSetMode',
			debounce(() => {
				if (resizerIsVisible) {
					if (!currentElement || !currentElement.parentNode) {
						hideResizer();
					} else {
						updateSize();
					}
				}

				if (!editor.isDestructed) {
					$$('img, table, iframe', editor.editor).forEach(
						(elm: HTMLElement) => {
							if (editor.getMode() === consts.MODE_SOURCE) {
								return;
							}

							if (
								!(elm as any).__jodit_resizer_binded &&
								((elm.tagName === 'IFRAME' &&
									editor.options.useIframeResizer) ||
									(elm.tagName === 'IMG' &&
										editor.options.useImageResizer) ||
									(elm.tagName === 'TABLE' &&
										editor.options.useTableResizer))
							) {
								(elm as any).__jodit_resizer_binded = true;
								bind(elm);
							}
						}
					);
				}
			}, editor.defaultTimeout)
		);
}
