/*
 * org.openmicroscopy.shoola.agents.dataBrowser.layout.LayoutUtils 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2013 University of Dundee. All rights reserved.
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.agents.dataBrowser.layout;

//Java imports
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

//Third-party libraries

//Application-internal dependencies
import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageDisplay;
import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageNode;
import org.openmicroscopy.shoola.agents.dataBrowser.browser.ImageSet;
import org.openmicroscopy.shoola.agents.dataBrowser.browser.Thumbnail;
import org.openmicroscopy.shoola.agents.util.ViewerSorter;
import pojos.DataObject;

/** 
 * Helper class providing methods to lay out the nodes.
 *
 * @author Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since OME3.0
 */
public class LayoutUtils
{

    /** The default number of items per row. */
    public static final int DEFAULT_PER_ROW = 10;

    /** The minimum value for dimension.*/
    private static final int MIN = 1;

    /**
     * Scales the thumbnail of the specified new node.
     * 
     * @param newNode The new node to handle.
     * @param oldNode The original node.
     */
    private static void scaleImage(ImageNode newNode, ImageNode oldNode)
    {
        Thumbnail th = newNode.getThumbnail();
        th.scale(oldNode.getThumbnail().getScalingFactor());
    }

    /**
     * Sets the size of the specified {@link ImageSet} node if it doesn't
     * have child.
     * 
     * @param node The {@link ImageSet} node to lay out.
     */
    static void noChildLayout(ImageDisplay node)
    {
        node.getInternalDesktop().setPreferredSize(
                node.getTitleBar().getMinimumSize());
        node.setVisible(true);
    }

    /**
     * Finds out the dimensions of the largest child node in the specified
     * {@link ImageDisplay}.
     * This method calculates the area of each child node and then returns
     * the dimensions of the child having the largest area.
     * Note that the returned <code>Dimension</code> object can have 
     * <code>0</code>-width and height.  In particular this is always the
     * case if the specified parent <code>node</code> has no children.
     * 
     * @param node The parent node. Mustn't be <code>null</code>.
     * @return The dimensions of the child having the largest area.
     * @see #max(Dimension, Dimension)
     */
    static Dimension maxChildDim(ImageDisplay node)
    {
        Dimension maxDim = new Dimension(0, 0);
        Component[] comps = node.getInternalDesktop().getComponents();
        Component c;
        for (int i = 0; i < comps.length; i++) {
            c = comps[i];
            if (c instanceof ImageDisplay) {
                maxDim = max(maxDim, c.getPreferredSize());
            }
        }
        return maxDim;
    }

    /**
     * Finds out the dimensions of the largest child node in the specified
     * collection.
     * This method calculates the area of each child node and then returns
     * the dimensions of the child having the largest area.
     * Note that the returned <code>Dimension</code> object can have 
     * <code>0</code>-width and height. In particular this is always the
     * case if the specified parent <code>node</code> has no children.
     * 
     * @param images The collection to handle.
     * @return See above,
     */
    static Dimension maxChildDim(Collection images)
    {
        Dimension maxDim = new Dimension(0, 0);
        Iterator children = images.iterator();
        ImageDisplay child;
        while (children.hasNext()) {
            child = (ImageDisplay) children.next();
            maxDim = max(maxDim, child.getPreferredSize());
        }
        return maxDim;  //[0, 0] if no children.
    }

    /**
     * Lays out the specified images in a square grid.
     * 
     * @param root The root node.
     * @param imageNodes The collection of images to lay out.
     */
    static void doSquareGridLayout(ImageDisplay root, List imageNodes)
    {
        Iterator children = imageNodes.iterator();
        ImageDisplay child;
        Dimension maxDim = maxChildDim(imageNodes);
        int n = imageNodes.size();
        n = (int) Math.floor(Math.sqrt(n))+1;
        Dimension d;

        try {
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (!children.hasNext()) //Done, less than n^2 children.
                        return;  //Go to finally.
                    child = (ImageDisplay) children.next();
                    d = child.getPreferredSize();
                    child.setBounds(j*maxDim.width, i*maxDim.height, d.width,
                            d.height);
                }
            }
        } finally {
            Rectangle bounds = root.getContentsBounds();
            d = bounds.getSize();
            root.getInternalDesktop().setSize(d);
            root.getInternalDesktop().setPreferredSize(d);
        }
    }

    /**
     * Lays out all child nodes in the specified parent <code>node</code>
     * in a square grid. 
     * The size of each cell in the grid will be that of the largest child
     * in the parent <code>node</code>.
     * 
     * @param node The parent node. Mustn't be <code>null</code>.
     * @param sorter The node sorter.
     * @param itemsPerRow The number of items per row.
     */
    static void doSquareGridLayout(ImageDisplay node, ViewerSorter sorter, int
            itemsPerRow)
    {
        //First find out the max dim among children.
        Dimension maxDim = maxChildDim(node);
        //Then figure out the number of columns, which is the same as the
        //number of rows.
        int n = node.getChildrenDisplay().size();
        if (n == 0) {   //Node with no children.
            node.getInternalDesktop().setPreferredSize(
                    node.getTitleBar().getMinimumSize());
            node.setVisible(true);
            return;
        }

        Component[] comps = node.getInternalDesktop().getComponents();

        List l = new ArrayList();
        for (int i = 0; i < comps.length; i++) 
            if (comps[i] instanceof ImageDisplay)
                l.add(comps[i]);
        Dimension dd = node.getSize();
        if (dd.width == 0 || dd.height == 0 && node.getParentDisplay() != null)
            dd = node.getParentDisplay().getSize();
        l = sorter.sort(l);
        if (dd.width >= MIN && dd.height >= MIN) {
            if (maxDim.width != 0) 
                n = dd.width/maxDim.width;
            if (n == 0) {
                n = DEFAULT_PER_ROW;
            }
            if (itemsPerRow >= 1) n = itemsPerRow;
        } else {
            if (itemsPerRow >= 1) {
                n = itemsPerRow; 
            } else {
                n = l.size();
                if (n > DEFAULT_PER_ROW)
                    n = (int) Math.floor(Math.sqrt(n))+1;  //See note.
            }
        }

        //Finally do layout.
        Dimension d;
        ImageDisplay child;
        Iterator children = l.iterator();
        try {
            int i = 0;
            while (children.hasNext()) {
                for (int j = 0; j < n; j++) {
                    if (!children.hasNext()) //Done, less than n^2 children.
                        return;  //Go to finally.
                    child = (ImageDisplay) children.next();
                    d = child.getPreferredSize();
                    child.setBounds(j*maxDim.width, i*maxDim.height, d.width,
                            d.height);
                }
                i++;
            }
        } finally {
            Rectangle bounds = node.getContentsBounds();
            d = bounds.getSize();
            node.getInternalDesktop().setSize(d);
            node.getInternalDesktop().setPreferredSize(d);
        }
    }

    /**
     * Relays out the node when refreshing the display.
     * 
     * @param node The node to refresh.
     * @param oldNode The node previously displayed.
     * @param newNodes The nodes to layout.
     * @param oldNodes The previously displayed out nodes.
     */
    static void redoLayout(ImageSet node, ImageSet oldNode, Collection newNodes,
            Collection oldNodes)
    {
        int n = newNodes.size();
        if (n == 0) {   //Node with no children.
            node.getInternalDesktop().setPreferredSize(
                    node.getTitleBar().getMinimumSize());
            node.setVisible(true);
            return;
        }

        Iterator children = newNodes.iterator();
        ImageDisplay child, oldChild;
        Object ho, oho, pho, poho;
        Iterator j;
        long id, pid;
        Class klass, pKlass;
        while (children.hasNext()) {
            child = (ImageDisplay) children.next();
            ho = child.getHierarchyObject();
            klass = ho.getClass();
            pho = child.getParentDisplay().getHierarchyObject();
            pKlass = pho.getClass();
            if (ho instanceof DataObject) {
                if (pho instanceof DataObject) {
                    j = oldNodes.iterator();
                    id = ((DataObject) ho).getId();
                    pid = ((DataObject) pho).getId();
                    while (j.hasNext()) {
                        oldChild = (ImageDisplay) j.next();
                        oho = oldChild.getHierarchyObject();
                        if (oldChild.getParentDisplay() != null) {
                            poho =
                               oldChild.getParentDisplay().getHierarchyObject();
                            if (oho instanceof DataObject) {
                                if (((DataObject) oho).getId() == id && 
                                        oho.getClass().equals(klass)) {
                                    if (((DataObject) poho).getId() == pid && 
                                            poho.getClass().equals(pKlass)) {
                                        if (child instanceof ImageNode) {
                                            scaleImage((ImageNode) child,
                                                    (ImageNode) oldChild);
                                        }
                                        child.setBounds(oldChild.getBounds());
                                    }
                                }
                            }
                        }
                    }
                } else { //pho not a dataobject
                    j = oldNodes.iterator();
                    id = ((DataObject) ho).getId();
                    while (j.hasNext()) {
                        oldChild = (ImageDisplay) j.next();
                        oho = oldChild.getHierarchyObject();
                        if (oldChild.getParentDisplay() != null) {
                            if (oho instanceof DataObject) {
                                if (((DataObject) oho).getId() == id && 
                                        oho.getClass().equals(klass)) {
                                    if (child instanceof ImageNode) {
                                        scaleImage((ImageNode) child,
                                                (ImageNode) oldChild);
                                    }
                                    child.setBounds(oldChild.getBounds());
                                }
                            }
                        }
                    }
                }
            }
        }
        if (oldNode == null) {
            Rectangle bounds = node.getContentsBounds();
            Dimension d = bounds.getSize();
            node.getInternalDesktop().setSize(d);
            node.getInternalDesktop().setPreferredSize(d);
        } else {
            Dimension d = oldNode.getRestoreSize();
            if (oldNode.isCollapsed()) {
                Rectangle r = oldNode.getBounds();
                Rectangle bounds = new Rectangle(r.x, r.y, d.width, d.height);
                node.setBounds(bounds);
                node.setPreferredSize(d);
                node.getInternalDesktop().setSize(d);
                node.getInternalDesktop().setPreferredSize(d);
                node.setCollapsed(true);
            } else {
                node.setBounds(oldNode.getBounds());
                node.getInternalDesktop().setSize(d);
                node.getInternalDesktop().setPreferredSize(d);
            }
        }
    }

    /**
     * Returns the object with the largest associated area.
     * This method calculates the <code>area = width x height</code> of each
     * <code>Dimension</code> object and then returns the one having the
     * largest area.
     *  
     * @param a The first dimensions. Mustn't be <code>null</code>.
     * @param b The second dimensions. Mustn't be <code>null</code>.
     * @return The one between <code>a</code> and <code>b</code> with the
     *         largest associated area.
     */
    public static Dimension max(Dimension a, Dimension b)
    {
        int w = a.width;
        int h = a.height;
        if (b.width > w) w = b.width;
        if (b.height > h) h = b.height;
        return new Dimension (w, h);
    }

    //NOTE: Let A be the function that calculates the area of a Dimension,
    //sz the number of children, and r = sqr(sz).
    //The required area for the layout mustn't be less than sz*A(maxDim).
    //B/c: r < [r]+1  =>  sz=r^2 < ([r]+1)^2  
    //Then: sz*A(maxDim) < [([r]+1)^2]*A(maxDim)

    /*
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (!children.hasNext()) //Done, less than n^2 children.
                return;  //Go to finally.
            child = (ImageDisplay) children.next();
            d = child.getPreferredSize();
            child.setBounds(j*maxDim.width, i*maxDim.height, d.width, 
                                d.height);
        }
    }  
     */  

}
