import { InjectionToken, Directive, ViewContainerRef, HostListener, Renderer2, Inject, Optional, Input, EventEmitter, Output, NgModule } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/fromEvent';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/takeUntil';
import 'rxjs/add/operator/map';

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @record
 */

class SlimScrollEvent {
    /**
     * @param {?=} obj
     */
    constructor(obj) {
        this.type = obj.type;
        this.y = obj && obj.y ? obj.y : 0;
        this.percent = obj && obj.percent ? obj.percent : 0;
        this.duration = obj && obj.duration ? obj.duration : 0;
        this.easing = obj && obj.easing ? obj.easing : 'linear';
    }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @record
 */

const SLIMSCROLL_DEFAULTS = new InjectionToken('NGX_SLIMSCROLL_DEFAULTS');
class SlimScrollOptions {
    /**
     * @param {?=} obj
     */
    constructor(obj) {
        this.position = obj && obj.position ? obj.position : 'right';
        this.barBackground = obj && obj.barBackground ? obj.barBackground : '#343a40';
        this.barOpacity = obj && obj.barOpacity ? obj.barOpacity : '1';
        this.barWidth = obj && obj.barWidth ? obj.barWidth : '12';
        this.barBorderRadius = obj && obj.barBorderRadius ? obj.barBorderRadius : '5';
        this.barMargin = obj && obj.barMargin ? obj.barMargin : '1px 0';
        this.gridBackground = obj && obj.gridBackground ? obj.gridBackground : '#adb5bd';
        this.gridOpacity = obj && obj.gridOpacity ? obj.gridOpacity : '1';
        this.gridWidth = obj && obj.gridWidth ? obj.gridWidth : '8';
        this.gridBorderRadius = obj && obj.gridBorderRadius ? obj.gridBorderRadius : '10';
        this.gridMargin = obj && obj.gridMargin ? obj.gridMargin : '1px 2px';
        this.alwaysVisible = obj && typeof obj.alwaysVisible !== 'undefined' ? obj.alwaysVisible : true;
        this.visibleTimeout = obj && obj.visibleTimeout ? obj.visibleTimeout : 1000;
    }
    /**
     * @param {?=} obj
     * @return {?}
     */
    merge(obj) {
        const /** @type {?} */ result = new SlimScrollOptions();
        result.position = obj && obj.position ? obj.position : this.position;
        result.barBackground = obj && obj.barBackground ? obj.barBackground : this.barBackground;
        result.barOpacity = obj && obj.barOpacity ? obj.barOpacity : this.barOpacity;
        result.barWidth = obj && obj.barWidth ? obj.barWidth : this.barWidth;
        result.barBorderRadius = obj && obj.barBorderRadius ? obj.barBorderRadius : this.barBorderRadius;
        result.barMargin = obj && obj.barMargin ? obj.barMargin : this.barMargin;
        result.gridBackground = obj && obj.gridBackground ? obj.gridBackground : this.gridBackground;
        result.gridOpacity = obj && obj.gridOpacity ? obj.gridOpacity : this.gridBackground;
        result.gridWidth = obj && obj.gridWidth ? obj.gridWidth : this.gridWidth;
        result.gridBorderRadius = obj && obj.gridBorderRadius ? obj.gridBorderRadius : this.gridBorderRadius;
        result.gridMargin = obj && obj.gridMargin ? obj.gridMargin : this.gridMargin;
        result.alwaysVisible = obj && typeof obj.alwaysVisible !== 'undefined' ? obj.alwaysVisible : this.alwaysVisible;
        result.visibleTimeout = obj && obj.visibleTimeout ? obj.visibleTimeout : this.visibleTimeout;
        return result;
    }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * @record
 */

class SlimScrollState {
    /**
     * @param {?=} obj
     */
    constructor(obj) {
        this.scrollPosition = obj && obj.scrollPosition ? obj.scrollPosition : 0;
        this.isScrollAtStart = obj && typeof obj.isScrollAtStart !== 'undefined' ? obj.isScrollAtStart : true;
        this.isScrollAtEnd = obj && typeof obj.isScrollAtEnd !== 'undefined' ? obj.isScrollAtEnd : false;
    }
}

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
const easing = {
    linear: (t) => t,
    inQuad: (t) => t * t,
    outQuad: (t) => t * (2 - t),
    inOutQuad: (t) => t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
    inCubic: (t) => t * t * t,
    outCubic: (t) => (--t) * t * t + 1,
    inOutCubic: (t) => t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
    inQuart: (t) => t * t * t * t,
    outQuart: (t) => 1 - (--t) * t * t * t,
    inOutQuart: (t) => t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t,
    inQuint: (t) => t * t * t * t * t,
    outQuint: (t) => 1 + (--t) * t * t * t * t,
    inOutQuint: (t) => t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
};
class SlimScrollDirective {
    /**
     * @param {?} viewContainer
     * @param {?} renderer
     * @param {?} document
     * @param {?} optionsDefaults
     */
    constructor(viewContainer, renderer, document, optionsDefaults) {
        this.viewContainer = viewContainer;
        this.renderer = renderer;
        this.document = document;
        this.optionsDefaults = optionsDefaults;
        this.scrollChanged = new EventEmitter();
        this.interactionSubscriptions = new Subscription();
        this.initWheel = () => {
            const /** @type {?} */ dommousescroll = Observable.fromEvent(this.el, 'DOMMouseScroll');
            const /** @type {?} */ mousewheel = Observable.fromEvent(this.el, 'mousewheel');
            const /** @type {?} */ wheelSubscription = Observable.merge(...[dommousescroll, mousewheel]).subscribe((e) => {
                let /** @type {?} */ delta = 0;
                if (e.wheelDelta) {
                    delta = -e.wheelDelta / 120;
                }
                if (e.detail) {
                    delta = e.detail / 3;
                }
                this.scrollContent(delta, true, false);
                if (e.preventDefault) {
                    e.preventDefault();
                }
            });
            this.interactionSubscriptions.add(wheelSubscription);
        };
        this.initDrag = () => {
            const /** @type {?} */ bar = this.bar;
            const /** @type {?} */ mousemove = Observable.fromEvent(this.document.documentElement, 'mousemove');
            const /** @type {?} */ touchmove = Observable.fromEvent(this.document.documentElement, 'touchmove');
            const /** @type {?} */ mousedown = Observable.fromEvent(bar, 'mousedown');
            const /** @type {?} */ touchstart = Observable.fromEvent(this.el, 'touchstart');
            const /** @type {?} */ mouseup = Observable.fromEvent(this.document.documentElement, 'mouseup');
            const /** @type {?} */ touchend = Observable.fromEvent(this.document.documentElement, 'touchend');
            const /** @type {?} */ mousedrag = mousedown.mergeMap((e) => {
                this.pageY = e.pageY;
                this.top = parseFloat(getComputedStyle(bar).top);
                return mousemove.map((emove) => {
                    emove.preventDefault();
                    return this.top + emove.pageY - this.pageY;
                }).takeUntil(mouseup);
            });
            const /** @type {?} */ touchdrag = touchstart.mergeMap((e) => {
                this.pageY = e.targetTouches[0].pageY;
                this.top = -parseFloat(getComputedStyle(bar).top);
                return touchmove.map((tmove) => {
                    return -(this.top + tmove.targetTouches[0].pageY - this.pageY);
                }).takeUntil(touchend);
            });
            const /** @type {?} */ dragSubscription = Observable.merge(...[mousedrag, touchdrag]).subscribe((top) => {
                this.body.addEventListener('selectstart', this.preventDefaultEvent, false);
                this.renderer.setStyle(this.body, 'touch-action', 'pan-y');
                this.renderer.setStyle(this.body, 'user-select', 'none');
                this.renderer.setStyle(this.bar, 'top', `${top}px`);
                const /** @type {?} */ over = this.scrollContent(0, true, false);
                const /** @type {?} */ maxTop = this.el.offsetHeight - this.bar.offsetHeight;
                if (over && over < 0 && -over <= maxTop) {
                    this.renderer.setStyle(this.el, 'paddingTop', -over + 'px');
                }
                else if (over && over > 0 && over <= maxTop) {
                    this.renderer.setStyle(this.el, 'paddingBottom', over + 'px');
                }
            });
            const /** @type {?} */ dragEndSubscription = Observable.merge(...[mouseup, touchend]).subscribe(() => {
                this.body.removeEventListener('selectstart', this.preventDefaultEvent, false);
                const /** @type {?} */ paddingTop = parseInt(this.el.style.paddingTop, 10);
                const /** @type {?} */ paddingBottom = parseInt(this.el.style.paddingBottom, 10);
                this.renderer.setStyle(this.body, 'touch-action', 'unset');
                this.renderer.setStyle(this.body, 'user-select', 'default');
                if (paddingTop > 0) {
                    this.scrollTo(0, 300, 'linear');
                }
                else if (paddingBottom > 0) {
                    this.scrollTo(0, 300, 'linear');
                }
            });
            this.interactionSubscriptions.add(dragSubscription);
            this.interactionSubscriptions.add(dragEndSubscription);
        };
        this.preventDefaultEvent = (e) => {
            e.preventDefault();
            e.stopPropagation();
        };
        this.viewContainer = viewContainer;
        this.el = viewContainer.element.nativeElement;
        this.body = this.document.querySelector('body');
        this.mutationThrottleTimeout = 50;
    }
    /**
     * @return {?}
     */
    ngOnInit() {
        if (this.optionsDefaults) {
            this.options = new SlimScrollOptions(this.optionsDefaults).merge(this.options);
        }
        else {
            this.options = new SlimScrollOptions(this.options);
        }
        this.setStyle();
        this.wrapContainer();
        this.initGrid();
        this.initBar();
        this.getBarHeight();
        this.initWheel();
        this.initDrag();
        if (!this.options.alwaysVisible) {
            this.hideBarAndGrid();
        }
        if (MutationObserver) {
            this.mutationObserver = new MutationObserver(() => {
                if (this.mutationThrottleTimeout) {
                    clearTimeout(this.mutationThrottleTimeout);
                    this.mutationThrottleTimeout = setTimeout(this.onMutation.bind(this), 50);
                }
            });
            this.mutationObserver.observe(this.el, { subtree: true, childList: true });
        }
        if (this.scrollEvents && this.scrollEvents instanceof EventEmitter) {
            const /** @type {?} */ scrollSubscription = this.scrollEvents.subscribe((event) => this.handleEvent(event));
            this.interactionSubscriptions.add(scrollSubscription);
        }
    }
    /**
     * @return {?}
     */
    ngOnDestroy() {
        this.interactionSubscriptions.unsubscribe();
    }
    /**
     * @param {?} e
     * @return {?}
     */
    handleEvent(e) {
        if (e.type === 'scrollToBottom') {
            const /** @type {?} */ y = this.el.scrollHeight - this.el.clientHeight;
            this.scrollTo(y, e.duration, e.easing);
        }
        else if (e.type === 'scrollToTop') {
            const /** @type {?} */ y = 0;
            this.scrollTo(y, e.duration, e.easing);
        }
        else if (e.type === 'scrollToPercent' && (e.percent >= 0 && e.percent <= 100)) {
            const /** @type {?} */ y = Math.round(((this.el.scrollHeight - this.el.clientHeight) / 100) * e.percent);
            this.scrollTo(y, e.duration, e.easing);
        }
        else if (e.type === 'scrollTo') {
            const /** @type {?} */ y = e.y;
            if (y <= this.el.scrollHeight - this.el.clientHeight && y >= 0) {
                this.scrollTo(y, e.duration, e.easing);
            }
        }
        else if (e.type === 'recalculate') {
            this.getBarHeight();
        }
    }
    /**
     * @return {?}
     */
    setStyle() {
        const /** @type {?} */ el = this.el;
        this.renderer.setStyle(el, 'overflow', 'hidden');
        this.renderer.setStyle(el, 'position', 'relative');
        this.renderer.setStyle(el, 'display', 'block');
    }
    /**
     * @return {?}
     */
    onMutation() {
        this.getBarHeight();
    }
    /**
     * @return {?}
     */
    wrapContainer() {
        this.wrapper = this.renderer.createElement('div');
        const /** @type {?} */ wrapper = this.wrapper;
        const /** @type {?} */ el = this.el;
        this.renderer.addClass(wrapper, 'slimscroll-wrapper');
        this.renderer.setStyle(wrapper, 'position', 'relative');
        this.renderer.setStyle(wrapper, 'overflow', 'hidden');
        this.renderer.setStyle(wrapper, 'display', 'inline-block');
        this.renderer.setStyle(wrapper, 'margin', getComputedStyle(el).margin);
        this.renderer.setStyle(wrapper, 'width', '100%');
        this.renderer.setStyle(wrapper, 'height', getComputedStyle(el).height);
        this.renderer.insertBefore(el.parentNode, wrapper, el);
        this.renderer.appendChild(wrapper, el);
    }
    /**
     * @return {?}
     */
    initGrid() {
        this.grid = this.renderer.createElement('div');
        const /** @type {?} */ grid = this.grid;
        this.renderer.addClass(grid, 'slimscroll-grid');
        this.renderer.setStyle(grid, 'position', 'absolute');
        this.renderer.setStyle(grid, 'top', '0');
        this.renderer.setStyle(grid, 'bottom', '0');
        this.renderer.setStyle(grid, this.options.position, '0');
        this.renderer.setStyle(grid, 'width', `${this.options.gridWidth}px`);
        this.renderer.setStyle(grid, 'background', this.options.gridBackground);
        this.renderer.setStyle(grid, 'opacity', this.options.gridOpacity);
        this.renderer.setStyle(grid, 'display', 'block');
        this.renderer.setStyle(grid, 'cursor', 'pointer');
        this.renderer.setStyle(grid, 'z-index', '99');
        this.renderer.setStyle(grid, 'border-radius', `${this.options.gridBorderRadius}px`);
        this.renderer.setStyle(grid, 'margin', this.options.gridMargin);
        this.renderer.appendChild(this.wrapper, grid);
    }
    /**
     * @return {?}
     */
    initBar() {
        this.bar = this.renderer.createElement('div');
        const /** @type {?} */ bar = this.bar;
        this.renderer.addClass(bar, 'slimscroll-bar');
        this.renderer.setStyle(bar, 'position', 'absolute');
        this.renderer.setStyle(bar, 'top', '0');
        this.renderer.setStyle(bar, this.options.position, '0');
        this.renderer.setStyle(bar, 'width', `${this.options.barWidth}px`);
        this.renderer.setStyle(bar, 'background', this.options.barBackground);
        this.renderer.setStyle(bar, 'opacity', this.options.barOpacity);
        this.renderer.setStyle(bar, 'display', 'block');
        this.renderer.setStyle(bar, 'cursor', 'pointer');
        this.renderer.setStyle(bar, 'z-index', '100');
        this.renderer.setStyle(bar, 'border-radius', `${this.options.barBorderRadius}px`);
        this.renderer.setStyle(bar, 'margin', this.options.barMargin);
        this.renderer.appendChild(this.wrapper, bar);
    }
    /**
     * @return {?}
     */
    getBarHeight() {
        const /** @type {?} */ barHeight = Math.max((this.el.offsetHeight / this.el.scrollHeight) * this.el.offsetHeight, 30) + 'px';
        const /** @type {?} */ display = parseInt(barHeight, 10) === this.el.offsetHeight ? 'none' : 'block';
        this.renderer.setStyle(this.bar, 'height', barHeight);
        this.renderer.setStyle(this.bar, 'display', display);
        this.renderer.setStyle(this.grid, 'display', display);
    }
    /**
     * @param {?} y
     * @param {?} duration
     * @param {?} easingFunc
     * @return {?}
     */
    scrollTo(y, duration, easingFunc) {
        const /** @type {?} */ start = Date.now();
        const /** @type {?} */ from = this.el.scrollTop;
        const /** @type {?} */ maxTop = this.el.offsetHeight - this.bar.offsetHeight;
        const /** @type {?} */ maxElScrollTop = this.el.scrollHeight - this.el.clientHeight;
        const /** @type {?} */ barHeight = Math.max((this.el.offsetHeight / this.el.scrollHeight) * this.el.offsetHeight, 30);
        const /** @type {?} */ paddingTop = parseInt(this.el.style.paddingTop, 10) || 0;
        const /** @type {?} */ paddingBottom = parseInt(this.el.style.paddingBottom, 10) || 0;
        const /** @type {?} */ scroll = (timestamp) => {
            const /** @type {?} */ currentTime = Date.now();
            const /** @type {?} */ time = Math.min(1, ((currentTime - start) / duration));
            const /** @type {?} */ easedTime = easing[easingFunc](time);
            if (paddingTop > 0 || paddingBottom > 0) {
                let /** @type {?} */ fromY = null;
                if (paddingTop > 0) {
                    fromY = -paddingTop;
                    fromY = -((easedTime * (y - fromY)) + fromY);
                    this.renderer.setStyle(this.el, 'paddingTop', `${fromY}px`);
                }
                if (paddingBottom > 0) {
                    fromY = paddingBottom;
                    fromY = ((easedTime * (y - fromY)) + fromY);
                    this.renderer.setStyle(this.el, 'paddingBottom', `${fromY}px`);
                }
            }
            else {
                this.el.scrollTop = (easedTime * (y - from)) + from;
            }
            const /** @type {?} */ percentScroll = this.el.scrollTop / maxElScrollTop;
            if (paddingBottom === 0) {
                const /** @type {?} */ delta = Math.round(Math.round(this.el.clientHeight * percentScroll) - barHeight);
                if (delta > 0) {
                    this.renderer.setStyle(this.bar, 'top', `${delta}px`);
                }
            }
            if (time < 1) {
                requestAnimationFrame(scroll);
            }
        };
        requestAnimationFrame(scroll);
    }
    /**
     * @param {?} y
     * @param {?} isWheel
     * @param {?} isJump
     * @return {?}
     */
    scrollContent(y, isWheel, isJump) {
        let /** @type {?} */ delta = y;
        const /** @type {?} */ maxTop = this.el.offsetHeight - this.bar.offsetHeight;
        const /** @type {?} */ hiddenContent = this.el.scrollHeight - this.el.offsetHeight;
        let /** @type {?} */ percentScroll;
        let /** @type {?} */ over = null;
        if (isWheel) {
            delta = parseInt(getComputedStyle(this.bar).top, 10) + y * 20 / 100 * this.bar.offsetHeight;
            if (delta < 0 || delta > maxTop) {
                over = delta > maxTop ? delta - maxTop : delta;
            }
            delta = Math.min(Math.max(delta, 0), maxTop);
            delta = (y > 0) ? Math.ceil(delta) : Math.floor(delta);
            this.renderer.setStyle(this.bar, 'top', delta + 'px');
        }
        percentScroll = parseInt(getComputedStyle(this.bar).top, 10) / (this.el.offsetHeight - this.bar.offsetHeight);
        delta = percentScroll * hiddenContent;
        this.el.scrollTop = delta;
        this.showBarAndGrid();
        if (!this.options.alwaysVisible) {
            if (this.visibleTimeout) {
                clearTimeout(this.visibleTimeout);
            }
            this.visibleTimeout = setTimeout(() => {
                this.hideBarAndGrid();
            }, this.options.visibleTimeout);
        }
        const /** @type {?} */ isScrollAtStart = delta === 0;
        const /** @type {?} */ isScrollAtEnd = delta === hiddenContent;
        const /** @type {?} */ scrollPosition = Math.ceil(delta);
        const /** @type {?} */ scrollState = new SlimScrollState({ scrollPosition, isScrollAtStart, isScrollAtEnd });
        this.scrollChanged.emit(scrollState);
        return over;
    }
    /**
     * @return {?}
     */
    showBarAndGrid() {
        this.renderer.setStyle(this.grid, 'background', this.options.gridBackground);
        this.renderer.setStyle(this.bar, 'background', this.options.barBackground);
    }
    /**
     * @return {?}
     */
    hideBarAndGrid() {
        this.renderer.setStyle(this.grid, 'background', 'transparent');
        this.renderer.setStyle(this.bar, 'background', 'transparent');
    }
    /**
     * @return {?}
     */
    destroy() {
        if (this.mutationObserver) {
            this.mutationObserver.disconnect();
            this.mutationObserver = null;
        }
        if (this.el.parentElement.classList.contains('slimscroll-wrapper')) {
            const /** @type {?} */ wrapper = this.el.parentElement;
            const /** @type {?} */ bar = this.el.querySelector('.slimscroll-bar');
            this.el.removeChild(bar);
            this.unwrap(wrapper);
        }
    }
    /**
     * @param {?} wrapper
     * @return {?}
     */
    unwrap(wrapper) {
        const /** @type {?} */ docFrag = document.createDocumentFragment();
        while (wrapper.firstChild) {
            const /** @type {?} */ child = wrapper.removeChild(wrapper.firstChild);
            docFrag.appendChild(child);
        }
        wrapper.parentNode.replaceChild(docFrag, wrapper);
    }
    /**
     * @param {?} $event
     * @return {?}
     */
    onResize($event) {
        this.getBarHeight();
    }
}
SlimScrollDirective.decorators = [
    { type: Directive, args: [{
                selector: '[slimScroll]',
                // tslint:disable-line
                exportAs: 'slimScroll'
            },] },
];
/** @nocollapse */
SlimScrollDirective.ctorParameters = () => [
    { type: ViewContainerRef, decorators: [{ type: Inject, args: [ViewContainerRef,] },] },
    { type: Renderer2, decorators: [{ type: Inject, args: [Renderer2,] },] },
    { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] },] },
    { type: undefined, decorators: [{ type: Inject, args: [SLIMSCROLL_DEFAULTS,] }, { type: Optional },] },
];
SlimScrollDirective.propDecorators = {
    "options": [{ type: Input },],
    "scrollEvents": [{ type: Input },],
    "scrollChanged": [{ type: Output, args: ['scrollChanged',] },],
    "onResize": [{ type: HostListener, args: ['window:resize', ['$event'],] },],
};

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
class NgSlimScrollModule {
}
NgSlimScrollModule.decorators = [
    { type: NgModule, args: [{
                declarations: [
                    SlimScrollDirective
                ],
                exports: [
                    SlimScrollDirective
                ]
            },] },
];

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */

/**
 * @fileoverview added by tsickle
 * @suppress {checkTypes} checked by tsc
 */
/**
 * Generated bundle index. Do not edit.
 */

export { SlimScrollEvent, SLIMSCROLL_DEFAULTS, SlimScrollOptions, NgSlimScrollModule, SlimScrollDirective as ɵa };
//# sourceMappingURL=ngx-slimscroll.js.map
