/* BEGIN_HEADER                                              Java TreeView
 *
 * $Author: alokito $
 * $RCSfile: ATRView.java,v $
 * $Revision: 1.2 $
 * $Date: 2010-05-02 13:39:00 $
 * $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.dendroview;

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

import javax.swing.JPanel;
import javax.swing.JScrollBar;

import edu.stanford.genetics.treeview.*;

/**
 *  Draws an array tree to show the relations between arrays This object requires
 *  a MapContainer to figure out the offsets for the arrays. Furthermore, it sets
 *  up a scrollbar to scroll the tree, although there is currently no way to specify
 *  how large you would like the scrollable area to be.
 *
 * @author     Alok Saldanha <alok@genome.stanford.edu>
 * @version    $Revision: 1.2 $ $Date: 2010-05-02 13:39:00 $
 */

public class ATRView extends ModelViewBuffered implements 
		MouseListener, KeyListener {
	/**  Constructor, sets up AWT components  */
	public ATRView() {
		super();

		panel = new JPanel();
		scrollbar = new JScrollBar(JScrollBar.VERTICAL, 0, 1, 0, 1);
		destRect = new Rectangle();

		panel.setLayout(new BorderLayout());
		panel.add(this, BorderLayout.CENTER);
		panel.add(scrollbar, BorderLayout.EAST);

		addMouseListener(this);
		addKeyListener(this);
	}


	private final static String[] hints = {
			"Click to select node",
			" - use arrow keys to navigate tree",
			};


	/*inherit description*/
	public String[] getHints() {
		return hints;
	}


	/**
	 *  Set the selected node and redraw
	 *
	 * @param  n  The new node to be selected Does nothing if the node is already selected.
	 */
	public void setSelectedNode(TreeDrawerNode n) {
		if (selectedNode == n) {
			return;
		}
		if (selectedNode != null) {
			drawer.paintSubtree(offscreenGraphics,
					xScaleEq, yScaleEq,
					destRect, selectedNode, false);
		}
		selectedNode = n;
		if (selectedNode != null) {
			if (xScaleEq != null) {
				drawer.paintSubtree(offscreenGraphics,
						xScaleEq, yScaleEq,
						destRect, selectedNode, true);
			}
		}

		if ((status != null) && hasMouse) {
			status.setMessages(getStatus());
		}
		synchMap();
		repaint();
	}

	/** make sure the selected array range reflects the selected node, if any. */
	private void synchMap() {
		if ((selectedNode != null) && (arraySelection != null)) {
			int start  = (int) (selectedNode.getLeftLeaf().getIndex());
			int end    = (int) (selectedNode.getRightLeaf().getIndex());
			arraySelection.deselectAllIndexes();
			arraySelection.setSelectedNode(selectedNode.getId());
			arraySelection.selectIndexRange(start, end);
			arraySelection.notifyObservers();
		}
		   if ((status != null) && hasMouse)
		   {status.setMessages(getStatus());}
	}



	/**
	 *  Set  <code>TreeSelection</code> object which coordinates the shared selection state.
	 *
	 * @param  arraySelection  The <code>TreeSelection</code> which is set by selecting arrays in the
	 *      </code>GlobalView</code>
	 */
	public void setArraySelection(TreeSelectionI arraySelection) {
		if (this.arraySelection != null) {
			this.arraySelection.deleteObserver(this);
		}
		this.arraySelection = arraySelection;
		this.arraySelection.addObserver(this);
	}


	/**
	 *  Set the drawer
	 *
	 * @param  d  The new drawer
	 */
	public void setInvertedTreeDrawer(InvertedTreeDrawer d) {
		if (drawer != null) {
			drawer.deleteObserver(this);
		}
		drawer = d;
		drawer.addObserver(this);
	}


	/**
	 *  Set the map.
	 *      For the ATRView, this determines where the leaves of the tree will be.
	 *
	 * @param  m  The new map to be used for determining the spacing between indexes.
	 */
	public void setMap(MapContainer m) {
		if (map != null) {
			map.deleteObserver(this);
		}
		map = m;
		map.addObserver(this);
	}




	/**
	 *  expect updates to come from map, arraySelection and drawer
	 *
	 * @param  o    The observable which sent the update
	 * @param  arg  Argument for this update, typically null.
	 */
	public void update(Observable o, Object arg) {
		if (isEnabled() == false) {
			return;
		}

		if (o == map) {
			//	    System.out.println("Got an update from map");
			offscreenValid = false;
			repaint();
		} else if (o == drawer) {
			//System.out.println("Got an update from drawer");
			offscreenValid = false;
			repaint();
		} else if (o == arraySelection) {
//			LogBuffer.println("got update from arraySelection "+o );
			TreeDrawerNode cand = null;
			if (arraySelection.getNSelectedIndexes() > 0) {
				// This clause selects the array node if only a single array is selected.
				if (arraySelection.getMinIndex() == arraySelection.getMaxIndex()) {
					cand = drawer.getLeaf(arraySelection.getMinIndex());
				}
				// this clause selects the root node if all arrays are selected.
				if (arraySelection.getMinIndex() == map.getMinIndex()) {
					if (arraySelection.getMaxIndex() == map.getMaxIndex()) {
						cand = drawer.getRootNode();
					}
				}
			}
			// Only notify observers if we're changing the selected node.
			if ((cand != null) && (cand.getId() != arraySelection.getSelectedNode())) {
				arraySelection.setSelectedNode(cand.getId());
				arraySelection.notifyObservers();
			} else {
				setSelectedNode(drawer.getNodeById(arraySelection.getSelectedNode()));
			}
		} else {
			System.out.println(viewName() + "Got an update from unknown " + o);
		}
	}


	/**
	 *  Need to blit another part of the buffer to the screen when the scrollbar moves.
	 *
	 * @param  evt  The adjustment event generated by the scrollbar
	 */
	public void adjustmentValueChanged(AdjustmentEvent evt) {
		repaint();
	}


	// method from ModelView
	/**
	 *  Implements abstract method from ModelView. In this case, returns "ATRView"
	 *
	 * @return    name of this subclass of modelview
	 */
	public String viewName() {
		return "ATRView";
	}

    // method from ModelView
	/**
	 *  Gets the status attribute of the ATRView object The status is some information
	 *  which the user might find useful.
	 *
	 * @return    The status value
	 */
		public String[]  getStatus() {
			String [] status;
			if (selectedNode != null) {
				if (selectedNode.isLeaf()) {
					status = new String [2];
					status[0] = "Leaf Node " + selectedNode.getId();
					status[1] = "Pos " + selectedNode.getCorr();
				} else {
					int [] nameIndex = getHeaderSummary().getIncluded();
					status = new String [nameIndex.length * 2];
					HeaderInfo atrInfo = getViewFrame().getDataModel().getAtrHeaderInfo();
					String [] names = atrInfo.getNames();
					for (int i = 0; i < nameIndex.length; i++) {
						status[2*i] = names[nameIndex[i]] +":";
						status[2*i+1] = " " +atrInfo.getHeader(atrInfo.getHeaderIndex(selectedNode.getId()))[ nameIndex[i]];
					}
				}
			} else {
				status = new String [2];
				status[0] = "Select Node to ";
				status[1] = "view annotation.";
			}
			return status;
		}

	protected HeaderSummary headerSummary = new HeaderSummary();
	/** Setter for headerSummary */
	public void setHeaderSummary(HeaderSummary headerSummary) {
		this.headerSummary = headerSummary;
	}
	/** Getter for headerSummary */
	public HeaderSummary getHeaderSummary() {
		return headerSummary;
	}

	/*inherit description*/
	public void updateBuffer(Graphics g) {
		if (offscreenChanged == true) {
			offscreenValid = false;
		}
		if ((offscreenValid == false) && (drawer != null)) {
			map.setAvailablePixels(offscreenSize.width);

			// clear the pallette...
			g.setColor(Color.white);
			g.fillRect
					(0, 0, offscreenSize.width, offscreenSize.height);
			g.setColor(Color.black);

			//	calculate Scaling
			destRect.setBounds(0, 0, map.getUsedPixels(), offscreenSize.height);
			xScaleEq = new LinearTransformation
					(map.getIndex(destRect.x), destRect.x,
					map.getIndex(destRect.x + destRect.width),
					destRect.x + destRect.width);
			yScaleEq = new LinearTransformation
					(drawer.getCorrMin(), destRect.y,
					drawer.getCorrMax(), destRect.y + destRect.height);

			// draw
			drawer.paint(g,
					xScaleEq, yScaleEq,
					destRect, selectedNode);
		} else {
			//	    System.out.println("didn't update buffer: valid = " + offscreenValid + " drawer = " + drawer);
		}
	}


	// Mouse Listener
	/**
	 *  When a mouse is clicked, a node is selected.
	 */
	public void mouseClicked(MouseEvent e) {
		if (isEnabled() == false) {
			return;
		}
		if (this == null) {
			return;
		}
		if (enclosingWindow().isActive() == false) {
			return;
		}
		if (drawer != null) {
			// the trick is translating back to the normalized space...
			setSelectedNode
					(drawer.getClosest(xScaleEq.inverseTransform(e.getX()),
					yScaleEq.inverseTransform(e.getY()),
			// weight must have correlation slope on top
					yScaleEq.getSlope() / xScaleEq.getSlope())
					);
		}
	}


	// method from KeyListener
	/**
	 *  Arrow keys are used to change the selected node.
	 *
	 * up selects parent of current.
	 * left selects left child.
	 * right selects right child
	 * down selects child with most descendants.
	 *
	 */
	public void keyPressed(KeyEvent e) {
		if (selectedNode == null) {
			return;
		}

		int c                = e.getKeyCode();
		switch (c) {
		case KeyEvent.VK_UP:
			selectParent();
		break;
		case KeyEvent.VK_LEFT:
			if (selectedNode.isLeaf() == false) {
				selectLeft();
			}
		break;
		case KeyEvent.VK_RIGHT:
			if (selectedNode.isLeaf() == false) {
				selectRight();
			}
		break;
		case KeyEvent.VK_DOWN:
			if (selectedNode.isLeaf() == false) {
				TreeDrawerNode right  = selectedNode.getRight();
				TreeDrawerNode left   = selectedNode.getLeft();
				if (right.getRange() > left.getRange()) {
					selectRight();
				} else {
					selectLeft();
				}
			}
		break;
		}
	}

	 private void selectParent() {
		   TreeDrawerNode current = selectedNode;
		   selectedNode = current.getParent();
		   if (selectedNode == null) {
			   selectedNode = current;
			   return;
		   }
		   if (current == selectedNode.getLeft())
			   current = selectedNode.getRight();
		   else
			   current = selectedNode.getLeft();
		   drawer.paintSubtree(offscreenGraphics, 
			xScaleEq, yScaleEq,
			destRect, current, true);
		   drawer.paintSingle(
		   	offscreenGraphics, xScaleEq, yScaleEq,
			destRect, selectedNode, true);
				 
		   synchMap();
		   repaint();
		 }
	 
	private void selectRight() {
		if (selectedNode.isLeaf()) {
			return;
		}
		TreeDrawerNode current  = selectedNode;
		selectedNode = current.getRight();
		drawer.paintSingle(offscreenGraphics,
				xScaleEq, yScaleEq, destRect, current, false);
		drawer.paintSubtree(offscreenGraphics,
				xScaleEq, yScaleEq,
				destRect, current.getLeft(), false);
		synchMap();
		repaint();
	}


	private void selectLeft() {
		if (selectedNode.isLeaf()) {
			return;
		}
		TreeDrawerNode current  = selectedNode;
		selectedNode = current.getLeft();
		drawer.paintSingle(offscreenGraphics,
				xScaleEq, yScaleEq, destRect, current, false);
		drawer.paintSubtree(offscreenGraphics,
				xScaleEq, yScaleEq,
				destRect, current.getRight(), false);
		synchMap();
		repaint();
	}


	/**
	 *  Key releases are ignored.
	 *
	 * @param  e  The keyevent
	 */
	public void keyReleased(KeyEvent e) { }


	/**
	 *  Key types are ignored.
	 *
	 * @param  e  the keypress.
	 */
	public void keyTyped(KeyEvent e) { }


	private TreeSelectionI arraySelection;
	private LinearTransformation xScaleEq, yScaleEq;
	private MapContainer map;
	private JScrollBar scrollbar;
	private InvertedTreeDrawer drawer = null;
	private TreeDrawerNode selectedNode = null;
	private Rectangle destRect = null;
}

