/* BEGIN_HEADER                                              Java TreeView
 *
 * $Author: alokito $
 * $RCSfile: KaryoPanel.java,v $
 * $Revision: 1.7 $
 * $Date: 2009-09-08 11:24:24 $
 * $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.plugin.karyoview;

import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;

import javax.swing.*;

import edu.stanford.genetics.treeview.*;
import edu.stanford.genetics.treeview.SwingWorker;
import edu.stanford.genetics.treeview.model.TVModel;

/**
 *  This class encapsulates a dendrogram view, which is the classic Eisen
 *  treeview. It uses a drag grid panel to lay out a bunch of linked
 *  visualizations of the data, a la Eisen. In addition to laying out
 *  components, it also manages the GlobalZoomMap. This is necessary since both
 *  the GTRView (gene tree) and GlobalView need to know where to lay out genes
 *  using the same map. The zoom map is managed by the ViewFrame- it represents
 *  the selected genes, and potentially forms a link between different views,
 *  only one of which is the DendroView.
 *
 * @author     Alok Saldanha <alok@genome.stanford.edu>
 * @version $Revision: 1.7 $ $Date: 2009-09-08 11:24:24 $
 */
public class KaryoPanel extends DragGridPanel implements MainPanel {
	/**
	 *  Constructor for the KaryoPanel object
	 *
	 * @param  tvmodel   data model to represent
	 * @param geneSelection shared selection model 
	 * @param  vFrame  parent ViewFrame of DendroView
	 * @param configNode config node to take state from and store state in.
	 */
	public KaryoPanel(DataModel tvmodel, TreeSelectionI geneSelection, ViewFrame vFrame, ConfigNode configNode) {
		super(2, 3);
		viewFrame = vFrame;

		startingGenome = new Genome(tvmodel);
		genome = startingGenome;

		bindConfig(configNode);
		// requires that genome be set...
		karyoDrawer = new KaryoDrawer(genome, geneSelection, DataModel.NODATA);
		karyoDrawer.bindConfig(getFirst("KaryoDrawer"));
		karyoView = new KaryoView(karyoDrawer, tvmodel);
		karyoView.bindConfig(getFirst("KaryoView"));
	    parameterPanel = new KaryoViewParameterPanel(karyoDrawer, karyoView, this);
	    statusPanel = new MessagePanel("View Status");

		windowActive = true;
		setBorderWidth(2);
		setBorderHeight(2);
		setMinimumWidth(1);
		setMinimumHeight(1);
		setFocusWidth(1);
		setFocusHeight(1);

		setupViews();
		
		
		coordinatesTimer = new javax.swing.Timer(1000, new TimerListener());
		coordinatesTimer.stop();

		try {
			CoordinatesPresets coordinatesPresets = KaryoscopeFactory.getCoordinatesPresets();
			if (configNode.getAttribute("coordinates", "").length() > 0) {
				FileSet source = new FileSet(configNode.getAttribute("coordinates", ""),
						vFrame.getApp().getCodeBase().toString() +"coordinates/");
				getGenome(source);
			} else if (coordinatesPresets.getDefaultIndex() != -1) {
				// requires that karyoView be set, so we can get header info for matching.
				int index = coordinatesPresets.getDefaultIndex();
				FileSet def = coordinatesPresets.getFileSet(index);
//				System.out.println("default index is " + index +", fileset " + def);
				getGenome(def);
			} else {
				useOriginal();
			}
		} catch (LoadException e) {
			LogBuffer.println("Error loading coordinates " + e);
			e.printStackTrace();
			useOriginal();
		}

		KaryoColorPresets colorPresets = KaryoscopeFactory.getColorPresets();

		if (colorPresets.getDefaultIndex() != -1) {
			karyoDrawer.getKaryoColorSet().copyStateFrom(colorPresets.getDefaultColorSet());
		}

	}
	public void getGenome(FileSet fileSet) throws LoadException {
		TVModel model = new TVModel();
		model.setFrame(viewFrame);
		model.loadNew(fileSet);
		getGenome(model);
	}
	ProgressMonitor coordinatesMonitor;
	javax.swing.Timer coordinatesTimer;
	CoordinatesTask coordinatesTask;
	CoordinatesSettingsPanel coordinatesPanel = null;
	class TimerListener implements ActionListener { // manages the averagermonitor
		public void actionPerformed(ActionEvent evt) {
			if (coordinatesMonitor.isCanceled() || coordinatesTask.done()) {
				coordinatesMonitor.close();
				coordinatesTask.stop();
				// Toolkit.getDefaultToolkit().beep();
				coordinatesTimer.stop();
				if (coordinatesTask.done()) {
					coordinatesMonitor.setNote("Matching complete");
				}
				if (coordinatesPanel != null) {
					coordinatesPanel.setEnabled(true);
				}
			} else {
				coordinatesMonitor.setNote(coordinatesTask.getMessage());
				coordinatesMonitor.setProgress(coordinatesTask.getCurrent());
			}
			repaint();
		}
	}
	class CoordinatesTask {
		private int current = 0;
		private String statMessage;
		
		
		/**
		* Called to start the task. I don't know why we bother with the ActualTask class, so don't ask.
		*/
		void go(DataModel tvmodel) {
			final DataModel model = tvmodel;
			setCurrent(0);
			final SwingWorker worker = new SwingWorker() {
				public Object construct() {
					return new ActualTask(model);
				}
			};
			worker.start();
		}
		
		
		/**
		* Called from ProgressBarDemo to find out how much work needs
		* to be done.
		*/
		int getLengthOfTask() {
			HeaderInfo existingHeaders = karyoView.getGeneInfo();
			return existingHeaders.getNumHeaders();
		}
		
		/**
		* Called from ProgressBarDemo to find out how much has been done.
		*/
		int getCurrent() {
			return current;
		}
		void setCurrent(int i) {
			current = i;
		}
		public void incrCurrent() { 
			current++;
		}
		/**
		* called to stop the averaging on a cancel...
		*/
		void stop() {
			current = getLengthOfTask();
		}
		
		
		/**
		* Called from ProgressBarDemo to find out if the task has completed.
		*/
		boolean done() {
			if (current >= getLengthOfTask()) {
				return true;
			} else {
				return false;
			}
		}
		
		String getMessage() {
			return statMessage;
		}
		class ActualTask {
			ActualTask(DataModel newModel) {
				Genome newGenome = new Genome(newModel);
				
				// set indexes to -1...
				int n = newGenome.getNumLoci();
				for (int i = 0; i < n ; i++) {
					newGenome.getLocus(i).setCdtIndex(-1);
				}
				
				statMessage = "Hashing new keys ";
				HeaderInfo newHeaders = newModel.getGeneHeaderInfo();
				int newN = newHeaders.getNumHeaders();
				Hashtable tempTable = new Hashtable((newN * 4) / 3, .75f);
				for (int j = 0; j < newN; j++) {
					tempTable.put(newHeaders.getHeader(j, "YORF"), new Integer(j));
				}

				statMessage = "Performing lookups";
				// match up indexes with using headerinfo...
				HeaderInfo existingHeaders = karyoView.getGeneInfo();
				int existingN =  existingHeaders.getNumHeaders();
				for (int i = 0; i < existingN; i++) {
					incrCurrent();
					String thisID = existingHeaders.getHeader(i, "YORF");
					if (thisID == null) continue;
					if (thisID.equals("")) continue;
					Integer j = (Integer) tempTable.get(thisID);
					if (j != null) {
						newGenome.getLocus(j.intValue()).setCdtIndex(i);
					} else {
						LogBuffer.println("Missing locus for " + thisID);
					}
					if (done()) break;
				}
				KaryoPanel.this.setGenome(newGenome);
				karyoDrawer.setGenome(newGenome);
				karyoView.recalculateAverages();
				karyoView.redoScale();
				stop();
			}
		}
	}

	public void getGenome(DataModel newModel) {
		coordinatesTask = new CoordinatesTask();
		coordinatesMonitor = new ProgressMonitor(this,
		"Finding matching coordinates",
		"Note", 0, coordinatesTask.getLengthOfTask());
		coordinatesMonitor.setProgress(0);
		coordinatesTask.go(newModel);
		coordinatesTimer.start();
	}
	public void useOriginal() {
		karyoDrawer.setGenome(startingGenome);
		karyoView.recalculateAverages();
		karyoView.redoScale();
	}
	


	// accessors
	/**
	 *  This method should be called only during initial setup of the modelview
	 *
	 *  It sets up the views, which are reinitialized if the model
	 *  changes.
	 *
	 */
	private void setupViews() {

	    Rectangle rectangle  = new Rectangle(0, 0, 1, 1);
	    
	    addComponent(new JScrollPane(parameterPanel), rectangle);
	    rectangle.translate(1, 0);
	    
	    addComponent(statusPanel, rectangle);

	    rectangle.setSize(2, 2);
	    rectangle.translate(-1, 1);
	    addComponent(karyoView.getComponent(), rectangle);
		karyoView.setParameterPanel(parameterPanel);
	    karyoView.setStatusPanel(statusPanel);
	    karyoView.setViewFrame(viewFrame);

	    rectangle.translate(0, 1);
	    // addView(KaryoZoomView);
	}

	/**
	 *  Adds a component to the DendroView
	 *
	 * @param  component  The component to be added
	 * @param  rectangle  The location to add it in
	 */
	public void addComponent(Component component, Rectangle rectangle) {
		if (component != null) {
			addComponent(component, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
		}
	}


	/**
	 *  Adds a ModelView to the KaryoPanel
	 *
	 * @param  modelView  The ModelView to be added
	 * @param  rectangle  The location to add it in
	 */
	public void addView(ModelView modelView, Rectangle rectangle) {
		addComponent(modelView.getComponent(), rectangle);
		modelView.setStatusPanel(statusPanel);
		modelView.setViewFrame(viewFrame);
	}


	/**
	 *  Determines if window is currently active
	 *
	 * @return    returns true if active
	 */
	public boolean windowActive() {
		return windowActive;
	}


	// MainPanel
		/**
	 *  This makes the persistent storage resemble the compnents, if it doesn't already. 
	 */
	public void syncConfig() {
	}

	
	public void showDisplayPopup() {
		SettingsPanel avePanel = new DisplaySettingsPanel(this, KaryoscopeFactory.getColorPresets(), viewFrame);
		JDialog popup = new ModelessSettingsDialog(viewFrame, "Display", avePanel);
		popup.addWindowListener(XmlConfig.getStoreOnWindowClose(viewFrame.getDataModel().getDocumentConfigRoot()));
		popup.pack();
		popup.setVisible(true);
	}
	public void showCoordinatesPopup() {
		coordinatesPanel = new CoordinatesSettingsPanel(KaryoPanel.this, 
				KaryoscopeFactory.getCoordinatesPresets(), viewFrame);
		JDialog popup = new ModelessSettingsDialog(viewFrame, "Coordinates", coordinatesPanel);
		popup.addWindowListener(XmlConfig.getStoreOnWindowClose(viewFrame.getDataModel().getDocumentConfigRoot()));
		popup.pack();
		popup.setVisible(true);
	}

	public void showAveragingPopup() {
		SettingsPanel avePanel = karyoView.getAveragerSettingsPanel();
		JDialog popup = new ModelessSettingsDialog(viewFrame, "Averaging", avePanel);
		popup.addWindowListener(XmlConfig.getStoreOnWindowClose(viewFrame.getDataModel().getDocumentConfigRoot()));
		popup.pack();
		popup.setVisible(true);
	}
	/**
	 *  Add items related to settings
	 *
	 * @param  menu  A menu to add items to.
	 */
	public void populateSettingsMenu(TreeviewMenuBarI menu) {
		menu.addMenuItem("Display...", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				showDisplayPopup();
			}
		}, 0);
		menu.setMnemonic(KeyEvent.VK_D);

		menu.addMenuItem("Averaging...", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				showAveragingPopup();
			}
		}, 0);
		menu.setMnemonic(KeyEvent.VK_A);
		
		menu.addMenuItem("Coordinates...", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				showCoordinatesPopup();
			}
		}, 0);
		menu.setMnemonic(KeyEvent.VK_C);

		menu.addMenuItem("Url Links...", new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				SettingsPanel urlPanel = new UrlSettingsPanel(viewFrame.getUrlExtractor() , viewFrame.getGeneUrlPresets());
				JDialog popup = new ModelessSettingsDialog(viewFrame, "Url Linking", urlPanel);
				popup.pack();
				popup.setVisible(true);
			}
		}, 0);
		menu.setMnemonic(KeyEvent.VK_U);
	}


	/**
	 *  Add items which do some kind of analysis
	 *
	 * @param  menu  A menu to add items to.
	 */
	public void populateAnalysisMenu(TreeviewMenuBarI menu) {
	}

	/**
	 *  Add items which allow for export, if any.
	 *
	 * @param  menu  A menu to add items to.
	 */
	public void populateExportMenu(TreeviewMenuBarI menu) {
		menu.addMenuItem("Export to Image...", new ActionListener() {
		  public void actionPerformed(ActionEvent actionEvent) {
				double origPPM, origPPV; // save since export currently changes these.
				origPPM = getKaryoDrawer().getPixelPerMap();
				origPPV = getKaryoDrawer().getPixelPerVal();

			  BitmapKaryoViewExportPanel bitmapPanel = new BitmapKaryoViewExportPanel
			(karyoView);
			bitmapPanel.setSourceSet(viewFrame.getDataModel().getFileSet());

			final JDialog popup = new CancelableSettingsDialog(viewFrame, "Export to Image", bitmapPanel);
			popup.pack();
			int width = popup.getWidth();
			int height = popup.getHeight();
			if (width < 500) width = 500;
			if (height < 300) height = 300;
			popup.setSize(width, height);
			popup.setVisible(true);
			getKaryoDrawer().setPixelPerMap(origPPM);
			getKaryoDrawer().setPixelPerVal(origPPV);
		  }
		});
		menu.setMnemonic(KeyEvent.VK_I);
	}

	/**
	 *  ensure a particular index is visible. Used by Find.
	 *
	 * @param  i  Index of gene in cdt to make visible
	 */
	public void scrollToGene(int i) {
		//LogPanel.println("KaryoPanel.scrollToGene not implemented");
	}
	public void scrollToArray(int i) {
		//LogPanel.println("KaryoPanel.scrollToArray not implemented");
	}

	private ConfigNode configNode;
	/** Setter for configNode */
	public void bindConfig(ConfigNode configNode) {
		this.configNode = configNode;
	}
	/** Getter for configNode */
	public ConfigNode getConfigNode() {
		return configNode;
	}
private ViewFrame viewFrame;
	private boolean windowActive;
	/** store original coordinates here... */
	private Genome startingGenome;
	private Genome genome;
	/** Setter for genome */
	public void setGenome(Genome genome) {
		FileSet fileSet = genome.getFileSet();
		configNode.setAttribute("coordinates", 
				fileSet.getRoot() + fileSet.getExt(),
				"");
		this.genome = genome;
	}
	/** Getter for genome */
	public Genome getGenome() {
		return genome;
	}
	
	private KaryoDrawer karyoDrawer;
	/** Setter for karyoDrawer */
	public void setKaryoDrawer(KaryoDrawer karyoDrawer) {
		this.karyoDrawer = karyoDrawer;
	}
	/** Getter for karyoDrawer */
	public KaryoDrawer getKaryoDrawer() {
		return karyoDrawer;
	}

	private KaryoView karyoView;
	/** Setter for karyoView */
	public void setKaryoView(KaryoView karyoView) {
		this.karyoView = karyoView;
	}
	/** Getter for karyoView */
	public KaryoView getKaryoView() {
		return karyoView;
	}

    private KaryoViewParameterPanel parameterPanel;

	private MessagePanel statusPanel;

	/**
	* always returns an instance of the node, even if it has to create it.
	*/
	private ConfigNode getFirst(String name) {
		ConfigNode cand = getConfigNode().fetchFirst(name);
		return (cand == null)? getConfigNode().create(name) : cand;
	}
	private static ImageIcon karyoIcon = null;
	/**
	 * icon for display in tabbed panel
	 */
	public ImageIcon getIcon() {
		if (karyoIcon == null) {
			try {
			karyoIcon = new ImageIcon("images/karyoscope.gif", "Karyoscope Icon");
			} catch (java.security.AccessControlException e) {
				// need form relative URL somehow...
			}		
		}
		return karyoIcon;
	}

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

