package lcm.util;

import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import java.util.*;

public class JImage extends JComponent
{
    public static final long serialVersionUID=1001;

    BufferedImage im;
    boolean fit = false;
    JPopupMenu popupMenu = new JPopupMenu("JImage Menu");
    AffineTransform t = new AffineTransform();

    public JImage()
    {
	this(null, true);
    }

    public JImage(int width, int height)
    {
	this(new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY), true);
    }
    

    public JImage(BufferedImage im)
    {
	this(im, true);
    }
    
    public JImage(BufferedImage im, boolean enableControls)
    {
	this.im = im;

	if (enableControls)
	    initControls();
    }

    void initControls()
    {
	JMenuItem jmi;

	jmi=new JMenuItem("JImage Menu");
	jmi.setEnabled(false);
	popupMenu.add(jmi);

	jmi=new JMenuItem("Fit");
	jmi.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    fit = true; repaint();
		}
	    });
	popupMenu.add(jmi);

	JMenu scaleMenu = new JMenu("Set Scale");
	popupMenu.add(scaleMenu);
	int scales[] = new int[] { 25, 50, 100, 200, 400, 800 };

	for (int i = 0; i < scales.length; i++)
	    {
		jmi=new JMenuItem(scales[i]+" %");
		jmi.addActionListener(new ScaleAction(scales[i]));
		scaleMenu.add(jmi);
	    }

	
	MyListener l = new MyListener();
	addMouseMotionListener(l);
	addMouseListener(l);
	addMouseWheelListener(l);
	addKeyListener(l);
    }

    class ScaleAction implements ActionListener
    {
	int scale;

	public ScaleAction(int scale)
	{
	    this.scale = scale;
	}

	public void actionPerformed(ActionEvent e)
	{
	    adjustZoom(scale/100.0, null);
	}
    }
    
    /** Zoom, preserving the center point on the screen. (When we
     draw, the center point may be moved in order to maximize the
     amount of image seen on screen.) **/
    void adjustZoom(double z, Point2D cp)
    {
	if (cp == null)
	    cp = new Point2D.Double(getWidth()/2, getHeight()/2);

	Point2D cxt = componentToImage(cp);
	double tx = cp.getX() - cxt.getX()*z;
	double ty = cp.getY() - cxt.getY()*z;
	
	t = new AffineTransform();
	t.translate(tx, ty);
	t.scale(z, z);
	
	fit = false;
	repaint();
    }
    
    // zoom in a notch
    void zoomIn(Point2D p)
    {
	double nz = t.getScaleX()*2.0;
	nz = Math.min(nz, 64);

	adjustZoom(nz, p);
    }

    // zoom out a notch
    void zoomOut(Point2D p)
    {
	double nz = t.getScaleX()*.5;
	nz = Math.max(nz, 1.0/32.0);

	adjustZoom(nz, p);
    }
    
    // if im == null, the image is cleared
    public void setImage(BufferedImage im)
    {
	this.im=im;
	invalidate();
	repaint();
    }

    public Dimension getMinimumSize()
    {
	//	if (im==null)
	    return new Dimension(2,2);

	    //	return new Dimension(im.getWidth(), im.getHeight());
    }

    public Dimension getPreferredSize()
    {
	return getMaximumSize();
    }

    public Dimension getMaximumSize()
    {
	if (im==null)
	    return new Dimension(2,2);

	return new Dimension(im.getWidth(), im.getHeight());
    }

    public Point2D componentToImage(Point2D p)
    {
	Point2D tp = null;
	
	try {
	    tp = t.inverseTransform(p, null);
	} catch (NoninvertibleTransformException ex) {
	    // can't happen.
	}

	return tp;
    }

    public synchronized void paint(Graphics gin)
    {
	Graphics2D g=(Graphics2D) gin;

	if (im==null)
	    return;

	int height=getHeight();
	int width=getWidth();

	if (fit)
	    {
		t = new AffineTransform();
		double scale = Math.min(((double) width)/im.getWidth(), 
					((double) height)/im.getHeight());
		// we'll re-center the transform in a moment.
		t.scale(scale, scale);
	    }

	// if the image (in either X or Y) is smaller than the view port, then center
	// the image with respect to that axis.
	double mwidth = im.getWidth()*t.getScaleX();
	double mheight = im.getHeight()*t.getScaleY();
	if (mwidth < width)
	    t.preConcatenate(AffineTransform.getTranslateInstance( (width-mwidth) / 2.0 - t.getTranslateX(), 0) );
	if (mheight < height)
	    t.preConcatenate(AffineTransform.getTranslateInstance( 0, (height-mheight)/2.0 - t.getTranslateY()));

	// if we're allowing panning (because only a portion of the image is visible),
	// don't allow translations that show less information that is possible.
	Point2D topleft     = t.transform(new Point2D.Double(0,0), null);
	Point2D bottomright = t.transform(new Point2D.Double(im.getWidth(),im.getHeight()), null);

	if (mwidth > width)
	    {
		if (topleft.getX() > 0)
		    t.preConcatenate(AffineTransform.getTranslateInstance(-topleft.getX(),0));
		if (bottomright.getX() < width)
		    t.preConcatenate(AffineTransform.getTranslateInstance(width-bottomright.getX(), 0));
		//		    t.translate(width-bottomright.getX(), 0);
	    }
	if (mheight > height)
	    {
		if (topleft.getY() > 0)
		    t.preConcatenate(AffineTransform.getTranslateInstance(0, -topleft.getY()));
		if (bottomright.getY() < height)
		    t.preConcatenate(AffineTransform.getTranslateInstance(0, height-bottomright.getY()));
	    }

	g.drawImage(im, t, null);
    }

    /*    public void setScale(boolean b)
	  {
	  scale=b;
	  }
    */

    class MyListener implements KeyListener, 
				MouseMotionListener, 
				MouseListener, MouseWheelListener
    {
	boolean     nodrag     = true;
	Point       dragBegin  = null;

	public void keyPressed(KeyEvent e)
	{
	}

        public void keyReleased(KeyEvent e)
	{
	}

	public void keyTyped(KeyEvent e)
	{
	}

	// returns true if the keypress was handled
	public boolean handleKeyPress(KeyEvent e)
	{
	    return false;
	}

	public void mouseWheelMoved(MouseWheelEvent e)
	{
	    int amount=e.getWheelRotation();

	    Point2D p = new Point2D.Double(e.getPoint().getX(), e.getPoint().getY());

	    if (amount > 0)
		zoomOut(p);
	    if (amount < 0)
		zoomIn(p);
	}

	public void mouseDragged(MouseEvent e)
	{
	    int mods=e.getModifiersEx();
	    Point dragEnd = e.getPoint();
	    boolean shift = (mods&MouseEvent.SHIFT_DOWN_MASK)>0;
	    boolean ctrl = (mods&MouseEvent.CTRL_DOWN_MASK)>0;
	    boolean alt = shift & ctrl;
	    ctrl = ctrl & (!alt);
	    shift = shift & (!alt);
	    boolean nomods = !(shift | ctrl | alt);

	    if (dragBegin == null)
		dragBegin = dragEnd;

	    nodrag=false;

	    if ((mods&InputEvent.BUTTON3_DOWN_MASK)>0 || true)
		{
		    double dx = dragEnd.getX() - dragBegin.getX();
		    double dy = dragEnd.getY() - dragBegin.getY();

		    synchronized (JImage.this)
			{
			    t.preConcatenate(AffineTransform.getTranslateInstance(dx, dy));
			}

		    dragBegin = dragEnd;
		    repaint();
		}
	}

	public void mouseMoved(MouseEvent e)
	{
	}

	public void mousePressed(MouseEvent e)
	{
	    dragBegin = e.getPoint();
	}

	public void mouseReleased(MouseEvent e)
	{
	    int mods=e.getModifiersEx();
	    
	    if (nodrag && e.getButton()==MouseEvent.BUTTON3)
		{
		    popupMenu.show(JImage.this, (int) e.getPoint().getX(), (int) e.getPoint().getY());
		}

	    nodrag = true;
	    dragBegin = null;
	}

	public void mouseClicked(MouseEvent e)
	{
	    if (e.getButton()==1 && e.getClickCount()==2)
		{
		    adjustZoom(1, null);
		}
	}

	public void mouseEntered(MouseEvent e)
	{
	}

	public void mouseExited(MouseEvent e)
	{
	}
    }
}
