import { ChangeDetectorRef, ContentChild, Directive, ElementRef, Input, IterableDiffers, NgZone, Optional, Renderer } from '@angular/core';
import { adjustRendered, calcDimensions, estimateHeight, initReadNodes, processRecords, populateNodeData, updateDimensions, updateNodeContext, writeToNodes } from './virtual-util';
import { Config } from '../../config/config';
import { Content } from '../content/content';
import { DomController } from '../../util/dom-controller';
import { isBlank, isFunction, isPresent } from '../../util/util';
import { Platform } from '../../platform/platform';
import { ViewController } from '../../navigation/view-controller';
import { VirtualFooter, VirtualHeader, VirtualItem } from './virtual-item';
export class VirtualScroll {
    constructor(_iterableDiffers, _elementRef, _renderer, _zone, _cd, _content, _platform, _ctrl, config, _dom) {
        this._iterableDiffers = _iterableDiffers;
        this._elementRef = _elementRef;
        this._renderer = _renderer;
        this._zone = _zone;
        this._cd = _cd;
        this._content = _content;
        this._platform = _platform;
        this._ctrl = _ctrl;
        this._dom = _dom;
        this._records = [];
        this._cells = [];
        this._nodes = [];
        this._vHeight = 0;
        this._lastCheck = 0;
        this._data = {
            scrollTop: 0,
        };
        this._queue = null;
        this.bufferRatio = 2;
        this.approxItemWidth = '100%';
        this.approxHeaderWidth = '100%';
        this.approxHeaderHeight = '40px';
        this.approxFooterWidth = '100%';
        this.approxFooterHeight = '40px';
        this._hasUpdate = false;
        this._renderer.setElementClass(_elementRef.nativeElement, 'virtual-loading', true);
        _content.readReady.subscribe(() => {
            this.readUpdate(true, true);
            if (!this._scrollSub) {
                this.addScrollListener(config.getBoolean('virtualScrollEventAssist'));
            }
        });
        _content.writeReady.subscribe(() => {
            this.writeUpdate();
        });
    }
    set virtualScroll(val) {
        this._records = val;
        if (isBlank(this._differ) && isPresent(val)) {
            this._differ = this._iterableDiffers.find(val).create(this._cd, this._trackBy);
        }
    }
    set headerFn(val) {
        if (isFunction(val)) {
            this._hdrFn = val.bind((this._ctrl && this._ctrl._cmp) || this);
        }
    }
    set footerFn(val) {
        if (isFunction(val)) {
            this._ftrFn = val.bind((this._ctrl && this._ctrl._cmp) || this);
        }
    }
    set virtualTrackBy(val) {
        this._trackBy = val;
    }
    readUpdate(checkDataChanges, dimensionsUpdated) {
        if (!this._records)
            return;
        if (checkDataChanges && !dimensionsUpdated) {
            if (isPresent(this._differ) && !isPresent(this._differ.diff(this._records))) {
                return;
            }
        }
        (void 0);
        this._hasUpdate = true;
        this._cells.length = 0;
        this._nodes.length = 0;
        this._itmTmp.viewContainer.clear();
        calcDimensions(this._data, this._elementRef.nativeElement, this.approxItemWidth, this.approxItemHeight, this.approxHeaderWidth, this.approxHeaderHeight, this.approxFooterWidth, this.approxFooterHeight, this.bufferRatio);
    }
    writeUpdate() {
        if (!this._hasUpdate) {
            return;
        }
        (void 0);
        processRecords(this._data.renderHeight, this._records, this._cells, this._hdrFn, this._ftrFn, this._data);
        this.renderVirtual();
        this._hasUpdate = false;
    }
    ngDoCheck() {
        if (this._init) {
            this.readUpdate(true, false);
            this.writeUpdate();
        }
    }
    ngAfterContentInit() {
        if (!this._init) {
            if (!this._itmTmp) {
                throw 'virtualItem required within virtualScroll';
            }
            this._init = true;
            if (!this.approxItemHeight) {
                this.approxItemHeight = '40px';
                console.warn('Virtual Scroll: Please provide an "approxItemHeight" input to ensure proper virtual scroll rendering');
            }
        }
    }
    renderVirtual() {
        const nodes = this._nodes;
        const cells = this._cells;
        const data = this._data;
        const records = this._records;
        data.topCell = 0;
        data.bottomCell = (cells.length - 1);
        populateNodeData(0, data.bottomCell, data.viewWidth, true, cells, records, nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef, true);
        this._cd.detectChanges();
        this._dom.read(() => {
            initReadNodes(nodes, cells, data);
        });
        this._dom.write(() => {
            const ele = this._elementRef.nativeElement;
            const recordsLength = records.length;
            const renderer = this._renderer;
            updateNodeContext(nodes, cells, data);
            this._cd.detectChanges();
            const lastEle = renderer.createElement(ele, 'div');
            lastEle.className = 'virtual-last';
            renderer.setElementClass(ele, 'virtual-scroll', true);
            renderer.setElementClass(ele, 'virtual-loading', false);
            writeToNodes(nodes, cells, recordsLength);
            this.setVirtualHeight(estimateHeight(recordsLength, cells[cells.length - 1], this._vHeight, 0.25));
            this._content.imgsUpdate();
        });
    }
    scrollUpdate(ev) {
        const data = this._data;
        const cells = this._cells;
        const nodes = this._nodes;
        data.scrollTop = ev.scrollTop;
        if (this._queue === 2) {
            this._dom.write(() => {
                writeToNodes(nodes, cells, this._records.length);
                this.setVirtualHeight(estimateHeight(this._records.length, cells[cells.length - 1], this._vHeight, 0.25));
                this._queue = 0;
            });
        }
        else if (this._queue === 1) {
            this._dom.write(() => {
                for (var i = 0; i < nodes.length; i++) {
                    if (nodes[i].hasChanges) {
                        nodes[i].view.detectChanges();
                    }
                }
                this._queue = 2;
            });
        }
        else {
            data.scrollDiff = (data.scrollTop - this._lastCheck);
            if (Math.abs(data.scrollDiff) > SCROLL_DIFFERENCE_MINIMUM) {
                this._lastCheck = data.scrollTop;
                if (data.scrollDiff > 0) {
                    var stopAtHeight = (data.scrollTop + data.renderHeight);
                    processRecords(stopAtHeight, this._records, cells, this._hdrFn, this._ftrFn, data);
                }
                updateDimensions(nodes, cells, data, false);
                adjustRendered(cells, data);
                var hasChanges = populateNodeData(data.topCell, data.bottomCell, data.viewWidth, data.scrollDiff > 0, cells, this._records, nodes, this._itmTmp.viewContainer, this._itmTmp.templateRef, this._hdrTmp && this._hdrTmp.templateRef, this._ftrTmp && this._ftrTmp.templateRef, false);
                if (hasChanges) {
                    this._queue = 1;
                    updateNodeContext(nodes, cells, data);
                }
            }
        }
    }
    scrollEnd(ev) {
        const nodes = this._nodes;
        const cells = this._cells;
        const data = this._data;
        updateDimensions(nodes, cells, data, false);
        adjustRendered(cells, data);
        this._dom.write(() => {
            updateNodeContext(nodes, cells, data);
            this._cd.detectChanges();
            writeToNodes(nodes, cells, this._records.length);
            this.setVirtualHeight(estimateHeight(this._records.length, cells[cells.length - 1], this._vHeight, 0.05));
            this._queue = 0;
        });
    }
    setVirtualHeight(newVirtualHeight) {
        if (newVirtualHeight !== this._vHeight) {
            this._renderer.setElementStyle(this._elementRef.nativeElement, 'height', newVirtualHeight > 0 ? newVirtualHeight + 'px' : '');
            this._vHeight = newVirtualHeight;
            (void 0);
        }
    }
    addScrollListener(eventAssist) {
        if (eventAssist) {
            this._content.enableJsScroll();
        }
        this._scrollSub = this._content.ionScroll.subscribe((ev) => {
            this.scrollUpdate(ev);
        });
        this._scrollEndSub = this._content.ionScrollEnd.subscribe((ev) => {
            this.scrollEnd(ev);
        });
    }
    ngOnDestroy() {
        this._scrollSub && this._scrollSub.unsubscribe();
        this._scrollEndSub && this._scrollEndSub.unsubscribe();
    }
}
VirtualScroll.decorators = [
    { type: Directive, args: [{
                selector: '[virtualScroll]'
            },] },
];
VirtualScroll.ctorParameters = [
    { type: IterableDiffers, },
    { type: ElementRef, },
    { type: Renderer, },
    { type: NgZone, },
    { type: ChangeDetectorRef, },
    { type: Content, },
    { type: Platform, },
    { type: ViewController, decorators: [{ type: Optional },] },
    { type: Config, },
    { type: DomController, },
];
VirtualScroll.propDecorators = {
    '_itmTmp': [{ type: ContentChild, args: [VirtualItem,] },],
    '_hdrTmp': [{ type: ContentChild, args: [VirtualHeader,] },],
    '_ftrTmp': [{ type: ContentChild, args: [VirtualFooter,] },],
    'virtualScroll': [{ type: Input },],
    'bufferRatio': [{ type: Input },],
    'approxItemWidth': [{ type: Input },],
    'approxItemHeight': [{ type: Input },],
    'approxHeaderWidth': [{ type: Input },],
    'approxHeaderHeight': [{ type: Input },],
    'approxFooterWidth': [{ type: Input },],
    'approxFooterHeight': [{ type: Input },],
    'headerFn': [{ type: Input },],
    'footerFn': [{ type: Input },],
    'virtualTrackBy': [{ type: Input },],
};
const SCROLL_DIFFERENCE_MINIMUM = 20;
//# sourceMappingURL=virtual-scroll.js.map