/*
 * @(#) ROIManager.java 1.0 2000/10/4
 * Copyright (c) 1999 Larry Rodrigues
 */
package com.vistech.roi;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import com.vistech.imageviewer.*;
import com.vistech.util.*;

/** Creates a number of ROI-related objects required by the QuantAnalyzer application,
  * and facilitates communication between them through events.
  *
  * @version 1.0  4 Oct 2000
  * @author Lawrence Rodrigues
  */
public class ROIManager implements ShapeEventListener, ActionListener, ChangeListener{
   protected int imageWidth, imageHeight;
   protected int viewerWidth, viewerHeight;
   protected CompositeCanvas2D sourceCanvas, destCanvas;
   protected Vector roiCollection = new Vector();
   protected ROIContext roiContext;
   protected ShapeMarker roiMarker;
   protected ReportPanel reportPan;
   protected ROI2D currentROI;
   protected int roiCount = 0;
   protected PanZoom destPanZoom, sourcePanZoom;
   protected ThresholdAdjPanel thAdjuster;
   protected Color drawingColor = Color.white;
   protected int maskValue = 255;

   /** Creates various objects.
     * @param img the image, which is needed for creating the ROIContext object.
     **/

   public JPanel createUI(BufferedImage img) {
      imageWidth = img.getWidth();
      imageHeight = img.getHeight();
      double aspectRatio = imageWidth/(double)imageHeight;
      Dimension d = getViewerSize(aspectRatio);
      viewerWidth = d.width;
      viewerHeight = d.height;
      int width = d.width/2;
      int height = d.height/2;

      sourceCanvas = new CompositeCanvas2D();
      sourceCanvas.setBufferedImage(img);

      sourceCanvas.setDisplayMode(DisplayMode.SCALED);
      sourceCanvas.setBorder(BorderFactory.createTitledBorder("Source Canvas"));
      roiMarker = new ShapeMarker(sourceCanvas);
      roiMarker.addShapeEventListener(this);
      roiContext = new ROIContext(img);

      BufferedImage destImage = roiContext.getDisplayImage();

      destCanvas = new CompositeCanvas2D();
      destCanvas.setBufferedImage(destImage);
      destCanvas.setPreferredSize(new Dimension(width, height));
      destCanvas.setDisplayMode(DisplayMode.SCALED);
      destCanvas.setBorder(BorderFactory.createTitledBorder("Destionation Canvas"));

      destPanZoom = new PanZoom((ImageManipulator)destCanvas);
      sourcePanZoom = new PanZoom((ImageManipulator)sourceCanvas);
      sourcePanZoom.setPanZoomOn(false);

      thAdjuster = new ThresholdAdjPanel();
      thAdjuster.addChangeListener(this);
      thAdjuster.addActionListener(this);
      thAdjuster.setBorder(BorderFactory.createTitledBorder("Adjust Threshold"));

      ShapesGUI shapesPan= new ShapesGUI(this);
      JSplitPane ssp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sourceCanvas, shapesPan);
      JSplitPane dsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, destCanvas, thAdjuster);

      JPanel viewerpan = new JPanel();
      viewerpan.setSize(2*width, height);
      viewerpan.setLayout(new GridLayout(1,2));
      viewerpan.add(sourceCanvas);
      viewerpan.add(destCanvas);

      reportPan = new ReportPanel(roiContext);

      JPanel bottompan = new JPanel();
      bottompan.add(shapesPan);
      bottompan.add(thAdjuster);

      JSplitPane vsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, reportPan, viewerpan);
      JPanel toppan = new JPanel();
      toppan.setLayout(new GridLayout(1,1));
      toppan.add(vsp);
      vsp.setDividerLocation(0.5);

      JPanel mainpan = new JPanel(new GridLayout(1,1));
      GridBagLayout gb = new GridBagLayout() ;
      GridBagConstraints c = new GridBagConstraints();
      mainpan.setLayout(gb);
      c.fill = GridBagConstraints.BOTH;
      c.gridwidth = GridBagConstraints.REMAINDER;
      c.weighty = 1.0; c.weightx = 1.0;
      gb.setConstraints(toppan, c);
      mainpan.add(toppan);
      c.weighty = 0.05;
      gb.setConstraints(bottompan, c);
      mainpan.add(bottompan);
      sourceCanvas.repaint();
      return mainpan;
   }

   /** This method is called when a shape event is received. Note that shape events are fired by the
     * ShapeMarker when a shape is drawn. From the input parameter (i.e.,ShapeEvent object),
     * this method extracts the Shape object.With that as the input parameter, this method
     * calls updateROI() to create a new ROI object.
     * @param e the shape event state object.
     **/
   public void shapeDrawn(ShapeEvent e){
      Shape shape = e.getShape();
      updateROI(shape);
   }

   /** For a specified shape, this method calls appropriate methods
     * to create a new ROI object, and to update inforamtion related to this ROI.
     * 
     * Before createing a new ROI object, this method performs inverse transformation of
     * the input shape from the user space to the image space.
     * Once a new ROI object is created, this method adds that ROI object to ReportPanel,
     * which computes and displays histogram and other statistics.
     * It then deposits the ROI object on the destination canvas.
     * @param shape the ROI shape.
     ***/

   public void updateROI(Shape shape) {
      if(shape == null) return;
      drawROINum(shape);
      BufferedImage img = sourceCanvas.getBufferedImage();
      if(img == null) return;
      if((reportPan != null)&& (shape != null)) {
         Shape txShape = AffineTransformUtil.inverseTransform(shape,
                                         sourceCanvas.getTransform());
         Shape txShape1 = AffineTransformUtil.inverseTransform(txShape,
                                              sourceCanvas.getDispModeAtx());
         createROI(txShape1);
         reportPan.addROI(currentROI);
         destCanvas.paintImage(roiContext.getDisplayImage());
      }
   }

   /** Creates a new ROI2D object for a specified shape, and deposits that ROI
     * on the displayed image.
     *
     * @param shape the ROI shape.
    */
   protected void createROI(Shape shape){
      if(shape == null) return;
      currentROI = new ROI2D();
      currentROI.setROIContext(roiContext);
      currentROI.setID(roiCount);
      currentROI.setDrawingColor(drawingColor);
      currentROI.setMaskValue(maskValue);
      currentROI.setShape(shape);
      currentROI.drawShape(shape, drawingColor);
      currentROI.setThreshold(maskValue);
      roiCollection.add(currentROI);
   }

   /** Draws the identification number of the ROI over the source canvas.
     * The identification number is computed incrementing the roiCount variable.
     * @param shape the ROI shape.
    */
   private void drawROINum(Shape shape){
      Graphics2D g = sourceCanvas.getDisplayedImageGC();
      if(shape == null) return;
      Rectangle bounds = shape.getBounds();
      int x = bounds.x;
      int y = bounds.y;
      int wid = bounds.width;
      int ht = bounds.height;
      g.drawString((new Integer(roiCount)).toString(), x+wid/2,y+ht+15);
      roiCount++;
   }

   /** Returns the current ROI2D object.
     * There may be multiple ROIs deposited on an image. This
     * method returns the one that is set as the current ROT. Typically,
     * the current ROI is the one that was deposited last.
     * @return the current ROI2D object.  
     */
   public ROI2D getCurrentROI() {
      return currentROI;
   }

   /** This method is called when a change eventis fired by the JSlider in
     * the ThresholdAdjPanel. From the input parameter, this method retrieves the
     * the threshold value set by the JSlider, and with that value it
     * then calls thresholdROI() to change the threshold.
     * @param  e the ChangeEvent object, which carries the threshold value.
     */
   public void stateChanged(ChangeEvent e){
      int threshValue = ((JSlider)(e.getSource())).getValue();
      thresholdROI(threshValue);
      thAdjuster.setTextFieldValue(threshValue);
   }

   /** This method is called when an action event is fired by the JTextField in
     * the ThresholdAdjPanel. From the input parameter, this method retrieves the
     * the threshold value entered by the user on the JTextField, and with that value it
     * then calls thresholdROI() to change the threshold.
     * @param  e the ActionEvent object, which carries the threshold value.
     */
   public void actionPerformed(ActionEvent e){
      String str = ((JTextField)e.getSource()).getText();
      int threshValue = (Integer.valueOf(str)).intValue();
      thresholdROI(threshValue);
      thAdjuster.setSliderValue(threshValue);
   }

   /** Performs the threshold operation on the ROI. Change in the threshold value is
     * reflected on the ROI shape drawn on the destination canvas.
     * @int the threshold value
     */
   protected  void thresholdROI(int thresh){
      if(currentROI == null) return;
      currentROI.setThreshold(thresh);
      destCanvas.paintImage(roiContext.getDisplayImage());
      destCanvas.repaint();
   }

   /** Turns the pan/zoom feature on or off.
     * @param state if true, turns on pan/zoom.
     */
   public void setPanZoom(boolean state) {
      if(state) {
         sourcePanZoom.setPanZoomOn(true);
         roiMarker.setRoiOn(false);
      } else  {
         sourcePanZoom.setPanZoomOn(false);
         roiMarker.setRoiOn(true);
      }
   }

   /** This is a proxy method that sets the current shape type in the roiMarker object.
     * The ROIManager class acts as intermediary between the GUI that selects the shape
     * type and the ShapeMarker class.
     * @param mode the shape type, which is one of ShapeMarker.FREEHAND,
     * ShapeMarker.ELLIPSE, and ShapeMarker.RECTANGLE.
     */
   public void setShapeType(int mode){ roiMarker.setShapeType(mode);}

   /** Sets the drawing color of the ROI outline.
     * @param color the drawing color of the ROI outline.
     */
   public void setDrawingColor(Color color){
      drawingColor = color;
      roiMarker.setDrawingColor(color);
      if(currentROI != null) currentROI.setDrawingColor(color);
   }

   /** Sets the mask value of the current ROI. This is the value with which the thresholded
     * pixels are highlighted.
     * @param value the mask value.
     */
   public void setMaskValue(int value){
      maskValue = value;
      if(currentROI != null) currentROI.setMaskValue(value);
   }

   /** Returns the dimesnions of the viewer frame.
     * @return the dimensions of the viewer frame.
     */
   public Dimension getViewerSize(){
      return new Dimension(viewerWidth, viewerHeight);
   }

   /** Computes size of the viewer frame for a specified aspect ratio,
     * and returns its dimensions.
     * @param imageAspectRatio the aspect ratio of the image to be displayed.
     * @return the dimensions of the viewer frame.
     */

   private Dimension getViewerSize(double imageAspectRatio){
      Dimension d= Toolkit.getDefaultToolkit().getScreenSize();
      int maxWidth = (int)(d.width *0.9);
      int maxHeight = (int)(d.height*0.9);
      double screenAspectRatio = maxWidth/(double)maxHeight;
      int viewerWidth,viewerHeight;
      if(imageAspectRatio  > screenAspectRatio){
          viewerWidth =   maxWidth;
          viewerHeight = (int)( maxHeight* screenAspectRatio/imageAspectRatio);
      }else{
          viewerHeight =   maxHeight;
          viewerWidth = (int)(maxWidth* imageAspectRatio/screenAspectRatio);
      }
      return new Dimension(maxWidth, maxHeight);
   }

   /** Erases shapes drawn over the source as well as the destination canvas.
     * Also resets the report panel.
     */
   public void eraseShapes(){
      roiMarker.eraseShapes();
      roiCount = 0;
      if(sourceCanvas != null) {
         sourceCanvas.reset();
         sourceCanvas.setBufferedImage(roiContext.getOriginalImage());
      }
      if(destCanvas != null)  {
        destCanvas.reset();
        destCanvas.setBufferedImage(roiContext.getOriginalImage());
      }
      if(reportPan != null) reportPan.reset();
      if(roiContext != null) roiContext.reset();
   }

   /** Erases shapes drawn over the source as well as the destination canvas.
     **/
   public void reset() {
      roiCount = 0;
      if(sourceCanvas != null) {
         sourceCanvas.reset();
         sourceCanvas.resetManipulation();
      }
      if(destCanvas != null) {
        destCanvas.reset();
        destCanvas.resetManipulation();
      }
      if(roiContext != null) roiContext.reset();
   }
}
