/*
 * Copyright (c) 2009, Tony Kohar. All Rights Reserved.
 *
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.qi4j.envisage.graph;

import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import prefuse.Display;
import prefuse.action.layout.graph.TreeLayout;
import prefuse.render.Renderer;
import prefuse.visual.NodeItem;

/* package */ final class StackedLayout
    extends TreeLayout
{
    /* package */ static int INSET = 10;

    private int zoom = 2;

    /* package */ StackedLayout( String group )
    {
        super( group );
    }

    @Override
    public void run( double frac )
    {
        // setup
        NodeItem root = getLayoutRoot();
        layout( root, 0, 0 );

        Rectangle2D bounds = root.getBounds();
        Display display = this.getVisualization().getDisplay( 0 );
        Dimension size = new Dimension( (int) bounds.getWidth(), (int) bounds.getHeight() );
        display.setSize( size );

        if( !display.isValid() )
        {
            display.validate();
        }
    }

    /* package */ void zoomOut()
    {
        zoom--;
        if( zoom < 1 )
        {
            zoom = 1;
        }
    }

    /* package */ void zoomIn()
    {
        zoom++;
        if( zoom > 4 )
        {
            zoom = 4;
        }
    }

    /* package */ void zoom( int zoom )
    {
        this.zoom = zoom;
    }

    /* package */ int getZoom()
    {
        return zoom;
    }

    private Dimension getItemMinSize( NodeItem node, Dimension minSize )
    {
        if( minSize == null )
        {
            minSize = new Dimension( 0, 0 );
        }

        String label = node.getString( "name" );
        FontMetrics fm = Renderer.DEFAULT_GRAPHICS.getFontMetrics( StackedGraphDisplay.FONT );
        int width = fm.stringWidth( label );
        int height = fm.getHeight();
        minSize.setSize( width + INSET + INSET, height + INSET + INSET );

        return minSize;
    }

    private void layout( NodeItem node, double x, double y )
    {
        Dimension minSize = getItemMinSize( node, null );
        node.setBounds( x, y, minSize.width, minSize.height );

        int depth = node.getDepth();

        if( depth > zoom )
        {
            node.setBounds( x, y, 0, 0 );
            node.setVisible( false );
        }
        else
        {
            node.setVisible( true );
        }

        double cx = x + INSET;
        double cy = y + minSize.height;

        Area area = new Area( node.getBounds() );

        boolean hasChild = false;
        for( int i = 0; i < node.getChildCount(); i++ )
        {
            hasChild = true;
            NodeItem child = (NodeItem) node.getChild( i );

            layout( child, cx, cy );
            area.add( new Area( child.getBounds() ) );

            // shifting location calculation
            Rectangle2D nodeRect = child.getBounds();
            if( depth == 0 )
            {
                // layer
                cy = cy + ( INSET * 2 ) + nodeRect.getHeight();
            }
            if( depth == 1 )
            {
                // module
                cx = cx + INSET + nodeRect.getWidth();
            }
            else if( depth == 2 )
            {
                // type container
                cx = cx + INSET + nodeRect.getWidth();
            }
            else if( depth == 3 )
            {
                // type
                cy = cy + INSET + nodeRect.getHeight();
            }
        }

        Rectangle2D bounds = area.getBounds2D();
        if( hasChild && depth <= zoom )
        {
            bounds.setRect( x, y, bounds.getWidth() + INSET, bounds.getHeight() + INSET );
        }

        node.setBounds( x, y, bounds.getWidth(), bounds.getHeight() );

        // relayout the child so it have consistent width or height
        //int depth = parent.getDepth();
        if( depth == 0 )
        {
            arrangeChildVertically( node );
        }
        else if( depth == 1 )
        {
            arrangeChildHorizontally( node );
        }
        else if( depth == 2 )
        {
            arrangeChildHorizontally( node );
        }
        else if( depth == 3 )
        {
            arrangeChildVertically( node );
        }
    }

    private void arrangeChildVertically( NodeItem parent )
    {
        double maxW = 0;
        for( int i = 0; i < parent.getChildCount(); i++ )
        {
            NodeItem node = (NodeItem) parent.getChild( i );
            Rectangle2D bounds = node.getBounds();
            maxW = Math.max( maxW, bounds.getWidth() );
        }

        for( int i = 0; i < parent.getChildCount(); i++ )
        {
            NodeItem node = (NodeItem) parent.getChild( i );
            Rectangle2D bounds = node.getBounds();
            node.setBounds( bounds.getX(), bounds.getY(), maxW, bounds.getHeight() );
        }
    }

    private void arrangeChildHorizontally( NodeItem parent )
    {
        double maxH = 0;
        for( int i = 0; i < parent.getChildCount(); i++ )
        {
            NodeItem node = (NodeItem) parent.getChild( i );
            Rectangle2D bounds = node.getBounds();
            maxH = Math.max( maxH, bounds.getHeight() );
        }

        for( int i = 0; i < parent.getChildCount(); i++ )
        {
            NodeItem node = (NodeItem) parent.getChild( i );
            Rectangle2D bounds = node.getBounds();
            node.setBounds( bounds.getX(), bounds.getY(), bounds.getWidth(), maxH );
        }
    }
}
