package flare.vis.operator.layout
{
    import flare.scale.ScaleType;
    import flare.util.Property;
    import flare.vis.axis.CartesianAxes;
    import flare.vis.data.Data;
    import flare.vis.data.DataSprite;
    import flare.vis.data.ScaleBinding;
    
    /**
     * Layout that places items along the X and Y axes according to data
     * properties. The AxisLayout can also compute stacked layouts, in which
     * elements that share the same data values along an axis are consecutively
     * stacked on top of each other.
     */
    public class AxisLayout extends Layout
    {
        protected var _xStacks:Boolean = false;
        protected var _yStacks:Boolean = false;
        
        protected var _xField:Property;
        protected var _yField:Property;
        protected var _xBinding:ScaleBinding;
        protected var _yBinding:ScaleBinding;
        
        // ------------------------------------------------
        
        /** The x-axis source property. */
        public function get xField():String { return _xBinding.property; }
        public function set xField(f:String):void { _xBinding.property = f; }
        
        /** The y-axis source property. */
        public function get yField():String { return _yBinding.property; }
        public function set yField(f:String):void { _yBinding.property = f; }
        
        /** Flag indicating if values should be stacked according to their
         *  x-axis values. */
        public function get xStacked():Boolean { return _xStacks; }
        public function set xStacked(b:Boolean):void { _xStacks = b; }

        /** Flag indicating if values should be stacked according to their
         *  y-axis values. */
        public function get yStacked():Boolean { return _yStacks; }
        public function set yStacked(b:Boolean):void { _yStacks = b; }
        
        /** The scale binding for the x-axis. */
        public function get xScale():ScaleBinding { return _xBinding; }
        public function set xScale(b:ScaleBinding):void {
            if (_xBinding) {
                if (!b.property) b.property = _xBinding.property;
                if (!b.group) b.group = _xBinding.group;
                if (!b.data) b.data = _xBinding.data;
            }
            _xBinding = b;
        }
        
        /** The scale binding for the y-axis. */
        public function get yScale():ScaleBinding { return _yBinding; }
        public function set yScale(b:ScaleBinding):void {
            if (_yBinding) {
                if (!b.property) b.property = _yBinding.property;
                if (!b.group) b.group = _yBinding.group;
                if (!b.data) b.data = _yBinding.data;
            }
            _yBinding = b;
        }
        
        // --------------------------------------------------------------------
        
        /**
         * Creates a new AxisLayout
         * @param xAxisField the x-axis source property
         * @param yAxisField the y-axis source property
         * @param xStacked indicates if values should be stacked according to
         *  their x-axis values
         * @param yStacked indicates if values should be stacked according to
         *  their y-axis values
         */        
        public function AxisLayout(xAxisField:String=null, yAxisField:String=null,
                                   xStacked:Boolean=false, yStacked:Boolean=false)
        {
            layoutType = CARTESIAN;
            
            _xBinding = new ScaleBinding();
            _xBinding.group = Data.NODES;
            _xBinding.property = xAxisField;
            _xStacks = xStacked;
            
            _yBinding = new ScaleBinding();
            _yBinding.group = Data.NODES;
            _yBinding.property = yAxisField;
            _yStacks = yStacked;
        }
        
        /** @inheritDoc */
        public override function setup():void
        {
            if (visualization==null) return;
            _xBinding.data = visualization.data;
            _yBinding.data = visualization.data;
            
            var axes:CartesianAxes = super.xyAxes;
            axes.xAxis.axisScale = _xBinding;
            axes.yAxis.axisScale = _yBinding;
        }
        
        /** @inheritDoc */
        protected override function layout():void
        {
            _xField = Property.$(_xBinding.property);
            _yField = Property.$(_yBinding.property);
            
            var axes:CartesianAxes = super.xyAxes;
            _xBinding.updateBinding(); axes.xAxis.axisScale = _xBinding;
            _yBinding.updateBinding(); axes.yAxis.axisScale = _yBinding;
            
            if (_xStacks || _yStacks) { rescale(); }            
            var x0:Number = axes.originX;
            var y0:Number = axes.originY;

            var xmapPos:Object = _xStacks ? new Object() : null;
            var xmapNeg:Object = _xStacks ? new Object() : null;
            var ymapPos:Object = _yStacks ? new Object() : null;
            var ymapNeg:Object = _yStacks ? new Object() : null;
            
            visualization.data.nodes.visit(function(d:DataSprite):void {
                var dx:Object, dy:Object, x:Number, y:Number, s:Number, z:Number;
                var o:Object = _t.$(d);
                dx = _xField.getValue(d); dy = _yField.getValue(d);
                
                var map:Object;
                if (_xField != null) {
                    x = axes.xAxis.X(dx);
                    if (_xStacks) {
                        map = (dx < 0 ? xmapNeg : xmapPos);
                        z = x - x0;
                        s = z + (isNaN(s=map[dy]) ? 0 : s);
                        o.x = x0 + s;
                        o.w = z;
                        map[dy] = s;
                    } else {
                        o.x = x;
                        o.w = x - x0;
                    }
                }
                if (_yField != null) {
                    y = axes.yAxis.Y(dy);
                    if (_yStacks) {
                        map = (dy < 0 ? ymapNeg : ymapPos);
                        z = y - y0;
                        s = z + (isNaN(s=map[dx]) ? 0 : s);
                        o.y = y0 + s;
                        o.h = z;
                        map[dx] = s;
                    } else {
                        o.y = y;
                        o.h = y - y0;
                    }
                }
            });
        }
        
        /** @private */
        protected function rescale():void {
            var xmapPos:Object = _xStacks ? new Object() : null;
            var xmapNeg:Object = _xStacks ? new Object() : null;
            var ymapPos:Object = _yStacks ? new Object() : null;
            var ymapNeg:Object = _yStacks ? new Object() : null;
            var xmax:Number = 0;
            var xmin:Number = 0;
            var ymax:Number = 0;
            var ymin:Number = 0;
            
            visualization.data.nodes.visit(function(d:DataSprite):void {
                var x:Object = _xField.getValue(d);
                var y:Object = _yField.getValue(d);
                var v:Number;
                
                if (_xStacks) {
                    if (x < 0) { 
                        v = isNaN(xmapNeg[y]) ? 0 : xmapNeg[y];
                        xmapNeg[y] = v = (Number(x) + v);
                        if (v < xmin) xmin = v;
                    } else {
                        v = isNaN(xmapPos[y]) ? 0 : xmapPos[y];
                        xmapPos[y] = v = (Number(x) + v);
                        if (v > xmax) xmax = v;
                    }
                }
                if (_yStacks) {
                    if (y < 0) {
                        v = isNaN(ymapNeg[x]) ? 0 : ymapNeg[x];
                        ymapNeg[x] = v = (Number(y) + v);
                        if (v < ymin) ymin = v;
                        
                    } else {
                        v = isNaN(ymapPos[x]) ? 0 : ymapPos[x];
                        ymapPos[x] = v = (Number(y) + v);
                        if (v > ymax) ymax = v;
                    }
                }
            });
            
            if (_xStacks) {
                _xBinding.scaleType = ScaleType.LINEAR;
                _xBinding.preferredMin = xmin;
                _xBinding.preferredMax = xmax;
            }
            if (_yStacks) {
                _yBinding.scaleType = ScaleType.LINEAR;
                _yBinding.preferredMin = ymin;
                _yBinding.preferredMax = ymax;
            }
        }
        
    } // end of class AxisLayout
}