package flare.vis.operator.distortion
{
    import flash.geom.Rectangle;
    
    /**
     * Computes a bifocal distortion of space, magnifying a focus region of space
     * and uniformly demagnifying the rest of the space. The affect is akin to
     * passing a magnifying glass over the data.
     * 
     * <p>
     * For more details on this form of transformation, see Y. K. Leung and 
     * M. D. Apperley, "A Review and Taxonomy of Distortion-Oriented Presentation
     * Techniques", in Transactions of Computer-Human Interaction (TOCHI),
     * 1(2): 126-160 (1994). Available online at
     * <a href="portal.acm.org/citation.cfm?id=180173&dl=ACM">
     * portal.acm.org/citation.cfm?id=180173&dl=ACM</a>.
     * </p>
     */
    public class BifocalDistortion extends Distortion
    {
        private var _rx:Number;
        private var _mx:Number;
        private var _ry:Number;
        private var _my:Number;
        
        /**
         * <p>Create a new BifocalDistortion with the specified range and 
         * magnification along both axes.</p>
         * 
         * <p><strong>NOTE:</strong>if the range value times the magnification
         * value is greater than 1, the resulting distortion can exceed the
         * display bounds.</p>
         * 
         * @param xrange the range around the focus that should be magnified along
         *  the x direction. This specifies the horizontal size of the magnified 
         *  focus region, and should be a value between 0 and 1, 0 indicating no
         *  focus region and 1 indicating the whole display.
         * @param xmag how much magnification along the x direction should be used
         *  in the focal area
         * @param yrange the range around the focus that should be magnified along
         *  the y direction. This specifies the vertical size of the magnified 
         *  focus region, and should be a value between 0 and 1, 0 indicating no
         *  focus region and 1 indicating the whole display.
         * @param ymag how much magnification along the y direction should be used
         *  in the focal area
         */
        public function BifocalDistortion(xRange:Number=0.1, xMagnify:Number=3,
            yRange:Number=0.1, yMagnify:Number=3)
        {
            _rx = xRange;
            _mx = xMagnify;
            _ry = yRange;
            _my = yMagnify;
            distortX = !(_rx == 0 || _mx == 1.0);
            distortY = !(_ry == 0 || _my == 1.0);
        }
        
        /** @inheritDoc */
        protected override function xDistort(x:Number):Number
        {
            return bifocal(x, layoutAnchor.x, _rx, _mx, _b.left, _b.right);    
        }
        
        /** @inheritDoc */
        protected override function yDistort(y:Number):Number
        {
            return bifocal(y, layoutAnchor.y, _ry, _my, _b.top, _b.bottom);    
        }
        
        /** @inheritDoc */
        protected override function sizeDistort(bb:Rectangle, x:Number, y:Number):Number
        {
            var xmag:Boolean = false, ymag:Boolean = false;
            var m:Number, c:Number, a:Number, min:Number, max:Number;
        
            if (distortX) {
                c = (bb.left+bb.right)/2;
                a = layoutAnchor.x;
                min = _b.left;
                max = _b.right;
                m = c<a ? a-min : max-a;
                if (m == 0) m = max-min;
                xmag = (Math.abs(c-a) <= _rx*m )
            }
            
            if (distortY) {
                c = (bb.top+bb.bottom)/2;
                a = layoutAnchor.y;
                min = _b.top;
                max = _b.bottom;
                m = c<a ? a-min : max-a;
                if (m == 0) m = max-min;
                ymag = (Math.abs(c-a) <= _ry*m);
            }
            
            if (xmag && !distortY) {
                return _mx;
            } else if (ymag && !distortX) {
                return _my;
            } else if (xmag && ymag) {
                return Math.min(_mx, _my);
            } else {
                return Math.min((1-_rx*_mx)/(1-_rx), (1-_ry*_my)/(1-_ry));
            }
        }
        
        private function bifocal(x:Number, a:Number, r:Number, 
                           mag:Number, min:Number, max:Number):Number
        {
            var m:Number, v:Number, s:Number, bx:Number;
            m = (x<a ? a-min : max-a);
            if ( m == 0 ) m = max-min;
            v = x - a, s = m*r;
            if ( Math.abs(v) <= s ) {  // in focus
                return v*mag + a;
            } else {                   // out of focus
                bx = r*mag;
                x = ((Math.abs(v)-s) / m) * ((1-bx)/(1-r));
                return (v<0?-1:1)*m*(x + bx) + a;
            }
        }
        
    } // end of class BifocalDistortion
}