import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';

/**
 * A simple popover.
 *
 * @property {HTMLElement} parent The parent element to show drop down relative to
 *
 * @event open Thrown when popover is displayed
 * @event close Thrown when popover is hidden
 */
export default class Popover extends Component {
    constructor($element, $scope, $timeout, $transitions) {
        super($element, $scope);
        this.$timeout = $timeout;
        this.$transitions = $transitions;
        this._shown = false;
    }

    $postLink() {
        super.$postLink();
        this.$element.addClass('vn-popover');

        this.docKeyDownHandler = e => this.onDocKeyDown(e);
        this.docFocusInHandler = e => this.onDocFocusIn(e);

        this.element.addEventListener('mousedown',
            e => this.onBackgroundMouseDown(e));
        this.element.addEventListener('focusin',
            e => this.onFocusIn(e));

        this.popover = this.element.querySelector('.popover');
        this.popover.addEventListener('mousedown',
            e => this.onMouseDown(e));

        this.arrow = this.element.querySelector('.arrow');
        this.content = this.element.querySelector('.content');
    }

    /**
     * @type {HTMLElement} The popover child.
     */
    get child() {
        return this.content.firstChild;
    }

    set child(value) {
        this.content.innerHTML = '';
        this.content.appendChild(value);
    }

    /**
     * @type {Boolean} Wether to show or hide the popover.
     */
    get shown() {
        return this._shown;
    }

    set shown(value) {
        if (value)
            this.show();
        else
            this.hide();
    }

    /**
     * Shows the popover emitting the open signal. If a parent is specified
     * it is shown in a visible relative position to it.
     *
     * @param {HTMLElement} parent Overrides the parent property
     */
    show(parent) {
        if (this._shown) return;

        if (parent) this.parent = parent;
        this._shown = true;
        this.element.style.display = 'block';
        this.$timeout.cancel(this.showTimeout);
        this.showTimeout = this.$timeout(() => {
            this.$element.addClass('shown');
            this.showTimeout = null;
        }, 30);

        this.document.addEventListener('keydown', this.docKeyDownHandler);
        this.document.addEventListener('focusin', this.docFocusInHandler);

        this.deregisterCallback = this.$transitions.onStart({}, () => this.hide());
        this.relocate();
        this.emit('open');
    }

    /**
     * Hides the popover emitting the close signal.
     */
    hide() {
        if (!this._shown) return;

        this._shown = false;
        this.$element.removeClass('shown');
        this.$timeout.cancel(this.showTimeout);
        this.showTimeout = this.$timeout(() => {
            this.element.style.display = 'none';
            this.showTimeout = null;
            this.emit('close');
        }, 250);

        this.document.removeEventListener('keydown', this.docKeyDownHandler);
        this.document.removeEventListener('focusin', this.docFocusInHandler);

        if (this.deregisterCallback)
            this.deregisterCallback();
    }

    /**
     * Repositions the popover to a correct location relative to the parent.
     */
    relocate() {
        if (!(this.parent && this._shown)) return;

        let style = this.popover.style;
        style.width = '';
        style.height = '';

        let arrowStyle = this.arrow.style;
        arrowStyle.top = '';
        arrowStyle.bottom = '';

        let parentRect = this.parent.getBoundingClientRect();
        let popoverRect = this.popover.getBoundingClientRect();
        let arrowRect = this.arrow.getBoundingClientRect();

        let arrowHeight = Math.sqrt(Math.pow(arrowRect.height, 2) * 2) / 2;

        let height = popoverRect.height;
        let width = Math.max(popoverRect.width, parentRect.width);
        let top = parentRect.top + parentRect.height + arrowHeight;
        let left = parentRect.left + parentRect.width / 2 - width / 2;

        let margin = 10;
        let showTop = top + height + margin > window.innerHeight;

        if (showTop)
            top = Math.max(parentRect.top - height - arrowHeight, margin);
        if (left + width + margin > window.innerWidth)
            left = window.innerWidth - width - margin;

        if (showTop)
            arrowStyle.bottom = `0`;
        else
            arrowStyle.top = `0`;

        arrowStyle.left = `${(parentRect.left - left) + parentRect.width / 2}px`;

        style.top = `${top}px`;
        style.left = `${left}px`;
        style.width = `${width}px`;

        if (height + margin * 2 + arrowHeight > window.innerHeight)
            style.height = `${window.innerHeight - margin * 2 - arrowHeight}px`;
    }

    onDocKeyDown(event) {
        if (event.defaultPrevented) return;

        if (event.keyCode == 27) { // Esc
            event.preventDefault();
            this.hide();
        }
    }

    onMouseDown(event) {
        this.lastMouseEvent = event;
    }

    onBackgroundMouseDown(event) {
        if (event != this.lastMouseEvent)
            this.hide();
    }

    onFocusIn(event) {
        this.lastFocusEvent = event;
    }

    onDocFocusIn(event) {
        if (event !== this.lastFocusEvent)
            this.hide();
    }
}
Popover.$inject = ['$element', '$scope', '$timeout', '$transitions'];

ngModule.component('vnPopover', {
    template: require('./popover.html'),
    controller: Popover,
    transclude: true
});