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

let positions = ['left', 'right', 'up', 'down'];

/**
 * A simple tooltip.
 *
 * @property {String} position The relative position to the parent
 */
export default class Tooltip extends Component {
    constructor($element, $scope, $timeout) {
        super($element, $scope);
        this.$timeout = $timeout;
        $element.addClass('vn-tooltip vn-shadow');
        this.position = 'down';
        this.margin = 10;
    }

    /**
     * Shows the tooltip.
     *
     * @param {HTMLElement} parent The parent element
     */
    show(parent) {
        this.parent = parent;
        this.$element.addClass('show');
        this.relocate();
        this.cancelTimeout();
        this.relocateTimeout = this.$timeout(() => this.relocate(), 50);
    }

    /**
     * Hides the tooltip.
     */
    hide() {
        this.$element.removeClass('show');
        this.cancelTimeout();
    }

    cancelTimeout() {
        if (this.relocateTimeout) {
            this.$timeout.cancel(this.relocateTimeout);
            this.relocateTimeout = null;
        }
    }

    /**
     * Repositions the tooltip acording to it's own size, position and parent location.
     */
    relocate() {
        let axis;
        let position = this.position;

        if (positions.indexOf(position) == -1)
            position = 'down';

        switch (position) {
        case 'right':
        case 'left':
            axis = 'x';
            break;
        default:
            axis = 'y';
        }

        let arrowSize = this.margin;
        let tipMargin = this.margin;
        let rect = this.parent.getBoundingClientRect();
        let tipRect = this.element.getBoundingClientRect();

        let tipComputed = this.window.getComputedStyle(this.element, null);
        let bgColor = tipComputed.getPropertyValue('background-color');

        let min = tipMargin;
        let maxTop = this.window.innerHeight - tipRect.height - tipMargin;
        let maxLeft = this.window.innerWidth - tipRect.width - tipMargin;

        // Coordinates

        let top;
        let left;

        function calcCoords() {
            top = rect.top;
            left = rect.left;

            if (axis == 'x')
                top += rect.height / 2 - tipRect.height / 2;
            else
                left += rect.width / 2 - tipRect.width / 2;

            switch (position) {
            case 'right':
                left += arrowSize + rect.width;
                break;
            case 'left':
                left -= arrowSize + tipRect.width;
                break;
            case 'up':
                top -= arrowSize + tipRect.height;
                break;
            default:
                top += arrowSize + rect.height;
            }
        }
        calcCoords();

        // Overflow

        let axisOverflow =
            axis == 'x' && (left < min || left > maxLeft) ||
            axis == 'y' && (top < min || top > maxTop);

        function switchPosition(position) {
            switch (position) {
            case 'right':
                return 'left';
            case 'left':
                return 'right';
            case 'up':
                return 'down';
            default:
                return 'up';
            }
        }

        if (axisOverflow) {
            position = switchPosition(position);
            calcCoords();
        }

        function range(coord, min, max) {
            return Math.min(Math.max(coord, min), max);
        }

        if (axis == 'x')
            top = range(top, min, maxTop);
        else
            left = range(left, min, maxLeft);

        let style = this.element.style;
        style.top = `${top}px`;
        style.left = `${left}px`;

        // Arrow position

        if (this.arrow)
            this.element.removeChild(this.arrow);

        let arrow = document.createElement('div');
        arrow.className = 'arrow';
        arrow.style.borderWidth = `${arrowSize}px`;

        let arrowStyle = arrow.style;

        if (axis == 'x') {
            let arrowTop = (rect.top + rect.height / 2) - top - arrowSize;
            arrowStyle.top = `${arrowTop}px`;
        } else {
            let arrowLeft = (rect.left + rect.width / 2) - left - arrowSize;
            arrowStyle.left = `${arrowLeft}px`;
        }

        let arrowCoord = `${-tipMargin}px`;

        switch (position) {
        case 'right':
            arrowStyle.left = arrowCoord;
            arrowStyle.borderRightColor = bgColor;
            arrowStyle.borderLeftWidth = 0;
            break;
        case 'left':
            arrowStyle.right = arrowCoord;
            arrowStyle.borderLeftColor = bgColor;
            arrowStyle.borderRightWidth = 0;
            break;
        case 'up':
            arrowStyle.bottom = arrowCoord;
            arrowStyle.borderTopColor = bgColor;
            arrowStyle.borderBottomWidth = 0;
            break;
        default:
            arrowStyle.top = arrowCoord;
            arrowStyle.borderBottomColor = bgColor;
            arrowStyle.borderTopWidth = 0;
        }

        this.element.appendChild(arrow);
        this.arrow = arrow;
    }

    $onDestroy() {
        this.hide();
    }
}
Tooltip.$inject = ['$element', '$scope', '$timeout'];

ngModule.vnComponent('vnTooltip', {
    template: require('./tooltip.html'),
    controller: Tooltip,
    transclude: true,
    bindings: {
        position: '@?'
    }
});

directive.$inject = ['$document', '$compile', '$templateRequest'];
export function directive($document, $compile, $templateRequest) {
    return {
        restrict: 'A',
        link: function($scope, $element, $attrs) {
            let tooltip;
            let $tooltip;
            let $tooltipScope;

            if ($attrs.tooltipId) {
                let tooltipKey = kebabToCamel($attrs.tooltipId);
                tooltip = $scope[tooltipKey];
                if (!(tooltip instanceof Tooltip))
                    throw new Error(`vnTooltip id should reference a tooltip instance`);
            } else {
                $tooltipScope = $scope.$new();
                $tooltipScope.text = $attrs.vnTooltip;
                let template = `<vn-tooltip class="text"><span translate>{{::text}}</span></vn-tooltip>`;
                $tooltip = $compile(template)($tooltipScope);
                $document.find('body').append($tooltip);
                tooltip = $tooltip[0].$ctrl;
            }

            if ($attrs.tooltipPosition)
                tooltip.position = $attrs.tooltipPosition;

            $element[0].title = '';
            $element.on('mouseover', function(event) {
                if (event.defaultPrevented) return;
                tooltip.show($element[0]);
            });
            $element.on('mouseout', function() {
                tooltip.hide();
            });
            $element.on('$destroy', function() {
                tooltip.hide();

                if ($tooltip) {
                    $tooltip.remove();
                    $tooltipScope.$destroy();
                }
            });
        }
    };
}
ngModule.directive('vnTooltip', directive);