# Source: ui/tree-list.reel/tree-list.js

    /**
     * @module ui/tree-list.reel
     * @requires montage/ui/component
     */
    var Component = require("ui/component").Component,
        TreeNode = require("core/tree-controller").TreeNode,
        WeakMap = require("collections/weak-map");

    /**
     * @class TreeList
     * @extends Component
     */
    exports.TreeList = Component.specialize(/** @lends TreeList.prototype */ {

        _controller: {
            value: null
        },

        controller: {
            get: function () {
                return this._controller;
            },
            set: function (value) {
                if (this._controller !== value) {
                    this._controller = value;
                    this._heights = new WeakMap();
                    if (this._controller) {
                        this._controller.delegate = this;
                    }
                }
            }
        },

        _rowTopMargins: {
            get: function () {
                if (!this.__rowTopMargins) {
                    this.__rowTopMargins = [];
                }
                return this.__rowTopMargins;
            }
        },

        _totalHeight: {
            value: 0
        },

        _isRootExpanded: {
            value: false
        },

        isRootExpanded: {
            get: function () {
                return this._isRootExpanded;
            },
            set: function (value) {
                if (this._isRootExpanded !== value) {
                    this._isRootExpanded = value;
                    if (this._controller) {
                        this._controller.expandNode(this._controller.data);
                    }
                }
            }
        },

        _isRootVisible: {
            value: true
        },

        isRootVisible: {
            get: function () {
                return this._isRootVisible;
            },
            set: function (value) {
                if (this._isRootVisible !== value) {
                    this._isRootVisible = value;
                    this.handleTreeChange();
                }
            }
        },

        _data: {
            value: null
        },

        handleTreeChange: {
            value: function () {
                var i, n;
                if (this._controller) {
                    if (this._controller.data !== this._data) {
                        this._data = this._controller.data;
                        if (this.isRootExpanded || !this.isRootVisible) {
                            this._controller.expandNode(this._controller.data);
                        }
                    }
                }
                this._heights = new WeakMap();
                if (this.repetition) {
                    this.repetitionController.content = this.getIterations();
                }
                if (this.controller &&
                    this.controller.data &&
                    this.controller.data.children &&
                    typeof this.rowHeight === "function") {
                    this._totalHeight = 0;
                    this._rowTopMargins.length = 0;
                    this._rowTopMargins.push(0);
                    for (i = 0, n = this.controller.data.children.length; i < n; i += 1) {
                        this._totalHeight += this.rowHeight(this.controller.data.children[i]);
                        this._rowTopMargins.push(this._totalHeight);
                    }
                }
            }
        },

        /**
            Represents the range of visible rows in the view window as
            an interval [startRow, endRow)
        */
        _visibilityRange: {
            value: [0, 0]
        },

        visibilityRange: {
            get: function () {
                return this._visibilityRange;
            },
            set: function (value) {
                this._visibilityRange = value;
                this.repetitionController.content = this.getIterations();
            }
        },

        _getNodeHeight: {
            value: function (node) {
                var expansionMetadata,
                    height;

                if (this._controller) {
                    expansionMetadata = this._controller._expansionMap.get(node);
                    if (expansionMetadata) {
                        height = this._heights.get(node);
                        if (!height) {
                            height = this._computeExpandedNodeHeight(node);
                            this._heights.set(node, height);
                        }
                        return height;
                    }
                    return 1;
                }
            }
        },

        _computeExpandedNodeHeight: {
            value: function (node) {
                if (this._controller) {
                    var children = this._controller.childrenFromNode(node),
                        height = 1,
                        length,
                        i;

                    if (children) {
                        length = children.length;
                        for (i = 0; i < length; i++) {
                            height += this._getNodeHeight(children[i]);
                        }
                    }
                    return height;
                }
            }
        },

        _addIterations: {
            value: function (node, iterationsArray, row, depth, parent) {
                var length,
                    children,
                    height,
                    treeNode,
                    i;

                if (node) {
                    treeNode = new TreeNode(node, this._controller);
                    treeNode.height = this._getNodeHeight(node);
                    treeNode.parent = parent;
                    treeNode.row = row;
                    if (!this.isRootVisible && (node === this._controller.data)) {
                        iterationsArray.push(treeNode);
                        treeNode.depth = depth;
                        depth--;
                    } else {
                        treeNode.depth = depth;
                        iterationsArray.push(treeNode);
                        row++;
                    }
                    if (this._controller.isNodeExpanded(node)) {
                        children = this._controller.childrenFromNode(node);
                        if (children) {
                            length = children.length;
                            for (i = 0; i < length; i++) {
                                if (this._isVisible(row, height = this._getNodeHeight(children[i]))) {
                                    row = this._addIterations(children[i], iterationsArray, row, depth + 1, treeNode);
                                } else {
                                    row += height;
                                }
                            }
                        }
                    }
                }
                return row;
            }
        },

        _isVisible: {
            value: function (startRow, height) {
                var endRow = startRow + height;

                return ((startRow < this._visibilityRange[1]) && (endRow > this._visibilityRange[0]));
            }
        },

        getIterations: {
            value: function () {
                var iterations = [];

                if (this._controller.data && (this._visibilityRange[1] > this._visibilityRange[0])) {
                    if (this._isVisible(0, this._getNodeHeight(this._controller.data))) {
                        this._addIterations(this._controller.data, iterations, 0, 0);
                    }
                }
                return iterations;
            }
        },

        templateDidLoad: {
            value: function() {
                var self = this;

                this.repetition.willDraw = function () {
                    self.needsDraw = true;
                };
            }
        },

        enterDocument: {
            value: function (firstTime) {
                if (firstTime) {
                    window.addEventListener("resize", this, false);
                    this._element.addEventListener("scroll", this, false);
                }
                this.handleScroll();
                this.handleTreeChange();
            }
        },

        handleResize: {
            value: function () {
                // Here we use the window height instead of the element height for
                // the calculation so that we still have enough rows even if the
                // tree list is later resized (as long as it's not resized taller
                // than the window). We're wasting a few rows when the tree list
                // is shorter than the window, but not that many.
                var startRow, height, endRow, index;
                if (typeof this.rowHeight === "function") {
                    index = 0;
                    height = this._element.scrollTop;
                    while (this._rowTopMargins[index + 1] < height) {
                        index++;
                    }
                    startRow = index;
                    height = this._rowTopMargins[startRow] + window.innerHeight;
                    while (this._rowTopMargins[index] < height) {
                        index++;
                    }
                    endRow = index;
                } else {
                    startRow = this._element.scrollTop / this._rowHeight;
                    height = window.innerHeight / this._rowHeight;
                    endRow = startRow + height;
                }
                this.visibilityRange = [startRow, endRow];
            }
        },

        _rowHeight: {
            value: 40
        },

        rowHeight: {
            get: function () {
                return this._rowHeight;
            },
            set: function (value) {
                if (this._rowHeight !== value) {
                    this._rowHeight = value;
                    this.needsDraw = true;
                    if (this.repetitionController) {
                        this.handleTreeChange();
                        this.handleResize();
                    }
                }
            }
        },

        _indentationWidth: {
            value: 30
        },

        indentationWidth: {
            get: function () {
                return this._indentationWidth;
            },
            set: function (value) {
                if (this._indentationWidth !== value) {
                    this._indentationWidth = value;
                    this.needsDraw = true;
                }
            }
        },

        handleScroll: {
            value: function () {
                this.handleResize();
            }
        },

        draw: {
            value: function () {
                var iteration,
                    element,
                    rowHeight,
                    i, length;

                for (i = 0, length = this.repetition._drawnIterations.length; i < length; i++) {
                    iteration = this.repetition._drawnIterations[i];
                    element = iteration.cachedFirstElement || iteration.firstElement;
                    if (typeof this.rowHeight === "function") {
                        if (!this.isRootVisible && iteration.object.data === this.controller.data) {
                            rowHeight = 0;
                            element.style.marginTop = 0;
                            element.style.height = this._totalHeight + "px";
                            element.style.visibility = "hidden";
                        } else {
                            rowHeight = this._rowTopMargins[iteration.object.row + 1] - this._rowTopMargins[iteration.object.row];
                            element.style.height = rowHeight + "px";
                            element.style.marginTop = this._rowTopMargins[iteration.object.row] + "px";
                            element.style.visibility = "visible";
                        }
                    } else {
                        element.style.marginTop = this._rowHeight * iteration.object.row + "px";
                        if (!this.isRootVisible && iteration.object.data === this.controller.data) {
                            element.style.height = this._rowHeight * (iteration.object.height - 1) + "px";
                            element.style.visibility = "hidden";
                        } else {
                            element.style.height = this._rowHeight * iteration.object.height + "px";
                            element.style.visibility = "visible";
                        }
                    }
                    element.style.marginLeft = this._indentationWidth * iteration.object.depth + "px";
                }
            }
        }

    });