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

/**
 * Treeview
 */
export default class Treeview extends Component {
    constructor($element, $scope, $transclude, $window) {
        super($element, $scope);
        this.$transclude = $transclude;
        this.$window = $window;
        this.readOnly = true;

        this.element.addEventListener('dragstart',
            event => this.dragStart(event));
        this.element.addEventListener('dragend',
            event => this.dragEnd(event));

        this.element.addEventListener('dragover',
            event => this.dragOver(event));
        this.element.addEventListener('drop',
            event => this.drop(event));
        this.element.addEventListener('dragenter',
            event => this.dragEnter(event));
        this.element.addEventListener('dragleave',
            event => this.dragLeave(event));

        this.dropCount = 0;
    }

    undrop() {
        if (!this.dropping) return;
        this.dropping.classList.remove('dropping');
        this.dropping = null;
    }

    findDroppable(event) {
        let element = event.target;
        while (element != this.element && !element.droppable)
            element = element.parentNode;
        if (element == this.element)
            return null;
        return element;
    }

    dragOver(event) {
        this.dragClientY = event.clientY;

        // Prevents page reload
        event.preventDefault();
    }

    onDragInterval() {
        if (this.dragClientY > 0 && this.dragClientY < 75)
            this.$window.scrollTo(0, this.$window.scrollY - 25);
    }

    dragStart(event) {
        event.target.classList.add('dragging');
        event.dataTransfer.setData('text', event.target.id);

        const element = this.findDroppable(event);
        this.dragging = element;

        this.interval = setInterval(() => this.onDragInterval(), 100);
    }

    dragEnd(event) {
        event.target.classList.remove('dragging');
        this.undrop();
        this.dropCount = 0;
        this.dragging = null;
        clearInterval(this.interval);
    }

    dragEnter(event) {
        let element = this.findDroppable(event);
        if (element) this.dropCount++;

        if (element != this.dropping) {
            this.undrop();
            if (element) element.classList.add('dropping');
            this.dropping = element;
        }
    }

    dragLeave(event) {
        let element = this.findDroppable(event);

        if (element) {
            this.dropCount--;
            if (this.dropCount == 0) this.undrop();
        }
    }

    drop(event) {
        event.preventDefault();
        this.element.classList.remove('dropping');

        const $dropped = this.dropping.$ctrl.item;
        const $dragged = this.dragging.$ctrl.item;

        if ($dropped != $dragged.parent)
            this.emit('drop', {$dropped, $dragged});
    }

    get data() {
        return this._data;
    }

    set data(value) {
        this._data = value;

        const sons = value.length;
        const rootElement = [{
            childs: value,
            active: true,
            sons: sons
        }];

        this.setParent(rootElement[0], value);

        this.items = rootElement;
    }

    fetch() {
        return this.fetchFunc().then(res =>
            this.data = res
        );
    }

    setParent(parent, childs) {
        childs.forEach(child => {
            child.parent = parent;

            if (child.childs)
                this.setParent(parent, child.childs);
        });
    }

    onToggle(event, item) {
        let ignoreEvent = event.defaultPrevented
            || event == this.lastActionEvent
            || !item.sons;
        if (ignoreEvent) return;

        if (item.active)
            this.fold(item);
        else
            this.unfold(item);
    }

    fold(item) {
        item.childs = undefined;
        item.active = false;
    }

    unfold(item) {
        return this.fetchFunc({$item: item}).then(newData => {
            this.setParent(item, newData);

            const childs = item.childs;
            if (childs) {
                childs.forEach(child => {
                    let index = newData.findIndex(newChild => {
                        return newChild.id == child.id;
                    });
                    newData[index] = child;
                });
            }

            if (this.sortFunc) {
                item.childs = newData.sort((a, b) =>
                    this.sortFunc({$a: a, $b: b})
                );
            }
        }).then(() => item.active = true);
    }

    onRemove(event, item) {
        this.lastActionEvent = event;
        if (this.removeFunc)
            this.removeFunc({$item: item});
    }

    remove(item) {
        const parent = item.parent;
        let childs = parent.childs;

        if (!childs) childs = [];

        let index = childs.indexOf(item);
        childs.splice(index, 1);
        if (parent) parent.sons--;
    }

    onCreate(event, parent) {
        this.lastActionEvent = event;
        if (this.createFunc)
            this.createFunc({$parent: parent});
    }

    create(item) {
        const parent = item.parent;
        let childs = parent.childs;
        if (!childs) childs = [];

        if (!parent.active)
            this.unfold(parent);
        else
            childs.push(item);

        if (this.sortFunc) {
            childs = childs.sort((a, b) =>
                this.sortFunc({$a: a, $b: b})
            );
        }

        if (parent) parent.sons++;
    }

    move(item, newParent) {
        if (newParent == item) return;

        if (item.parent) {
            const parent = item.parent;
            const childs = parent.childs;
            const index = childs.indexOf(item);
            parent.sons--;

            childs.splice(index, 1);
        }

        item.parent = newParent;

        if (!newParent.active) {
            this.unfold(newParent).then(() => {
                item.parent.sons++;
            });
        } else
            this.create(item);
    }
}

Treeview.$inject = ['$element', '$scope', '$transclude', '$window'];

ngModule.vnComponent('vnTreeview', {
    template: require('./index.html'),
    controller: Treeview,
    bindings: {
        rootLabel: '@',
        data: '<?',
        draggable: '<?',
        droppable: '<?',
        fetchFunc: '&',
        removeFunc: '&?',
        createFunc: '&?',
        sortFunc: '&?',
        readOnly: '<?'
    },
    transclude: true
});