package flare.vis.operator.layout
{
    import flare.animate.TransitionEvent;
    import flare.animate.Transitioner;
    import flare.vis.Visualization;
    import flare.vis.axis.Axes;
    import flare.vis.axis.CartesianAxes;
    import flare.vis.data.DataList;
    import flare.vis.data.DataSprite;
    import flare.vis.data.EdgeSprite;
    import flare.vis.data.NodeSprite;
    import flare.vis.operator.Operator;
    
    import flash.display.Shape;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    /**
     * Base class for all operators that perform spatial layout. Provides
     * methods for retrieving the desired layout bounds, providing a layout
     * anchor point, and returning the layout root (for tree layouts in
     * particular). This class also provides convenience methods for
     * manipulating the visibility of axes and performing common updates
     * to edge control points in graph/tree visualizations.
     */
    public class Layout extends Operator
    {
        /** Constant indicating Cartesian (x, y) coordinates. */
        public static const CARTESIAN:String = "cartesian";
        /** Constant indicating polar (radius, angle) coordinates. */
        public static const POLAR:String = "polar";
        
        /** @private */
        protected static const _dummy:Shape = new Shape();
        /** @private */
        protected static const _rect:Rectangle = new Rectangle();
        
        // -- Properties ------------------------------------------------------
                
        /** The type of layout and axes. This value should be
         *  <code>CARTESIAN</code> for x,y axes, <code>POLAR</code> for polar
         *  coordinates (radius, angle), or null for no axes. */
        public var layoutType:String = null;
        
        /** A transitioner for storing value updates. */
        protected var _t:Transitioner = null;
        
        protected var _anchor:Point = new Point(0,0);
        protected var _setAnchor:Boolean = false;
        
        private var _bounds:Rectangle = null;
        private var _root:DataSprite = null;
        
        /** The layout bounds for the layout. If this value is not explicitly
         *  set, the bounds for the visualization is returned. */
        public function get layoutBounds():Rectangle {
            if (_bounds != null) return _bounds;
            if (visualization != null) return visualization.bounds;
            return null;
        }
        public function set layoutBounds(b:Rectangle):void { _bounds = b; }
        
        /** The layout anchor, used by some layout instances to place an
         *  initial item or determine a focal point. */
        public function get layoutAnchor():Point {
            if (!_setAnchor)
                autoAnchor();
            return _anchor;
        }
        public function set layoutAnchor(p:Point):void {
            _anchor = p;
            _setAnchor = true;
        }
        
        /** Automatically-generate an anchor point. */
        protected function autoAnchor():void
        {
            if (layoutType == POLAR) {
                var b:Rectangle = layoutBounds;
                _anchor.x = (b.left + b.right) / 2;
                _anchor.y = (b.top + b.bottom) / 2;
            } else {
                _anchor.x = 0;
                _anchor.y = 0;
            }
        }
        
        /** The layout root, the root node for tree layouts. */
        public function get layoutRoot():DataSprite {
            if (_root != null) return _root;
            if (visualization != null) {
                return visualization.data.tree.root;
            }
            return null;
        }
        public function set layoutRoot(r:DataSprite):void { _root = r; }
        
                
        // -- Placement and Axis Helpers --------------------------------------
        
        /** @inheritDoc */
        public override function operate(t:Transitioner=null):void
        {
            _t = (t ? t : Transitioner.DEFAULT);
            adjustAxes();
            layout();
            _t = null;
        }
        
        /**
         * Calculates the spatial layout of visualized items. Layout operators
         * override this method with their layout implementations.
         * @param t a Transitioner instance for collecting value updates.
         */
        protected function layout():void
        {
            // sub-classes should override
        }
        
        /** @private */
        protected function adjustAxes():void
        {
            if (layoutType == CARTESIAN) {
                showAxes(_t);
            } else {
                hideAxes(_t);
            }
        }
        
        /**
         * Reveals the axes.
         * @param t a transitioner to collect value updates
         * @return the input transitioner
         */
        public function showAxes(t:Transitioner=null):Transitioner
        {
            var axes:Axes = visualization.axes;
            if (axes == null || axes.visible) return t;
            
            if (t==null || t.immediate) {
                axes.alpha = 1;
                axes.visible = true;
            } else {
                t.$(axes).alpha = 1;
                t.$(axes).visible = true;
            }
            return t;
        }
        
        /**
         * Hides the axes.
         * @param t a transitioner to collect value updates
         * @return the input transitioner
         */
        public function hideAxes(t:Transitioner=null):Transitioner
        {
            var axes:Axes = visualization.axes;
            if (axes == null || !axes.visible) return t;
            
            if (t==null || t.immediate) {
                axes.alpha = 0;
                axes.visible = false;
            } else {
                t.$(axes).alpha = 0;
                t.$(axes).visible = false;
            }
            return t;
        }
        
        /**
         * Returns the visualization's axes as a CartesianAxes instance.
         * Creates/modifies existing axes as needed to ensure the
         * presence of CartesianAxes.
         */
        protected function get xyAxes():CartesianAxes
        {
            var vis:Visualization = visualization;
            if (vis == null) return null;
            
            if (vis.xyAxes == null) {
                vis.axes = new CartesianAxes();
            }
            return vis.xyAxes;
        }
        
        /**
         * Returns an angle value that minimizes the angular distance
         * between a reference angle and a target angle. This
         * method may shift the angle value by multiples of 2 pi.
         * @param a1 the reference angle to stay close to
         * @param a2 the target angle value
         * @return an angle that minimizes the distance
         */
        protected function minAngle(a1:Number, a2:Number):Number
        {
            var inc:Number = 2*Math.PI*(a1 > a2 ? 1 : -1);
            for (; Math.abs(a1-a2) > Math.PI; a2 += inc);
            return a2;
        }
        
        // -- Edge Helpers ----------------------------------------------------
        
        private static var _clear:Boolean;
        
        /**
         * Updates all edges to be straight lines. Useful for undoing the
         * results of layouts that route edges using edge control points.
         * @param list a data list of edges to straighten
         * @param t a transitioner to collect value updates
         */
        public static function straightenEdges(list:DataList,
            t:Transitioner):Transitioner
        {
            // set end points to mid-points
            list.visit(function(e:EdgeSprite):void {
                if (e.points == null) return;
                _clear = true;
                
                var src:NodeSprite = e.source;
                var trg:NodeSprite = e.target;
                
                // create new control points
                var i:uint, len:uint = e.points.length, f:Number;
                var cp:Array = new Array(len);
                var x1:Number, y1:Number, x2:Number, y2:Number;
                
                // get target end points
                x1 = t.$(src).x; y1 = t.$(src).y;
                x2 = t.$(trg).x; y2 = t.$(trg).y;
                
                for (i=0; i<len; i+=2) {
                    f = (i+2)/(len+2);
                    cp[i]   = x1 + f * (x2 - x1);
                    cp[i+1] = y1 + f * (y2 - y1);
                }
                t.$(e).points = cp;
            });
            return t;
        }
        
        /** @private */
        protected function updateEdgePoints(t:Transitioner=null):void
        {
            if (t==null || t.immediate || layoutType==POLAR) {
                clearEdgePoints();
            } else {
                _clear = false;
                straightenEdges(visualization.data.edges, t);
                // after transition, clear out control points
                if (_clear) {
                    var f:Function = function(evt:Event):void {
                        clearEdgePoints();
                        t.removeEventListener(TransitionEvent.END, f);
                    };
                    t.addEventListener(TransitionEvent.END, f);
                }
            }
        }
        
        /**
         * Strips all EdgeSprites in a visualization of any control points.
         */
        public function clearEdgePoints():void
        {
            visualization.data.edges["points"] = null;
        }
        
    } // end of class Layout
}