/**
 * i-doit Base tree class.
 * This class should be used as an "abstract" parent. Extending classes need to provide an own data handler ("loadChildrenNodes").
 *
 * @author  Leonard Fischer <lfischer@i-doit.com>
 */
window.BaseTree = Class.create({
    options:   {},
    openNodes: [],
    cache:     {},

    /**
     * Constructor method.
     *
     * @param $container
     * @param options
     * @returns {Window.BaseTree}
     */
    initialize: function ($container, options) {
        this.$container = $container;
        this.options = {
            rootNodeId:  1,
            nodeSorting: Prototype.emptyFunction
        };

        // Empty the cache for each instantiation.
        this.cache = {};
        this.openNodes = [];

        Object.extend(this.options, options || {});

        this.addObserver();

        return this;
    },

    /**
     * Method for adding all necessary observers.
     */
    addObserver: function () {
        this.$container.on('click', '.child-toggle', function (ev) {
            var nodeId = ev.findElement('li').readAttribute('data-id');

            this.toggleOpenNode(nodeId);
        }.bind(this));
    },

    /**
     * Method for setting the currently opened nodes.
     *
     * @param nodes
     * @returns {Window.BaseTree}
     */
    setOpenNodes: function (nodes) {
        // We prefix the keys so we ALWAYS work with strings.
        this.openNodes = nodes.map(function (d) {
            return 'n' + d;
        });

        return this;
    },

    /**
     * Method for toggling the node state (open/closed).
     *
     * @param nodeId
     * @returns {Window.BaseTree}
     */
    toggleOpenNode: function(nodeId) {
        if (this.isOpenNode(nodeId)) {
            this.openNodes = this.openNodes.filter(function(id) {
                return 'n' + nodeId !== id;
            });
        } else {
            this.openNodes.push('n' + nodeId);
        }

        this.process();

        return this;
    },

    /**
     * Method for finding out if a node is open.
     *
     * @param nodeId
     * @returns {boolean}
     */
    isOpenNode: function (nodeId) {
        return this.openNodes.indexOf('n' + nodeId) !== -1;
    },

    /**
     * Process method for rendering the complete tree.
     */
    process: function () {
        this.openNodes.push('n' + this.options.rootNodeId);

        this.openNodes = this.openNodes.uniq();

        this.displayRoot();
    },

    /**
     * Method for rendering a node.
     *
     * @param data
     * @returns {*}
     */
    renderNode: function (data) {
        var open  = this.isOpenNode(data.nodeId);

        return new Element('li', { 'data-id': data.nodeId })
            .update(new Element('img', { src: this.getToggleImage(open), className: 'child-toggle ' + (data.hasChildren ? '' : 'hide') }))
            .insert(new Element('div', { className: 'tree-inner' })
                .insert(new Element('img', { src: data.nodeTypeIcon, className: 'mr5', title: data.nodeTypeTitle }))
                .insert(new Element('span').update(data.nodeTitle)))
            .insert(new Element('ul', { className: 'css-tree ' + (open ? '' : 'hide') }));
    },

    /**
     * Method for displaying the tree root. Will re-load data if necessary.
     */
    displayRoot: function () {
        var $root, data, i;

        if (this.cache.hasOwnProperty('n' + this.options.rootNodeId)) {
            data = this.cache['n' + this.options.rootNodeId];

            $root = new Element('ul', { className: 'css-tree' });

            $root.update(this.renderNode(data).addClassName('root'));

            this.$container.update($root);

            data.children.sort(this.options.nodeSorting);

            for (i in data.children) {
                if (!data.children.hasOwnProperty(i)) {
                    continue;
                }

                $root.insert(this.renderNode(data.children[i]));

                // Don't iterate over a redundant node, creating a recursion.
                if (this.options.rootNodeId == data.children[i].nodeId) {
                    continue;
                }

                if (this.isOpenNode(data.children[i].nodeId)) {
                    this.displayChildrenNodes(data.children[i].nodeId);
                }
            }

            return;
        }

        this.loadChildrenNodes(this.options.rootNodeId, this.displayRoot.bind(this));
    },

    /**
     * Method for displaying children nodes. Will re-load data if necessary.
     *
     * @param nodeId
     */
    displayChildrenNodes: function (nodeId) {
        var $childrenList = this.$container.down('[data-id="' + nodeId + '"] ul'),
            data, i;

        if (this.cache.hasOwnProperty('n' + nodeId) && (!this.cache['n' + nodeId].hasChildren || (this.cache['n' + nodeId].hasChildren && this.cache['n' + nodeId].children.length))) {
            data = this.cache['n' + nodeId];
            $childrenList.update();

            data.children.sort(this.options.nodeSorting);

            for (i in data.children) {
                if (!data.children.hasOwnProperty(i)) {
                    continue;
                }

                $childrenList.insert(this.renderNode(data.children[i]));

                if (this.isOpenNode(data.children[i].nodeId)) {
                    this.displayChildrenNodes(data.children[i].nodeId);
                }
            }

            return;
        }

        $childrenList
            .insert(new Element('li')
                .update(new Element('img', { src: window.dir_images + 'axialis/user-interface/loading.svg', className: 'ml10 animation-rotate' })));

        this.loadChildrenNodes(nodeId, this.displayChildrenNodes.bind(this));
    },

    /**
     * Method for loading children nodes via ajax.
     *
     * @param nodeId
     * @param callback
     */
    loadChildrenNodes: function (nodeId, callback) {
        throw "There is no data for element " + nodeId + "! Please extend this class and implement 'loadChildrenNodes'.";
    },

    /**
     * Use this method to get the correct 'toggle' icon.
     *
     * @param open
     * @returns {string}
     */
    getToggleImage: function(open) {
        return window.dir_images + 'tree-structure/' + (open ? 'minus' : 'plus') + '.svg'
    }
});
