/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.tool;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import javax.accessibility.AccessibleContext;
import javax.swing.JDesktopPane;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLayeredPane;
import javax.swing.JRootPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.plaf.basic.BasicInternalFrameUI;
import com.sun.javatest.util.PrefixMap;

//-------------------------------------------------------------------------

    /**
     * A container that presents the current desktop tools in a JDesktopPane.
     */
class MDIDeskView extends DeskView {
    MDIDeskView(Desktop desktop) {
	this(desktop, getDefaultBounds());
    }

    MDIDeskView(DeskView other) {
	this(other.getDesktop(), other.getBounds());
	//System.err.println("MDI: create from " + other);
	//System.err.println("MDI: create " + other.getTools().length + " tools");
	
	Tool[] tools = other.getTools();
	for (int i = 0; i < tools.length; i++) 
	    addTool(tools[i]);

	setVisible(other.isVisible());
    }

    private MDIDeskView(Desktop desktop, Rectangle bounds) {
	super(desktop);
	initMainFrame(bounds);
	uif.setDialogParent(mainFrame);
	JDialog.setDefaultLookAndFeelDecorated(true);
    }

    public void dispose() {
	mainFrame.setVisible(false);
	mainFrame.dispose();
	super.dispose();
    }

    public boolean isVisible() {
	return mainFrame.isVisible();
    }

    public void setVisible(boolean v) {
	if (v == mainFrame.isVisible())
	    return;

	mainFrame.setVisible(v);

	if (v) {
	    Window[] ww = mainFrame.getOwnedWindows();
	    if (ww != null) {
		for (int i = 0; i < ww.length; i++)
		    ww[i].toFront();
	    }
	}
    }

    public boolean isEmpty() {
	return (internalFrames.size() == 0);
    }

    public Tool[] getTools() {
	Tool[] tools = new Tool[internalFrames.size()];
	return (Tool[]) internalFrames.keySet().toArray(tools);
    }

    public void addTool(Tool t) {
	DeskView view = t.getDeskView();
	if (view == this)
	    return;

	// save info about dialogs before we remove tool from other view
	ToolDialog[] tds = t.getToolDialogs();
	boolean[] vis = new boolean[tds.length];
	for (int i = 0; i < tds.length; i++)
	    vis[i] = tds[i].isVisible();

	// remove tool from other view (if any)
	if (view != null)
	    view.removeTool(t);
	    
	//System.err.println("MDI.addTool " + t);
	// create resizeable, closable, maximizable, iconifiable frame
	JInternalFrame f = new JInternalFrame(t.getTitle(), true, true, true, true);
	f.setName(t.getShortTitle());

	f.putClientProperty(this, t);
	f.setJMenuBar(t.getMenuBar());
	f.setContentPane(t);
	f.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
	f.pack();
	f.addInternalFrameListener(listener);
	f.getAccessibleContext().setAccessibleDescription(t.getToolTipText());

	// copy accessible info to rootpane
	JRootPane root = f.getRootPane();
	root.setName("root");
	AccessibleContext f_ac = f.getAccessibleContext();
	AccessibleContext r_ac = root.getAccessibleContext();
	r_ac.setAccessibleName(f_ac.getAccessibleName());
	r_ac.setAccessibleDescription(f_ac.getAccessibleDescription());

	// center new tool on desktop
	// (doesn't work if desktop not yet shown/sized, sigh)
	// (-perhaps use preferredSize?)
	Dimension dtSize = desktopPane.getSize();
	if (dtSize.width == 0 || dtSize.height == 0)
	    dtSize = desktopPane.getPreferredSize();
	Dimension size = f.getSize();
	f.setLocation(Math.max(0, dtSize.width/2 - size.width/2), 
		      Math.max(0, dtSize.height/2 - size.height/2) );
	f.setVisible(true);
	internalFrames.put(t, f);

	//System.err.println("MDI.addTool--desktopPane: " + desktopPane);
	//System.err.println("MDI.addTool--internal frame: " + f);
	
	desktopPane.add(f);
	f.toFront();
	t.addObserver(listener);
	
	t.setDeskView(this);

	// update tool dialogs
	for (int i = 0; i < tds.length; i++) 
	    tds[i].initDialog(this, vis[i]);
    }

    public void removeTool(Tool t) {
	//System.err.println("MDI: remove " + t);
	JInternalFrame f = (JInternalFrame) (internalFrames.get(t));
	if (f != null) {
	    desktopPane.remove(f);
	    internalFrames.remove(t);
	}
	t.removeObserver(listener);
	t.setDeskView(null);

	// ensure there is a valid keyboard focus
	KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
	Component fo = fm.getPermanentFocusOwner();
	if (fo == null || !fo.isShowing())
	    desktopPane.requestFocusInWindow();
    }

    public Tool getSelectedTool() {
	JInternalFrame f = desktopPane.getSelectedFrame(); // NOT VALID FOR DIALOGS!
	if (f == null)
	    return null;

	Tool t = (Tool) (f.getClientProperty(this));
	return t;
    }

    public void setSelectedTool(Tool t) {
	JInternalFrame f = (JInternalFrame) (internalFrames.get(t));
	if (f != null) {
	    try {
		f.setIcon(false);
		f.setSelected(true);
		desktopPane.moveToFront(f);
	    }
	    catch (Exception e) { // for java.beans.PropertyVetoException
	    }
	}
    }

    public int getStyle() {
	return Desktop.MDI_STYLE;
    }

    public JFrame[] getFrames() {
	return new JFrame[] { mainFrame };
    }

    public Rectangle getBounds() {
	return mainFrame.getBounds();
    }  

    public boolean isToolOwnerForDialog(Tool tool, Container dialog) {
	if (dialog == null)
	    return false;
	
	if (useInternalDialogs) 
	    return (dialog.getParent() == desktopPane);
	else
	    return (dialog.getParent() == mainFrame);
    }

    public Container createDialog(Tool tool, String uiKey, String title, 
				  JMenuBar menuBar, Container body,
				  Rectangle bounds) {
	UIFactory uif = tool.uif;

	if (useInternalDialogs) {
	// create resizeable, closable, non-maximizable, non-iconifiable frame
	    JInternalFrame f = new JInternalFrame(title, true, true, false, false);
	    f.putClientProperty(this, tool);

	    f.setName(uiKey);
	    uif.setAccessibleInfo(f, uiKey);
	    // copy accessible info to rootpane
	    JRootPane root = f.getRootPane();
	    root.setName(uiKey + ".root");
	    AccessibleContext f_ac = f.getAccessibleContext();
	    AccessibleContext r_ac = root.getAccessibleContext();
	    r_ac.setAccessibleName(f_ac.getAccessibleName());
	    r_ac.setAccessibleDescription(f_ac.getAccessibleDescription());

	    if (menuBar != null)
		f.setJMenuBar(menuBar);

	    f.setContentPane(body);
	    
	    if (bounds == null) {
		f.pack();
		// for some reason the first call of pack seems to yield small results
		// so we need to pack it again to get the real results.  Additional calls
		// seem to have no effect, so after 2 calls we seem to have stable results.
		f.pack();
		Dimension size = f.getSize();
		JInternalFrame tf = (JInternalFrame) (internalFrames.get(tool));
		Rectangle tb = tf.getBounds();
		f.setLocation(Math.max(0, tb.x + (tb.width - size.width) / 2), 
			      Math.max(0, tb.y + (tb.height - size.height ) / 2) );
	    }
	    else 
		f.setBounds(bounds);

	    // put dialogs above tools on the desktop
	    JLayeredPane.putLayer(f, JLayeredPane.DEFAULT_LAYER.intValue() + 1);

	    desktopPane.add(f);

	    return f;
	}
	else {
	    JDialog d = uif.createDialog(uiKey, mainFrame, title, body);
	    if (menuBar != null)
		d.setJMenuBar(menuBar);
	    
	    if (bounds == null) {
		d.pack();
		d.setLocationRelativeTo(mainFrame);
	    }
	    else 
		d.setBounds(bounds);

	    return d;
	}
    }
  
    protected void saveDesktop(Map m) {
	saveBounds(mainFrame, new PrefixMap(m, "dt"));
	saveTools(m);
    }

    protected void restoreDesktop(Map m) {
	restoreBounds(mainFrame, new PrefixMap(m, "dt"));
	restoreTools(m);
    }

    protected void saveTool(Map m, Tool t) {
	super.saveTool(m, t);
	JInternalFrame f = (JInternalFrame) (internalFrames.get(t));
	saveBounds(f, new PrefixMap(m, "dt"));
    }

    protected Tool restoreTool(Map m, String name) throws Fault, ToolManager.Fault
    {
	Tool t = super.restoreTool(m, name);
	JInternalFrame f = (JInternalFrame) (internalFrames.get(t));
	restoreBounds(f, new PrefixMap(m, "dt"));
	f.setVisible(true);
	return t;
    }

    // internal routines

    private void initMainFrame(Rectangle bounds) {
	//System.err.println("MDI: create");
	mainFrame = createFrame(listener, "mdi.main");
	desktopPane = new JDesktopPane();
	desktopPane.setName("mdi.desk");
	uif.setAccessibleInfo(desktopPane, "mdi.desk");
	desktopPane.setPreferredSize(new Dimension(bounds.width, bounds.height));
	mainFrame.setContentPane(desktopPane);
	if (bounds == null) {
	    mainFrame.pack();
            // a null param is supposed to center the frame, but JDK doesn't seem to 
            // a modern definition of "center" so it may not work as expected on a
            // Xinerama X11 screen.
            // Old implemenation is the commented line below.
	    //mainFrame.setLocationRelativeTo(null);
	    Dimension size = mainFrame.getSize();
	    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
	    mainFrame.setLocation(ge.getCenterPoint().x - size.width/2,
				  ge.getCenterPoint().y - size.height/2);
	}
	else
	    mainFrame.setBounds(bounds);

	mainFrame.addWindowListener(new WindowAdapter() {
	    public void windowClosing(WindowEvent e) {
		getDesktop().checkToolsAndExitIfOK(mainFrame);
	    }
	});
    }

    private void doCascade() {
	Point p = new Point(0, 0);
	JInternalFrame[] frames = getVisibleInternalFrames();
	for (int i = 0; i < frames.length; i++) {
	    JInternalFrame f = frames[i];
	    f.setLocation(p);
	    desktopPane.moveToFront(f); // would be nice to preserve layering if possible
	    if (i == frames.length - 1) {
		try {
		    f.setSelected(true);  
		    // would be nice to make sure the previously 
		    // selected window stays on top
		}
		catch (Exception e) { // for java.beans.PropertyVetoException
		}
	    }
	    BasicInternalFrameUI ui = (BasicInternalFrameUI) (f.getUI());
	    int offset = ui.getNorthPane().getHeight();
	    p.x += offset;
	    p.y += offset;
	}
    }

    private void doTile() {
	JInternalFrame[] frames = getVisibleInternalFrames();
	int n = frames.length;
	int cols = (int) Math.sqrt(n);
	int rows = (n + cols - 1) / cols;
	Dimension dSize = desktopPane.getSize();
	Dimension tSize = new Dimension(dSize.width / cols, dSize.height / rows);
	for (int i = 0; i < frames.length; i++) {
	    JInternalFrame f = frames[i];
	    int c = (i % cols);
	    int r = (i / cols);
	    f.setBounds(c * tSize.width, r * tSize.height, tSize.width, tSize.height);
	}
    }

    private JInternalFrame[] getVisibleInternalFrames() {
	Vector v = new Vector();
	for (int i = 0; i < desktopPane.getComponentCount(); i++) {
	    Component c = desktopPane.getComponent(i);
	    if (c instanceof JInternalFrame && c.isVisible())
		v.add(c);
	}
	JInternalFrame[] frames = new JInternalFrame[v.size()];
	v.copyInto(frames);
	return frames;
    }

    private JFrame mainFrame;
    private HashMap internalFrames = new HashMap(17);
    private JDesktopPane desktopPane;
    private Listener listener = new Listener();

    private static final String TILE = "tile";
    private static final String CASCADE = "cascade";

    private static final boolean useInternalDialogs = true;

    private class Listener 
	implements ActionListener, AncestorListener, 
		   InternalFrameListener, MenuListener, 
		   Tool.Observer 
    {
	// --------- ActionListener  ---------
	
	public void actionPerformed(ActionEvent e) {
	    String cmd = e.getActionCommand();
	    if (cmd.equals(CASCADE))
		doCascade();
	    else if (cmd.equals(TILE))
		doTile();
	    else {
		JMenuItem mi = (JMenuItem) (e.getSource());
		Object o = mi.getClientProperty(this);
		if (o instanceof Window)
		    ((Window) o).toFront();
		if (o instanceof JInternalFrame)
		    ((JInternalFrame) o).toFront();
		else if (o instanceof Tool)
		    setSelectedTool((Tool) o);
	    }
	}
	
	// --------- AncestorListener ---------
	
	public void ancestorAdded(AncestorEvent event) {
	    Tool[] tools = getTools();
	    for (int i = 0; i < tools.length; i++) {
		Tool t = tools[i];
		t.addObserver(listener);
	    }
	}
	
	public void ancestorMoved(AncestorEvent event) { }
	
	public void ancestorRemoved(AncestorEvent event) {
	    Tool[] tools = getTools();
	    for (int i = 0; i < tools.length; i++) {
		Tool t = tools[i];
		t.removeObserver(listener);
	    }
	}
	
	// --------- InternalFrameListener ---------
	
	public void internalFrameClosed(InternalFrameEvent e)  {  
	    JInternalFrame f = (JInternalFrame) (e.getSource());
	    if (f.getContentPane() instanceof Tool) {
		Tool t = (Tool) (f.getContentPane());
		removeTool(t);
		t.removeObserver(listener);
		t.dispose();
		
		f.getJMenuBar().removeAll();
		f.setContentPane(new Container());
	    }
	}
	
	public void internalFrameClosing(InternalFrameEvent e)  { 
	    JInternalFrame f = (JInternalFrame) (e.getSource());
	    Tool t = (Tool) (f.getContentPane());
	    if (getDesktop().isOKToClose(t, mainFrame))
		f.dispose();
	}
	
	public void internalFrameActivated(InternalFrameEvent e) { }

	public void internalFrameDeactivated(InternalFrameEvent e) { }
	
	public void internalFrameDeiconified(InternalFrameEvent e)  { }
	
	public void internalFrameIconified(InternalFrameEvent e)  { }
	
	public void internalFrameOpened(InternalFrameEvent e)  { }
	
	// --------- MenuListener ---------

	public void menuSelected(MenuEvent e) {
	    Tool[] tools = getTools();

	    JMenu m = (JMenu) (e.getSource());
	    m.removeAll();
	    JMenuItem tmi = uif.createMenuItem("dt.windows", TILE, this);
	    tmi.setEnabled(tools.length > 0);
	    m.add(tmi);
	    JMenuItem cmi = uif.createMenuItem("dt.windows", CASCADE, this);
	    cmi.setEnabled(tools.length > 0);
	    m.add(cmi);

            /*
	    JMenu winOpenMenu = getWindowOpenMenu();
	    if (winOpenMenu.getItemCount() > 0) {
		m.addSeparator();
		m.add(getWindowOpenMenu());
	    }
            */

	    if (tools.length > 0)
		m.addSeparator();

	    int n = 0;

	    // add entries for all current tools
	    for (int i = 0; i < tools.length; i++) {
		Tool tool = tools[i];
		addMenuItem(m, n++, tool.getTitle(), tool);
	    }

	    // add entries for all internal dialogs,
	    // which are internal frames not containing tools
	    JInternalFrame[] frames = desktopPane.getAllFrames();
	    for (int i = 0; i < frames.length; i++) {
		JInternalFrame f = frames[i];
		if (f.isVisible() && !(f.getContentPane() instanceof Tool) )
		    addMenuItem(m, n++, f.getTitle(), f);
	    }
	    
	    // add entries for any external dialogs
	    Window[] ownedWindows = mainFrame.getOwnedWindows();
	    for (int i = 0; i < ownedWindows.length; i++) {
		Window w = ownedWindows[i];
		if (w instanceof JDialog && w.isVisible()) 
		    addMenuItem(m, n++, ((JDialog) w).getTitle(), w);
	    }
	}

	private void addMenuItem(JMenu m, int n, String s, Object o) {
	    JMenuItem mi = new JMenuItem(uif.getI18NString("dt.windows.toolX.mit", 
						 new Object[] { new Integer(n), s }));
	    if (n < 10) 
		mi.setMnemonic(Character.forDigit(n, 10));
	    mi.addActionListener(this);
	    mi.putClientProperty(this, o);
	    m.add(mi);
	}

	public void menuDeselected(MenuEvent e) {
	}

	public void menuCanceled(MenuEvent e) {
	}
	
	// --------- Tool.Observer ---------
	
	public void shortTitleChanged(Tool src, String newValue) {
	}
	
	public void titleChanged(Tool src, String newValue) {
	    JInternalFrame f = (JInternalFrame) (internalFrames.get(src));
	    f.setTitle(newValue);
	    //System.err.println("Tool title changed: " + newValue);
	}

	public void toolDisposed(Tool src) { }
	
    }
} 
