import ij.*;
import ij.io.*; 
import ij.gui.*;
import ij.process.*;
import ij.plugin.filter.*; 
import ij.measure.Calibration;

import java.util.Vector; 
import java.io.*; 
import java.awt.*;
import java.awt.event.*; 
import java.util.Properties;
import java.text.DecimalFormat; 

/** This plug-in implements a "Get Extended Image Info" command.  This
    is a simple variation on the standard ij.plugin.filter.Info class.
    In addition to all the information provided by that plugin, the
    ExtendedInfo class also displays color map data and the info
    field, if present, of the FileInfo object maintained by the image
    of interest.

@see ij.io.FileInfo
@see ij.io.ImagePlus

@author Patrick Kelly <phkelly@ucsd.edu> 

*/
public class ExtendedInfo_ implements PlugInFilter, ActionListener {
    private ImagePlus imp;
    private FileInfo finfo; 
    private ImageProcessor ip; 

    private Dialog infoDlg; 

    private final String TITLE_STRING = "Extended Image Info"; 

    public int setup(String arg, ImagePlus imp) {
	// Set image. Use this to read file info.
	this.imp = imp; 
	return DOES_ALL+NO_CHANGES;
    }

    public void run(ImageProcessor ip) {
	this.ip = ip; 
	finfo = imp.getFileInfo(); 
	// Build a dialog display.
	infoDlg = buildInfoDialog(); 
	infoDlg.show(); 
    }

    //
    // Display standard image info. This ensures that user gets a
    // proper superset of the data produced by the
    // ij.plugin.filter.Info object.
    //
    private Dialog buildInfoDialog() {
	Dialog idlg = new Dialog(IJ.getInstance(),TITLE_STRING); 
	idlg.setLayout(new BorderLayout()); 

	Panel p = new Panel(); 
	p.setLayout(new FlowLayout(FlowLayout.CENTER)); 
	idlg.add("Center",p); 

	// Build string w/ standard info from image.  
	Panel pstand = buildStandardInfo(); 
	p.add(pstand); 

	// Extended info portion of dialog.
	Panel pext  = buildExtendedInfo(); 
	p.add(pext); 

	// Control button.
	Panel pb = new Panel();
	pb.setLayout(new FlowLayout()); 

	Button button = new Button(" OK "); 
	button.addActionListener(this); 

	idlg.add("South",pb); 

	pb.add(button); 
	idlg.pack(); 

	return idlg; 
    }

    // --------------------------------------------------
    public void actionPerformed(ActionEvent e) {
	infoDlg.setVisible(false);
	infoDlg.dispose(); 
    }

    // --------------------------------------------------
    //
    // Display color map and image description data if present.
    //
    private Panel buildExtendedInfo() {
	Panel p = new Panel(); 

	boolean infoSet = haveInfo(finfo); 
	boolean cmapSet = haveColorMap(finfo); 

	// Determine appropriate layout to use.
	if(infoSet && cmapSet)
	    p.setLayout(new GridLayout(2/*rows*/,1/*cols*/)); 
	else
	    p.setLayout(new FlowLayout());

	TextArea ta; 
	if(cmapSet) {
	    // Convert color map information to a string.
	    String s = colorMap2String(finfo); 
	    ta = new TextArea(s,10/*rows*/,30/*cols*/); 

	    Panel pcm = new Panel(new BorderLayout()); 
	    pcm.add("North",new Label("Color map:")); 
	    pcm.add("Center",ta);
	    p.add(pcm); 
	}
	if(infoSet) {
	    // Add FileInfo.info field to display.
	    ta = new TextArea(finfo.info,
			      10/*rows*/,30/*cols*/); 
	    Panel pid = new Panel(new BorderLayout()); 
	    pid.add("North",new Label("Image Description:")); 
	    //p.add(ta); 
	    pid.add("Center",ta); 
	    p.add(pid); 
	}

	// Handle case where neither is set.
	if(!infoSet && !cmapSet)
	    p.add(new Label("No additional information in image.")); 

	return p; 
    }

    // --------------------------------------------------
    //
    // Determine if info field is set in input FileInfo object.
    //
    private boolean haveInfo(FileInfo fi) {
	return fi.info!=null; 
    }

    // --------------------------------------------------
    //
    // Determine if color map information is maintained in input
    // FileInfo object.
    //
    private boolean haveColorMap(FileInfo fi) {
	return fi.fileType==FileInfo.GRAY8
	    || fi.fileType==FileInfo.COLOR8; 
    }

    // --------------------------------------------------
    //
    // Converts input color map information to a string.
    //
    private String colorMap2String(FileInfo fi) {
	String s = ""; 
	if(fi.reds.length>0 && fi.greens.length>0 && fi.blues.length>0)
	    s += byteArrays2String(fi.reds,fi.greens,fi.blues); 

	return s; 
    }
    
    // --------------------------------------------------
    //
    // Convert 8-bit color map information to string format. Note,
    // display bytes as unsigned.
    //
    private String byteArrays2String(byte[] reds, byte[] greens, byte[] blues) {
	int len = reds.length; 
	if(len != greens.length || len != blues.length)
	    return ""; 

	String s = ""; 

	// Integer mask used to get unsigned value from bytes.
	int imask = 255; 

	DecimalFormat df = new DecimalFormat("000"); 
	for(int i=0; i<len;++i) {
	    s += "RGB\t"+df.format(i)+"="; 
	    // Red component.
	    int r = imask & reds[i]; 
	    s += df.format(r); 
	    
	    // Green component.
	    int g = imask & greens[i]; 
	    s += "\t"+df.format(g); 

	    // Blue component.
	    int b = imask & blues[i];
	    s += "\t"+df.format(b) + "\n"; 
	}
	return s; 
    }

    // --------------------------------------------------
    private Panel buildStandardInfo() {
	Panel p = new Panel(); 
	p.setLayout(new FlowLayout()); 
	String sinfo = standardInfoString();

	MultiLineLabel label = new MultiLineLabel(sinfo); 
	label.setFont(new Font("Dialog", Font.PLAIN, 12));
	p.add(label); 

	return p; 
    }

    // --------------------------------------------------
    //
    // This is taken from the ij.plugin.filter.Info class. We use the
    // same code here to ensure that the extended image information
    // provided by our sytem is a proper superset of that provided in
    // standard plugin.
    //
    private String standardInfoString() {
	String s;
	int type;
		
	s = new String("");
	s += "Title: '" + imp.getTitle() + "'\n";
 	Calibration cal = imp.getCalibration();
	if (cal.scaled()) {
	    s += "Width:  "+IJ.d2s(imp.getWidth()*cal.pixelWidth,2)+" " + cal.getUnits()+" ("+imp.getWidth()+")\n";
	    s += "Height:  "+IJ.d2s(imp.getHeight()*cal.pixelHeight,2)+" " + cal.getUnits()+" ("+imp.getHeight()+")\n";
	    if (cal.pixelWidth==cal.pixelHeight)
		s += "Resolution:  "+IJ.d2s(1.0/cal.pixelWidth,1) + " pixels per "+cal.getUnit()+"\n";
	    else {
		s += "X Resolution:  "+IJ.d2s(1.0/cal.pixelWidth,1) + " pixels per "+cal.getUnit()+"\n";
		s += "Y Resolution:  "+IJ.d2s(1.0/cal.pixelHeight,1) + " pixels per "+cal.getUnit()+"\n";
	    }
	} else {
	    s += "Width:  " + imp.getWidth() + " pixels\n";
	    s += "Height:  " + imp.getHeight() + " pixels\n";
	}
	type = imp.getType();
    	switch (type) {
	case ImagePlus.GRAY8:
	    s += "Bits per pixel: 8 ";
	    if (imp.isInvertedLut())
		s += "(inverted LUT)\n";
	    else
		s += "(grayscale LUT)\n";
	    break;
	case ImagePlus.GRAY16: case ImagePlus.GRAY32:
	    if (type==imp.GRAY16)
		s += "Bits per pixel: 16 (signed int)\n";
	    else
		s += "Bits per pixel: 32 (float)\n";
	    s += "Display window: " + IJ.d2s(ip.getMin()) + " - " + IJ.d2s(ip.getMax()) + "\n";
	    break;
	case ImagePlus.COLOR_256:
	    s += "Bits per pixel: 8 (color LUT)\n";
	    break;
	case ImagePlus.COLOR_RGB:
	    s += "Bits per pixel: 32 (RGB)\n";
	    break;
    	}
    	int nSlices = imp.getStackSize();
    	if (nSlices>1) {
	    ImageStack stack = imp.getStack();
	    s += "Slices: " + nSlices + " (" + imp.getCurrentSlice() + ")\n";
	}
	ImageCanvas ic = imp.getWindow().getCanvas();
    	double mag = ic.getMagnification();
    	if (mag!=1.0)
	    s += "Magnification: " + mag + "\n";
	Roi roi = imp.getRoi();
	if (roi == null)
	    s += "No ROI\n";
	else {
	    switch (roi.getType()) {
	    case Roi.RECTANGLE: s += "Rectangular ROI\n"; break;
	    case Roi.OVAL: s += "Oval ROI\n"; break;
	    case Roi.POLYGON: s += "Polygon ROI\n"; break;
	    case Roi.FREEROI: s += "Freehand ROI\n"; break;
	    case Roi.LINE: s += "Line Selection\n"; break;
	    case Roi.POLYLINE: s += "Polyline Selection\n"; break;
	    case Roi.FREELINE: s += "Freehand line Selection\n"; break;
	    }
	    Rectangle r = roi.getBoundingRect();
	    if (cal.scaled()) {
		s += "  X: " + IJ.d2s(r.x*cal.pixelWidth,2) + " (" + r.x + ")\n";
		s += "  Y: " + IJ.d2s(r.y*cal.pixelHeight,2) + " (" +  r.y + ")\n";
		s += "  Width: " + IJ.d2s(r.width*cal.pixelWidth,2) + " (" +  r.width + ")\n";
		s += "  Height: " + IJ.d2s(r.height*cal.pixelHeight,2) + " (" +  r.height + ")\n";
	    } else {
		s += "  X: " + r.x + "\n";
		s += "  Y: " + r.y + "\n";
		s += "  Width: " + r.width + "\n";
		s += "  Height: " + r.height + "\n";
	    }
	}

	s += " \n";
	s += IJ.freeMemory() + "\n";
	Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
	s += "Screen Size: " + d.width + "x" + d.height;
	
	return s; 
    }

}   // end ExtendedInfo class

