package fplot;

import java.awt.*;
import java.awt.event.*;
/**
 * 
 * 
 * @author Christian Semrau
 * <a href="mailto:Christian.Semrau@student.uni-magdeburg.de">
 * Christian.Semrau@student.uni-magdeburg.de</a>
 * <br><a href="http://chsemrau.de">Homepage</a>
 */
public class Zoomer implements MouseListener, MouseMotionListener {
	protected Canvas canvas;

	// zaehlt die Veraenderungen des Darstellungsbereichs
	protected int modCount = 0;
	// Darstellungsbereich
	protected double xmin, xmax, ymin, ymax;
	// letzter Darstellungsbereich
	protected double oxmin, oxmax, oymin, oymax;
	// letzte und erste Position, an der die Maus mit gedrueckter Taste
	// bewegt wurde
	protected Point lastDrag, firstDrag;

	protected final static int NO_STATE = 0;
	public final static int MOVE_STATE = 1, RESIZE_STATE = 2;
	// aktueller und voriger Drag-Modus
	protected int dragState = MOVE_STATE, preState = NO_STATE;
	protected boolean dragging = false;

	// der Darstellungsbereich kann maximal bis +- grenze reichen,
	// die kleinste erlaubte Aufloesung ist 1/grenze
	protected final static double grenze = 1e6;

/**
 * Konstruiert einen Zoomer.
 * Uebergeben wird das Canvas, in dem die Mausereignisse registriert
 * werden, und fuer das ein skalierbarer und verschiebbarer
 * Koordinatenbereich realisiert wird.
 */
public Zoomer(Canvas c) {
	canvas = c;
}

/** Aendert den Mauscursor entsprechend dem aktuellen Zustand */
public void changeMouseCursor() {
	if (dragState != preState)
	switch(dragState){
		case MOVE_STATE: canvas.setCursor(
			Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); break;
		case RESIZE_STATE: canvas.setCursor(
			Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); break;
		case NO_STATE: canvas.setCursor(
			Cursor.getDefaultCursor()); break;
	}
	preState = dragState;
}

/**
 * Liefert das Canvas.
 * @return java.awt.Canvas
 */
public Canvas getCanvas() {
	return canvas;
}

/** Liefert den Veraenderungszaehler. */
public int getModCount() {
	return modCount++;
}

public double getXmax() { return xmax;}

public double getXmin() { return xmin;}

public double getYmax() { return ymax;}

public double getYmin() { return ymin;}

/**
 * Reagiert auf Klicks der rechten Maustaste.
 * Schaltet zwischen Verschieben und Skalieren um.
 */
public void mouseClicked(MouseEvent e) {
	int mods = e.getModifiers();
	if ((mods & e.BUTTON3_MASK) != 0){
		if (dragState == MOVE_STATE) dragState = RESIZE_STATE;
		else
		if (dragState == RESIZE_STATE) dragState = MOVE_STATE;
		changeMouseCursor();
	}
}

/**
 * Reagiert auf Bewegungen der Maus mit gedrueckter Taste.
 * Skaliert oder verschiebt den Darstellungsbereich.
 * Ruft die Methode mouseWasDragged auf, die anwendungsspezifische
 * Reaktionen implementiert.
 * @see mouseWasDragged(java.awt.event.MouseEvent e)
 */
public void mouseDragged(MouseEvent e) {
	dragging = true;
	oxmin = xmin;
	oxmax = xmax;
	oymin = ymin;
	oymax = ymax;
	int dx = e.getX()-lastDrag.x;
	int dy = e.getY()-lastDrag.y;
	if (dragState == RESIZE_STATE){
		double factor = 0.02;
		zoomX(firstDrag.x, Math.exp(-dx*factor));
		zoomY(firstDrag.y, Math.exp(dy*factor));
	}else
	if (dragState == MOVE_STATE){
		double x1 = xmin - scaleI2X()*dx;
		double x2 = xmax - scaleI2X()*dx;
		double y1 = ymin + scaleI2Y()*dy;
		double y2 = ymax + scaleI2Y()*dy;
		// Grenzen einhalten
		if (x1<-grenze){ x2 -= grenze+x1; x1 = -grenze; }
		if (y1<-grenze){ y2 -= grenze+y1; y1 = -grenze; }
		if (x2>+grenze){ x1 += grenze-x2; x2 = +grenze; }
		if (y2>+grenze){ y1 += grenze-y2; y2 = +grenze; }
		xmin = x1; xmax = x2;
		ymin = y1; ymax = y2;
		modCount++;
	}

	lastDrag = e.getPoint();
	if (oxmin!=xmin || oxmax!=xmax || oymin!=ymin || oymax!=ymax){
		mouseWasDragged(e);
	}
}

/** Aktiviert den MouseMotionListener, wenn die Maus ins Fenster kommt */
public void mouseEntered(MouseEvent e) {
	canvas.addMouseMotionListener(this);
}

/** Deaktiviert den MouseMotionListener, wenn die Maus das Fenster verlaesst */
public void mouseExited(MouseEvent e) {
	canvas.removeMouseMotionListener(this);
}

/**
 * Reagiert auf Bewegungen der Maus innerhalb des Canvas.
 * Muss ueberschrieben werden, um Reaktionen zu erhalten.
 */
public void mouseMoved(MouseEvent e) {
}

/** Setzt die drag-Koordinaten, wenn eine Maustaste gedrueckt wurde */
public void mousePressed(MouseEvent e) {
	firstDrag = lastDrag = e.getPoint();
}

/**
 * Deaktiviert den drag-Modus.
 * Ruft die Methode mouseWasReleased auf, die anwendungsspezifische
 * Reaktionen implementiert.
 * @see mouseWasReleased(java.awt.event.MouseEvent e)
 */
public void mouseReleased(MouseEvent e) {
	dragging = false;
	changeMouseCursor();
	mouseWasReleased(e);
}

/**
 * Wird automatisch aufgerufen, wenn beim Verschieben oder Skalieren
 * tatsaechlich eine Veraenderung des Bildbereiches erfolgte.
 * Diese Methode muss ueberschrieben werden, um Reaktionen zu erhalten.
 */
public void mouseWasDragged(MouseEvent e) {
}

/**
 * Wird automatisch aufgerufen, wenn eine Maustaste innerhalb des
 * Canvas losgelassen wird.
 * Diese Methode muss ueberschrieben werden, um Reaktionen zu erhalten.
 */
public void mouseWasReleased(MouseEvent e) {
}

/** Liefert den Skalierungsfaktor in x-Richtung,
 * der von Pixelkoordinaten in Funktionskoordinaten umrechnet */
public double scaleI2X() {
	return (xmax-xmin)/(canvas.getBounds().width-1);
}

/** Liefert die Funktionskoordinate der Pixelkoordinate x */
public double scaleI2X(int x) {
	return x*(xmax-xmin)/(canvas.getBounds().width-1)+xmin;
}

/** Liefert den Skalierungsfaktor in y-Richtung,
 * der von Pixelkoordinaten in Funktionskoordinaten umrechnet */
public double scaleI2Y() {
	return (ymax-ymin)/(canvas.getBounds().height-1);
}

/** Liefert die Funktionskoordinate der Pixelkoordinate y */
public double scaleI2Y(int y) {
	return y*(ymax-ymin)/(canvas.getBounds().height-1)+ymin;
}

/** Liefert die Pixelkoordinate der Funktionskoordinate x */
public int scaleX2I(double x) {
	return (int)Math.round((x-xmin)/(xmax-xmin)*(canvas.getBounds().width-1));
}

/** Liefert die Pixelkoordinate der Funktionskoordinate y */
public int scaleY2I(double y) {
	return (int)Math.round((y-ymin)/(ymax-ymin)*(canvas.getBounds().height-1));
}

/** Setzt den Darstellungsbereich auf die uebergebenen Werte,
 * passt den Bereich an die Grenzen an */
public void setCoords(double xmi, double xma, double ymi, double yma) {
	if (xmi < -grenze) xmi = -grenze;
	if (ymi < -grenze) ymi = -grenze;
	if (xma > +grenze) xma = +grenze;
	if (yma > +grenze) yma = +grenze;

	double grenzinv = 1./grenze;
	if (xmi+grenzinv > xma){
		double xmid = (xma+xmi)/2;
		xmi = xmid-grenzinv/2;
		xma = xmid+grenzinv/2;
	}
	if (ymi+grenzinv > yma){
		double ymid = (yma+ymi)/2;
		ymi = ymid-grenzinv/2;
		yma = ymid+grenzinv/2;
	}
	oxmin = xmin;
	oxmax = xmax;
	oymin = ymin;
	oymax = ymax;
	xmin = xmi; xmax = xma;
	ymin = ymi; ymax = yma;
	modCount++;
}

/**
 * Setzt den drag-State auf den angegebenen Wert.
 * @param state int
 */
public void setDragState(int state) {
	if (state==RESIZE_STATE || state==MOVE_STATE){
		preState = dragState;
		dragState = state;
	}
}

/** Zoomt den Darstellungsbereich um den Punkt p */
public void zoom(Point p, double factor) {
	zoomX(p.x, factor);
	zoomY(p.y, factor);
}

/** Zoomt in x-Richtung */
public void zoomX(int px, double factor) {
	double x = scaleI2X(px);
	double xma = x-(x-xmax)*factor;
	double xmi = x-(x-xmin)*factor;
	if (xma > +grenze) xma = +grenze;
	if (xmi < -grenze) xmi = -grenze;
	if (xmi+1./grenze <= xma){
		oxmin = xmin;
		oxmax = xmax;
		xmin = xmi; xmax = xma;
		modCount++;
	}
}

/** Zoomt in y-Richtung */
public void zoomY(int py, double factor) {
	double y = scaleI2Y(canvas.getBounds().height-1-py);
	double yma = y-(y-ymax)*factor;
	double ymi = y-(y-ymin)*factor;
	if (yma > +grenze) yma = +grenze;
	if (ymi < -grenze) ymi = -grenze;
	if (ymi+1./grenze <= yma){
		oymin = ymin;
		oymax = ymax;
		ymin = ymi; ymax = yma;
		modCount++;
	}
}
}