/*
 * org.openmicroscopy.shoola.util.ui.tpane.TinyPaneModel
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006 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.util.ui.tpane;


//Java imports
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;

//Third-party libraries

//Application-internal dependencies

/** 
 * The Model sub-component in the {@link TinyPane}'s MVC triad.
 * This is a presentation Model which holds and manages presentation data
 * in behalf of the {@link TinyPane}, which has pass-through methods that
 * delegate to this class, but manage the change notification process.  So
 * all the other sub-components regard the {@link TinyPane} as the Model.
 * 
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @version 2.2
 * <small>
 * (<b>Internal version:</b> $Revision: 4967 $ $Date: 2007-07-30 13:36:27 +0000 (Mon, 30 Jul 2007) $)
 * </small>
 * @since OME2.2
 */
class TinyPaneModel
{
    
    /** The frame's content pane. */
    private Container       contentPane;
    
    /** 
     * Tells if the frame has to be highlighted.
     * If <code>null</code>, the frame's title bar will display the normal
     * background.  If a color is specified, the title bar will be highlighted
     * using the specified color. 
     */
    private Color           highlight;
   
    /** 
     * Tells if the frame has to be highlighted.
     * If <code>null</code>, the frame's title bar will display the normal
     * background.  If a color is specified, the title bar will be highlighted
     * using the specified color. 
     */
    private Color           highlightHistory;
    
    /** Tells if the frame is expanded or collapsed. */
    private boolean         collapsed;
    
    /** Tells if the frame is expanded or collapsed. */
    private boolean         closed;
    
    /** The size of the frame before the last collapse request. */
    private Dimension       restoreSize;
    
    /** Identifies the title bar the frame is fitted with. */
    private int             titleBarType;
    
    /** All contents are added to this container. */
    private JLayeredPane    desktopPane;
    
    /** Tells if the frame is in single or multi-view mode. */
    private boolean         singleViewMode;
    
    /** Replaces the {@link #desktopPane} when we're in single-view mode. */
    private JLayeredPane    singleViewDesktop;
    
    /** 
     * The bounds of the currently displayed child view relative to the
     * {@link #desktopPane} in which it was originally contained, if we're
     * in single-view mode or <code>null</code> when in multi-view mode.
     * We save this object so that we can restore the original bounds when we
     * add the component back to the internal desktop &#151; that is, after
     * the user selects another child to display in the single-view desktop
     * or when the user switches back to the multi-view mode.
     */
    private Rectangle       childViewBounds;
    
    /** 
     * The type of the currently displayed child view's title bar, if we're in
     * single-view mode.
     * We save this value so that we can restore it when the child view is
     * returned to the original {@link #desktopPane}.
     */
    private int             childViewTitleBarType;
    
    /** 
     * The collapsed state of the the currently displayed child view, if we're
     * in single-view mode.
     * We save this value so that we can restore it when the child view is
     * returned to the original {@link #desktopPane}.
     */
    private boolean         childViewCollapsed;
    
    /** The title of the component. */
    private String          title;
    
    /** The note displayed in the titleBar e.g. # of items. */
    private String			note;
    
    /** 
     * Flag to indicate if the frame is resizable. Default value is
     * <code>false</code>.
     */
    private boolean         resizable;
    
    /**
     * Flag to indicate if listen to the border so that the component can
     * be resized, dragged.
     */
    private boolean         listenToBorder;
    
    /** The frame's icon. */
    private Icon            frameIcon;
    
    /** The collection of buttons to add to the <code>TitleBar</code>.*/
    private List             decoration;

    /**
     * Checks that <code>c</code> is one of the components that were added
     * to the internal desktop.
     * 
     * @param c The component to check.
     * @return <code>true</code> if <code>c</code> is a child to the internal
     *         desktop; <code>false</code> otherwise.
     */
    private boolean isChild(Component c)
    {
        if (c != null) {
            //First check if it's a child to the desktopPane.
            Component[] children = desktopPane.getComponents();
            for (int i = 0; i < children.length; ++i) 
                if (c == children[i]) return true;
            
            //We might be in single-view mode and c was removed from the
            //desktopPane.  Check.
            if (c == getChildView()) return true;   
        }
        return false;
    }
    
    /**
     * Utility method to pick the first child component out of a given
     * container.
     * 
     * @param c The container.
     * @return Returns the first component in <code>c</code> if <code>c</code>
     *         is not <code>null</code> and has children; returns <code>null
     *         </code> if <code>c</code> is <code>null</code> or has no
     *         children.
     */
    private Component getFirstChild(Container c)
    {
        Component child = null;
        if (c != null) {
            Component[] comp = c.getComponents();
            if (0 < comp.length) child = comp[0];
        }
        return child;
    }
    
    /** 
     * Sets the size of the container when we display
     * a child in single view mode.
     * 
     * @param parent The container.
     */
    private void setParentSizeForSingleView(Container parent)
    {
        if (parent instanceof TinyPane) {
            Dimension d = ((TinyPane) parent).getTitleBar().getPreferredSize();
            Dimension dDesktop = singleViewDesktop.getPreferredSize();
            parent.setSize(d.width+dDesktop.width, d.height+dDesktop.height+4);
            parent.validate();
        } else setParentSizeForSingleView(parent.getParent());
    }
    
    /** 
     * Sets the size of the container when we display
     * the children in multi-view mode.
     * 
     * @param parent The container.
     */
    private void setParentSizeForMultiView(Container parent)
    {
    	if (parent == null) return;
        if (parent instanceof TinyPane) {
            parent.setSize(parent.getPreferredSize());
            parent.validate();
        } else setParentSizeForMultiView(parent.getParent());
    }
    
    /**
     * Creates a new instance.
     * After creation the model is not collapsed, the title bar is the 
     * {@link TinyPane#FULL_BAR}, and there's no highlight.
     * However, the internal desktop is not installed yet and you need
     * to call {@link #setSingleViewMode(boolean, TinyPaneUI)} passing
     * <code>false</code> to complete intialization.  At which point, the
     * model will be in multi-view mode and the internal desktop will be 
     * installed.  
     * 
     * @param contentPane	The container hosting the display.
     * @param title         The frame's title.
     * @param restoreSize   The initial frame's size. 
     *                      Mustn't be <code>null</code>.
     */
    TinyPaneModel(Container contentPane, String title, Dimension restoreSize)
    {
        if (contentPane == null) 
            throw new NullPointerException("No content pane.");
        this.contentPane = contentPane;
        this.title = title;
        setRestoreSize(restoreSize);
        desktopPane = new JLayeredPane();
        setTitleBarType(TinyPane.FULL_BAR);
        resizable = true;
        listenToBorder = true;
        note = "";
        decoration = new ArrayList();
    }
    
    /**
     * Restores the original view.
     * 
     * @param uiDelegate The frame's View.  We need it here to decorate the
     *                   desktops.  Mustn't be <code>null</code>.
     */
    void restoreDisplay(TinyPaneUI uiDelegate)
    {
        contentPane.removeAll();
        if (singleViewMode) {
            if (singleViewDesktop != null)
                contentPane.add(
                    uiDelegate.decorateDesktopPane(singleViewDesktop),
                    BorderLayout.CENTER);
        } else 
            contentPane.add(uiDelegate.decorateDesktopPane(desktopPane),
                    BorderLayout.CENTER);
        contentPane.validate();
        contentPane.repaint();
    }

    /**
     * Tells if we're in single or multi-view mode. 
     * 
     * @return See above.
     */
    boolean isSingleViewMode() { return singleViewMode; }
    
    /**
     * Sets the frame's view mode.
     *  
     * @param singleViewMode <code>true</code> to switch to the single-view
     *                       mode, <code>false</code> to return to the default
     *                       multi-view mode.
     * @param uiDelegate The frame's View.  We need it here to decorate the
     *                   desktops.  Mustn't be <code>null</code>.
     */
    void setSingleViewMode(boolean singleViewMode, TinyPaneUI uiDelegate)
    {
        if (uiDelegate == null) 
            throw new NullPointerException("No UI delegate.");
        this.singleViewMode = singleViewMode;
        if (singleViewMode) {  //Create the single-view tmp desktop.
            singleViewDesktop = new JLayeredPane();
            contentPane.removeAll();
            contentPane.add(
                    uiDelegate.decorateDesktopPane(singleViewDesktop),
                    BorderLayout.CENTER);
            
            //Make the first child (if any) of the internal desktop the 
            //current child view.
            setChildView(getFirstChild(desktopPane));
        } else {  //Normal multi-view mode, restore the internal desktop.
            contentPane.removeAll();
            contentPane.add(
                    uiDelegate.decorateDesktopPane(desktopPane),
                    BorderLayout.CENTER);
            
            //Return the current child view to the internal desktop.
            setChildView(null);
            singleViewDesktop = null;
        }
    }
    
    /**
     * Sets the given component in the single-view desktop.
     * This method does nothing if the frame is not in single-view mode.
     * Otherwise, the passed component is assumed to be a child of the 
     * internal desktop or is <code>null</code> if the single-view desktop
     * has to be cleared.
     * 
     * @param child The child component. 
     */
    void setChildView(Component child)
    {
        Component oldChild = getChildView();
        if (oldChild != null) {  //Return it to the internal desktop.
            singleViewDesktop.remove(oldChild);
            desktopPane.add(oldChild);
            oldChild.setBounds(childViewBounds);
            if (oldChild instanceof TinyPane) {
                TinyPane tf = (TinyPane) oldChild;
                tf.setTitleBarType(childViewTitleBarType);
                tf.setCollapsed(childViewCollapsed);
                if (child == null) {
                    setParentSizeForMultiView(tf.getParent());
                    //contentPane.validate();
                    //contentPane.repaint(); 
                }
            }
        }
        if (isChild(child)) {  //We've got a new child to display.
            desktopPane.remove(child);
            singleViewDesktop.add(child);
            childViewBounds = child.getBounds();
            //was new Dimension(childViewBounds.width, childViewBounds.height)
            singleViewDesktop.setPreferredSize(child.getPreferredSize());
            if (child instanceof TinyPane) {
                TinyPane tf = (TinyPane) child;
                childViewTitleBarType = tf.getTitleBarType();
                childViewCollapsed = tf.isCollapsed();
                tf.setTitleBarType(TinyPane.HEADER_BAR);
                tf.setCollapsed(false);
                setParentSizeForSingleView(tf.getParent());
            }
            //Need to set the size of the child after setting the title bar.
            Dimension d = child.getPreferredSize();
            child.setBounds(2, 2, d.width, d.height);
            //Do it this way otherwise the methods are called at init time.
        }
    }
    
    /**
     * Returns the component that is showing in the single-view desktop when
     * in single-view mode.
     * The returned value will always be <code>null</code> if the frame is
     * not in single-view mode.  However, it can also be <code>null</code>
     * if the frame is in single-view mode but no components were added to
     * the internal desktop.
     * 
     * @return The child view, that is the component that is showing in the
     *         single-view desktop when in single-view mode.
     */
    Component getChildView() { return getFirstChild(singleViewDesktop); }
    
    /**
     * Returns the title of this component.
     * 
     * @return See below.
     */
    String getTitle() { return title; }
    
    /**
     * Sets the title of this component.
     * 
     * @param title The title to set.
     */
    void setTitle(String title) { this.title = title; }
    
    /**
     * Returns the note added to the <code>TitleBar</code>.
     * 
     * @return See above.
     */
    String getNote() { return note; }
    
    /**
     * Sets the note added to the <code>TitleBar</code>.
     * 
     * @param note The note to set.
     */
    void setNote(String note) { this.note = note; }
    
    /** 
     * Returns the title bar type.
     * 
     * @return One of the constants defined by {@link TinyPane}. 
     */
    int getTitleBarType() { return titleBarType; }
    
    /**
     * Sets the title bar type.
     * Does nothing if <code>type</code> is not a valid one.
     * 
     * @param type One of the constants defined by {@link TinyPane}.
     * @return <code>true</code> if <code>type</code> is a valid flag and so
     *         it has been set; <code>false</code> if <code>type</code> is
     *         not a valid flag and so it has been ignored.
     */
    boolean setTitleBarType(int type)
    {
        switch (type) {
            case TinyPane.FULL_BAR: 
            case TinyPane.STATIC_BAR:
            case TinyPane.SMALL_BAR:
            case TinyPane.HEADER_BAR: 
            case TinyPane.NO_BAR:
            case TinyPane.SMALL_TITLE_BAR:
                titleBarType = type;
                return true;
        }
        return false;
    }
    
    /**
     * Returns the bounds of the area, within the internal desktop, which is
     * occupied by the contained components.
     * 
     * @return See above.
     */
    Rectangle getContentsBounds()
    {
        Component[] comp = desktopPane.getComponents();
        int x = 0, y = 0, //Top left corner coordinates.
            br_x = 0, br_y = 0;  //Bottom right corner coordinates.
        Rectangle bounds;
        for (int i = 0; i < comp.length; ++i) {
            bounds = comp[i].getBounds();
            x = Math.min(x, bounds.x);
            y = Math.min(y, bounds.y);
            br_x = Math.max(br_x, bounds.x+bounds.width);
            br_y = Math.max(br_y, bounds.y+bounds.height);
        }
        return new Rectangle(x, y, br_x-x, br_y-y);
    }
    
    /**
     * Returns the restore size.
     * 
     * @return See above.
     */
    Dimension getRestoreSize() { return restoreSize; }
    
    /** 
     * Sets the restore size.
     * 
     * @param rs The restore size.  Mustn't be <code>null</code>. 
     */
    void setRestoreSize(Dimension rs) 
    { 
        if (rs == null) throw new NullPointerException("No restore size.");
        this.restoreSize = rs;
    }
    
    /**
     * Returns the frame's internal desktop.
     * 
     * @return See above.
     */
    JComponent getDesktopPane() { return desktopPane; }
    
    /**
     * Returns the highlight color. 
     * 
     * @return See aoove.
     */
    Color getHighlight() { return highlight; }
    
    /**
     * Returns the highlight color. 
     * 
     * @return See aoove.
     */
	Color getPreviousHighlight() { return highlightHistory; }
	
    /**
     * Sets the highlight color. 
     * 
     * @param highlight The color to set.
     */
    void setHighlight(Color highlight)
    { 
    	highlightHistory = getHighlight();
    	this.highlight = highlight; 
    }
    
    /**
     * Returns the collapsed state.
     * 
     * @return See above.
     */
    boolean isCollapsed() { return collapsed; }
    
    /**
     * Sets the collapsed state. 
     * 
     * @param collapsed The state to set.
     */
    void setCollapsed(boolean collapsed) { this.collapsed = collapsed; }
    
    /**
     * Sets the resizable state.
     * 
     * @param resizable The state to set.
     */
    void setResizable(boolean resizable) { this.resizable = resizable; }
    
    /**
     * Returns the resizable state.
     * 
     * @return See above.
     */
    boolean isResizable() { return resizable; }
    
    /**
     * Sets the frame's icon.
     * 
     * @param frameIcon The icon to set.
     */
    void setFrameIcon(Icon frameIcon) { this.frameIcon = frameIcon; }
    
    /**
     * Returns the frame's icon.
     * 
     * @return See above.
     */
    Icon getFrameIcon() { return frameIcon; }
    
    /**
     * Installs a border listener if <code>true</code>, removes
     * it if <code>false</code>.
     * 
     * @param listenToBorder The border listener flag.
     */
    void setListenToBorder(boolean listenToBorder)
    { 
        this.listenToBorder = listenToBorder;
    }
    
    /**
     * Returns the border listener flag.
     * 
     * @return See below.
     */
    boolean isListenToBorder() { return listenToBorder; }

    /**
     * Returns the frame's container.
     * 
     * @return See above.
     */
    Container getContentPane() { return contentPane; }
    
    /**
     * Returns the collection of buttons to add to the <code>TitleBar</code>.
     * 
     * @return See above.
     */
    List getDecoration() { return decoration; }
    
    /**
     * Sets the collection of buttons to add to the <code>TitleBar</code>.
     * 
     * @param decoration The collection to set.
     */
    void setDecoration(List decoration) { this.decoration = decoration; }

	/**
     * Returns the closed state.
     * 
     * @return See above.
     */
    boolean isClosed() { return closed; }
    
    /**
     * Sets the closed state. 
     * 
     * @param closed The state to set.
     */
    void setClosed(boolean closed) { this.closed = closed; }
    
}
