/* BEGIN_HEADER                                              Java TreeView
 *
 * $Author: alokito $
 * $RCSfile: LinkedPanel.java,v $
 * $Revision: 1.33 $
 * $Date: 2008-06-11 01:58:57 $
 * $Name:  $
 *
 * This file is part of Java TreeView
 * Copyright (C) 2001-2014 Alok Saldanha, All Rights Reserved. Modifications Copyright (C) Lawrence Berkeley Lab.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * END_HEADER
 */
package edu.stanford.genetics.treeview;

import java.awt.*;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import edu.stanford.genetics.treeview.core.PluginManager;

public class LinkedPanel extends JTabbedPane implements MainPanel {
	/**
	 * This class enables you to put a MainPanel in a separate window.
	 * the separate window is not a ViewFrame and should be thought of as
	 * subordinate to the ViewFrame that holds the LinkedPanel, although
	 * there's no way to enforce that from java without always having the
	 * subwindow on top.
	 */
	private class MainPanelFrame extends JFrame {
		MainPanel mainPanel;
		/**
		 * @param mp main panel to display
		 */
		public MainPanelFrame(MainPanel mp) {
			super();
			mainPanel = mp;
			final WindowListener listener = new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
//					dockMainPanelDialog(MainPanelFrame.this);
					removeDialog(MainPanelFrame.this);
				}
				public void windowClosed(WindowEvent e) {}
				public void windowIconified(WindowEvent e) {}
				public void windowDeiconified(WindowEvent e) {}
			};
			addWindowListener(listener);

			JButton dockButton = new JButton("Dock");
			dockButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					removeWindowListener(listener);
					dockMainPanelDialog(MainPanelFrame.this);
				}
			});

			JButton closeButton = new JButton("Close");
			closeButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					removeWindowListener(listener);
					removeDialog(MainPanelFrame.this);
				}
			});
			
			JPanel buttonPanel = new JPanel();
			buttonPanel.add(dockButton);
			buttonPanel.add(closeButton);
			
			getContentPane().setLayout(new BorderLayout());
			getContentPane().add(buttonPanel, BorderLayout.SOUTH);
			getContentPane().add((Component) mainPanel,BorderLayout.CENTER);
			setTitle(mp.getName() + ": " + viewFrame.getDataModel().getSource());
		}

		/**
		 * @return main panel displayed by dialog
		 */
		public MainPanel getMainPanel() {
			return mainPanel;
		}
	}
	public LinkedPanel(ViewFrame viewFrame) {
		super();
		setName("LinkedPanel");
		setViewFrame(viewFrame);
		addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				ConfigNode viewsNode = getConfigNode();
				if (viewsNode != null) {
					viewsNode.setAttribute("selected", getSelectedIndex(), 0);
				}
			}
		});
	}

	ViewFrame viewFrame;
	/** Setter for viewFrame */
	public void setViewFrame(ViewFrame viewFrame) {
		this.viewFrame = viewFrame;
	}
	/** Getter for viewFrame */
	public ViewFrame getViewFrame() {
		return viewFrame;
	}

	/**
	 *  This syncronizes the sub compnents with their persistent storage.
	 */
	public void syncConfig() {
		int n = getComponentCount();
		for (int i = 0; i < n; i++) {
			MainPanel modelView = (MainPanel) getComponentAt(i);
			modelView.syncConfig();
		}
	}

	ConfigNode configNode = null;
	/** Setter for configNode */
	public void setConfigNode(ConfigNode configNode) {
		this.configNode = configNode;
		restoreState();
	}
	public void restoreState() {
		removeAll();
		// awlright, setup views...
		ConfigNode viewsNode = getConfigNode();
		if (viewsNode != null) {
			ConfigNode [] viewNodes = viewsNode.fetch("View");
			PluginFactory [] plugins = PluginManager.getPluginManager().getPluginFactories();
			for (int i = 0; i <viewNodes.length; i++) {
				ConfigNode thisNode = viewNodes[i];
				String thisType = thisNode.getAttribute("type", null);
				
				// check to see if covered by plugin...
				for (int j =0; j < plugins.length; j++) {
					if (thisType.equals(plugins[j].getPluginName())) {
						restorePlugin(thisNode, plugins[j]);
						thisType = null; // forestall further adds
						break;
					}
				}
				
				if (thisType == null) {
					// do nothing...
				} else {
					LogBuffer.println(viewFrame.getDataModel().getSource() + 
							": encountered unknown View of type " + thisType);
				}
			}
		}
		if (getComponentCount() == 0) {
			PluginFactory foo = PluginManager.getPluginManager().getPluginFactory(0);
			PluginFactory [] plugins = PluginManager.getPluginManager().getPluginFactories();
			for (int i = 0 ; i < plugins.length; i++)
				if ("Dendrogram".equals(plugins[i].getPluginName()))
					foo = plugins[i];
			if (foo != null) {
				addPlugin(foo);
			} else {
				JOptionPane.showMessageDialog(this, "No plugins loaded");
			}
		}
		if (getComponentCount() > 0) {
			int selected = viewsNode.getAttribute("selected", 0);
			setSelectedIndex(selected);
		}
	}
	/**
	* this method gets the config node on which this component is based, or null.
	*/
	public ConfigNode getConfigNode() {
		return configNode;
	}

	/**
	 *  Add items related to settings
	 *
	 * @param  menu  A menu to add items to.
	 */
	 public void populateSettingsMenu(TreeviewMenuBarI menu) {
		 MainPanel panel = (MainPanel) getSelectedComponent();
		 if (panel != null) {
			 panel.populateSettingsMenu(menu);
		 }
	 }


	/**
	 *  Add items which do some kind of analysis
	 *
	 * @param  menu  A menu to add items to.
	 */
	public void populateAnalysisMenu(TreeviewMenuBarI menu) {
		MainPanel panel = (MainPanel) getSelectedComponent();
		if (panel != null) {
			panel.populateAnalysisMenu(menu);
		}
		if (menu.getItemCount() > 0) menu.addSeparator();
		PluginFactory [] plugins = PluginManager.getPluginManager().getPluginFactories();
		for (int i = 0; i < plugins.length; i++) {
			final PluginFactory thisFactory = plugins[i];
			menu.addMenuItem(thisFactory.getPluginName(), new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					// MainPanel plugin = 
					addPlugin(thisFactory);
				}
			});
		}
		if (plugins.length == 0) {			
			menu.addMenuItem("No Plugins Found", new ActionListener() {
				public void actionPerformed(ActionEvent e) {
				}			
			});
		}
		if (menu.getItemCount() > 0) menu.addSeparator();
		menu.addSeparator();
		
		menu.addMenuItem("Remove Current", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				removeCurrent();
			}
		});
		menu.setMnemonic(KeyEvent.VK_R);
		
		menu.addMenuItem("Detach Current", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				detachCurrent();
			}
		});
		menu.setMnemonic(KeyEvent.VK_D);
		
		/*
		MenuItem menuItem2 = getModel().getStatMenuItem();
		menu.add(menuItem2);
		*/
	}


	/**
	 *  Add items which allow for export, if any.
	 *
	 * @param  menu  A menu to add items to.
	 */
	 public void populateExportMenu(TreeviewMenuBarI menu) {
		 MainPanel panel = (MainPanel) getSelectedComponent();
		 if (panel != null) {
			 if (menu.getItemCount() > 0) menu.addSeparator();					
			 panel.populateExportMenu(menu);
		 }
	 }


	/**
	 *  ensure a particular index is visible. Used by Find.
	 *
	 * @param  index  Index of gene in cdt to make visible
	 */
	public void scrollToGene(int index) {
		int n = getComponentCount();
		for (int i = 0; i < n; i++) {
			MainPanel modelView = (MainPanel) getComponentAt(i);
			modelView.scrollToGene(index);
		}
	}
	public void scrollToArray(int index) {
		int n = getComponentCount();
		for (int i = 0; i < n; i++) {
			MainPanel modelView = (MainPanel) getComponentAt(i);
			modelView.scrollToArray(index);
		}
	}
	/**
	 * used to add existing instances, with state stored in confignode
	 * 
	 * @param thisNode
	 * @param f
	 * @return
	 */
	public MainPanel restorePlugin(ConfigNode thisNode, PluginFactory f) {
		MainPanel plugin =  f.restorePlugin(thisNode, getViewFrame());
		if (plugin != null) {
			switch(plugin.getConfigNode().getAttribute("dock", -1)) {
				case 0:
					addDialog(plugin);
					break;
				case 1:
					addTab(plugin);
					setSelectedComponent((Component) plugin);
					break;
				case -1:
					addTab(plugin);
					setSelectedComponent((Component) plugin);
				 	break;
			}
		}
		return plugin;
	}
	/**
	 * used to add new instances of the plugin
	 */
	public MainPanel addPlugin(PluginFactory f) {
		ConfigNode thisNode = getConfigNode().create("View");
		thisNode.setAttribute("type", f.getPluginName(), null);
		f.configurePlugin(thisNode, getViewFrame());
		return restorePlugin(thisNode, f);
	}

	public void addTab(MainPanel mp) {
		addTab(mp.getName(), mp.getIcon(), (Component) mp, "What's this button do?");
		mp.getConfigNode().setAttribute("dock", 1, -1);
	}

	/**
	 * used to hold list of open mp dialogs
	 */
	Vector mpdialogs = new Vector();
	public void addDialog(MainPanel mp) {
		final MainPanelFrame nmp = new MainPanelFrame(mp);
		mpdialogs.add(nmp);
		Rectangle r = viewFrame.getBounds();
		r.height -=10;
		r.width -= 10;
		r.x += 10;
		r.y += 10;
		nmp.setBounds(r);
		nmp.setVisible(true);
		mp.getConfigNode().setAttribute("dock", 0, -1);
	}
	/**
	 * removed ConfigNode of mainpanel as well as dialog window
	 * 
	 * @param mp mainpanel to remove
	 */
	public void removeDialog(MainPanel mp) {
		Enumeration e = mpdialogs.elements();
		while (e.hasMoreElements()) {
			MainPanelFrame mpd = (MainPanelFrame) e.nextElement();
			if (mpd.getMainPanel() == mp)
				removeDialog(mpd);
		}
	}
	/**
	 * removed ConfigNode of mainpanel as well as dialog window
	 * 
	 * @param mp mainpanel to remove
	 */
	public void removeDialog(MainPanelFrame mpd) {
		mpdialogs.remove(mpd);
		ConfigNode viewsNode = getConfigNode();
		viewsNode.remove(mpd.getMainPanel().getConfigNode());
		mpd.dispose();
	}
	public void dockMainPanelDialog(MainPanelFrame mpd) {
		MainPanel mp = mpd.getMainPanel();
		mpdialogs.remove(mpd);
		mpd.setVisible(false);
		addTab(mp);
		mpd.dispose();
		mp.getConfigNode().setAttribute("dock", 1, -1);
	}
	public void detachCurrent() {
		Component current =  getSelectedComponent();
		if (current != null) {
			MainPanel mp = (MainPanel) current;
			remove(current);
			addDialog(mp);
			mp.getConfigNode().setAttribute("dock", 0, -1);
		}
	}
	public void removeCurrent() {
		Component current =  getSelectedComponent();
		if (current != null) {
			MainPanel cPanel = (MainPanel) current;
			cPanel.syncConfig();
			ConfigNode viewsNode = getConfigNode();
			viewsNode.remove(cPanel.getConfigNode());
			remove(current);
		}
	}
	


	/* (non-Javadoc)
	 * @see edu.stanford.genetics.treeview.MainPanel#getIcon()
	 */
	public ImageIcon getIcon() {
		// can't nest linked panels yet.
		return null;
	}
	public MainPanel[] getMainPanelsByName(String name) {
		Vector matches = new Vector();
		// check the detached plugins
		Enumeration e = mpdialogs.elements();
		while (e.hasMoreElements()) {
			MainPanelFrame mpd = (MainPanelFrame) e.nextElement();
			MainPanel mp = mpd.getMainPanel();
			if (name.equals(mp.getName()))
				matches.add(mp);
		}
		Component [] docked = this.getComponents();

		// check the docked plugins
		for (int i =0; i < docked.length; i++) {
			MainPanel mp = (MainPanel) docked[i];
			if (name.equals(mp.getName())) {
				matches.add(mp);
			}
		}
		
		Object [] comps = matches.toArray();
		MainPanel [] ret = new MainPanel[comps.length];
		for (int i = 0 ; i < comps.length; i++)
			ret[i] = (MainPanel) comps[i];
		return ret;
	}
	public MainPanel[] getMainPanels() {
		Vector matches = new Vector();
		// check the detached plugins
		Enumeration e = mpdialogs.elements();
		while (e.hasMoreElements()) {
			MainPanelFrame mpd = (MainPanelFrame) e.nextElement();
			MainPanel mp = mpd.getMainPanel();
			matches.add(mp);
		}
		Component [] docked = this.getComponents();

		// check the docked plugins
		for (int i =0; i < docked.length; i++) {
			MainPanel mp = (MainPanel) docked[i];
			matches.add(mp);
		}
		
		Object [] comps = matches.toArray();
		MainPanel [] ret = new MainPanel[comps.length];
		for (int i = 0 ; i < comps.length; i++)
			ret[i] = (MainPanel) comps[i];
		return ret;
	}

	public void export(MainProgramArgs args) throws ExportException {
		throw new ExportException("Export not implemented for plugin " + getName());
	}

}