/**
 * Abstract class for D3 components
 * with the [Tree layout](https://github.com/d3/d3-hierarchy/#tree).
 */
Ext.define('Ext.d3.hierarchy.tree.Tree', {
    extend: 'Ext.d3.hierarchy.Hierarchy',

    config: {
        treeCls: 'tree',

        nodeTransform: null,

        /**
         * @private
         * Specifies a fixed distance between the parent and child nodes.
         * By default, the distance is `tree depth / (number of tree levels - 1)`.
         * @cfg {Number} [depth=0]
         */
        depth: 0,

        /**
         * The radius of the circle that represents a node.
         * @cfg {Number} [nodeRadius=5]
         */
        nodeRadius: 5,

        nodeTransition: true,

        /**
         * [Fixed size](https://github.com/mbostock/d3/wiki/Tree-Layout#nodeSize),
         * of each node as a two-element array of numbers representing x and y.
         * Note that if the `nodeSize` config is not set,
         * the [layout size](https://github.com/d3/d3-hierarchy/blob/master/README.md#tree_size)
         * will be set to the size of the component's {@link #getScene scene}, but the resulting
         * tree node layout is not guaranteed to stretch to fill the whole scene.
         * @cfg {Number[]} nodeSize
         */
        nodeSize: null,

        noSizeLayout: false,

        renderLinks: true,

        transitions: {
            select: {
                targetScale: 1.5
            }
        }
    },

    updateTreeCls: function(treeCls, oldTreeCls) {
        var baseCls = this.baseCls,
            el = this.element;

        if (treeCls && Ext.isString(treeCls)) {
            el.addCls(treeCls, baseCls);

            if (oldTreeCls) {
                el.removeCls(oldTreeCls, baseCls);
            }
        }
    },

    applyLayout: function() {
        return d3.tree();
    },

    updateNodeSize: function(nodeSize) {
        var layout = this.getLayout();

        layout.nodeSize(nodeSize);
    },

    updateColorAxis: function(colorAxis) {
        var me = this;

        if (!me.isConfiguring) {
            me.getRenderedNodes()
                .select('circle')
                .style('fill', function(node) {
                    return colorAxis.getColor(node);
                });
        }
    },

    hideRoot: function() {
        // Overriding the parent method here.
        // With trees, we have to have the root hidden via
        // `display: none` instead of `visibility: hidden`,
        // so that the hidden nodes are not visible to
        // `svgElement.getBBox`. In all other cases, hiding
        // via `visibility: hidden` is the preferred way,
        // because we can still measure hidden things.
        var me = this,
            rootVisible = me.getRootVisible();

        me.nodesGroup
            .select('.' + me.defaultCls.root)
            .classed(me.defaultCls.hidden, !rootVisible);
    },

    onNodeSelect: function(node, el) {
        var me = this,
            transitionCfg = me.getTransitions().select,
            to = transitionCfg.targetScale,
            from = transitionCfg.sourceScale,
            transition = me.createTransition('select'),
            circle = el.select('circle');

        me.callParent(arguments);

        // Remove the fill given by the `colorAxis`, so that
        // the CSS style can be used to specify the color
        // of the selection.
        circle.style('fill', null);

        if (transition.duration()) {
            circle
                .transition(transition)
                .attr('transform', 'scale(' + to + ',' + to + ')')
                .transition(transition)
                .attr('transform', 'scale(' + from + ',' + from + ')');
        }
    },

    onNodeDeselect: function(node, el) {
        var me = this,
            colorAxis = me.getColorAxis();

        me.callParent(arguments);

        // Restore the original color.
        // (see 'onNodeSelect' comments).
        el.select('circle')
            .style('fill', function(node) {
                return colorAxis.getColor(node);
            });
    }

});
