package iiuf.awt;

import java.awt.Container;
import java.awt.Cursor;
import java.awt.Component;
import java.awt.TextComponent;
import java.awt.Frame;
import java.awt.Button;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.GridBagConstraints;
import java.awt.Image;
import java.awt.Window;
import java.awt.image.BufferedImage;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.io.InputStream;
import java.lang.reflect.Method;
import javax.swing.ImageIcon;

import iiuf.util.Util;
import iiuf.util.AsyncInvocation;
import iiuf.util.AsyncAccelerator;
import iiuf.log.Log;

/**
   Awt utilities.

   (c) 1999, 2000, 2001, IIUF, DIUF<p>
   
   @author $Author: ohitz $
   @version $Revision: 1.1 $
*/
public class Awt {
  public  static Cursor       STOP_CURSOR = makeCursor("stop", new Point(8, 8));
  private static Component    emptyCmp    = newComponent();
  private static final double PI2         = Math.PI * 2;
  
  public static double getAngle(int ox, int oy, int x, int y) {
    double dx = x - ox;
    double dy = y - oy;
    double r  = Math.sqrt(dx * dx + dy * dy);
    double result = dy < 0 ? Math.PI + Math.asin(dx / r) : -Math.asin(dx / r);
    result %= PI2;
    return result < 0 ? PI2 + result : result;
  }
  
  public static Point place(Rectangle rectToPlace, int x, int y, Rectangle outerLimit) {
    rectToPlace.setLocation(x - rectToPlace.width / 2, y - rectToPlace.height / 2);
    return fitSmallerIntoBigger(rectToPlace.x, rectToPlace.y, rectToPlace.width, rectToPlace.height, outerLimit);
  }
  
  public static Point centerOnScreen(Dimension dim) {
    Toolkit   tk = Toolkit.getDefaultToolkit();
    Dimension sd = tk.getScreenSize();
    return new Point((sd.width - dim.width) / 2, (sd.height - dim.height) / 2);
  }

  public static Point fitSmallerIntoBigger(int x, int y, int width, int height, Rectangle bigger) {
    return new Point(x < bigger.x ? 
		     bigger.x : 
		     x + width  > bigger.x + bigger.width  ? bigger.x + bigger.width  -
		     width  : x,
		     y < bigger.y ? 
		     bigger.y : 
		     y + height > bigger.y + bigger.height ? bigger.y + bigger.height -
		     height : y);
  }
  
  public final static boolean near(int x, int y, int x0, int y0, int x1, int y1, int tolerance) {
    int t2    = tolerance * tolerance;
    int x1_x0 = x1 - x0;
    int y1_y0 = y1 - y0;
    int x_x0  = x  - x0;
    int y_y0  = y  - y0;
    int denom = x1_x0 * x1_x0 + y1_y0 * y1_y0;
    if(denom == 0)
      return x_x0 * x_x0 + y_y0 * y_y0 <= t2;
    else {
      int nom = x_x0 * x1_x0 + y_y0 * y1_y0;
      if(nom < 0 || nom > denom) 
	return false;
      else {
	double u = (double)nom / (double)denom;
	double dx = x - (x0 + u * x1_x0);
	double dy = y - (y0 + u * y1_y0);
	return dx * dx + dy * dy <= t2;
      }
    }
  }
  
  private static Cursor makeCursor(String name, Point hotSpot) {
    return makeCursor(Awt.class.getResource("rsrc/" + name + "_crsr.gif"), hotSpot, name);
  }
  
  public  static Cursor makeCursor(URL cursorImage, Point hotSpot, String name) {
    Toolkit   tk      = Toolkit.getDefaultToolkit();
    int       ccolors = tk.getMaximumCursorColors();
    ImageIcon ic      = null;
    // try to get optimized version
    try {
      String url = cursorImage.toExternalForm();
      int    idx = url.lastIndexOf('.');
      int    bpp = -1;
      for(bpp = 31; bpp >= 0; bpp--)
	if((ccolors & (1 << bpp)) != 0)
	  break;
      if(idx == -1)
	url += bpp + "bpp";
      else
	url = url.substring(0, idx) + bpp + "bpp" + url.substring(idx);
      URL curl = new URL(url);
      InputStream in = curl.openStream();
      in.close();
      ic = new ImageIcon(new URL(url));
    } catch(Exception e) {
      ic = new ImageIcon(cursorImage);
    }
    Image     crsr = ic.getImage();
    Dimension d    = tk.getBestCursorSize(ic.getIconWidth(), ic.getIconHeight());
    if(d.width != ic.getIconWidth() || d.height != ic.getIconHeight()) {
      Image tmp = crsr;
      crsr = new BufferedImage(d.width, d.height, BufferedImage.TYPE_4BYTE_ABGR);
      ic.paintIcon(emptyCmp, crsr.getGraphics(), 0, 0);
    } 
    return tk.createCustomCursor(crsr, hotSpot, name);
  }

  public static void setCursor(Container container, int cursor) {
    Component[] cmps = container.getComponents();
    container.setCursor(Cursor.getPredefinedCursor(cursor));
    for(int i = 0; i < cmps.length; i++) {
      if(cmps[i] instanceof Container)
	setCursor((Container)cmps[i], cursor);
      else if (cmps[i] instanceof TextComponent && 
	       cursor == Cursor.DEFAULT_CURSOR)
	cmps[i].setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
      else
	cmps[i].setCursor(Cursor.getPredefinedCursor(cursor));
    }
  }

  public static Window getWindow(Component component) {
    if(component == null) return null;
    if(component instanceof Window) return (Window)component;
    else return getWindow(component.getParent());
  }
  
  public static Frame getFrame(Component component) {
    if(component == null) return null;
    if(component instanceof Frame) return (Frame)component;
    else return getFrame(component.getParent());
  }
  
  public static void setFrameCursor(Component component, int cursor) {
    setCursor(getFrame(component), cursor);
  }
  
  public static GridBagConstraints constraints(boolean last) {
  	return constraints(last, 0, 0, GridBagConstraints.NONE);
  }

  public static GridBagConstraints constraints(boolean last, int fill) {
  	return constraints(last, 0, 0, fill);
  }
  
  public static GridBagConstraints constraints(boolean last, int fill, float weightx, float weighty) {
    return constraints(last, 0, 0, fill, weightx, weighty);
  }
  
  public static GridBagConstraints constraints(boolean last, int insetx, int insety) {
    return constraints(last, insetx, insety, GridBagConstraints.NONE);
  }
  
  public static GridBagConstraints constraints(boolean last, int insetx, int insety, int fill) {
    float weightx = 0;
    float weighty = 0;
    if(fill == GridBagConstraints.HORIZONTAL) weightx = 1;
    if(fill == GridBagConstraints.VERTICAL)   weighty = 1;
    if(fill == GridBagConstraints.BOTH)
      weightx =weighty = 1;
    return constraints(last, insetx, insety, fill, weightx, weighty);
  }
  
  public static GridBagConstraints constraints(boolean last, 
					       int insetx, int insety, 
					       int fill, 
					       float weightx, float weighty) {
    GridBagConstraints result = new GridBagConstraints();
    result.weightx = weightx;
    result.weighty = weighty;
    result.anchor = GridBagConstraints.WEST;
    result.fill   = fill;
    result.insets.left   = result.insets.right = insetx;
    result.insets.bottom = result.insets.top   = insety;
    if(last) result.gridwidth = GridBagConstraints.REMAINDER;
    return result;
  }
  
  public static Button newButton(String label, ActionListener listener) {
    return newButton(label, label, listener);
  }

  public static Button newButton(String label, String command, ActionListener listener) {
    Button result = new Button(label);
    result.addActionListener(listener);
    result.setActionCommand(command);
    return result;
  }
  
  public static int getIndex(Container container, Component component) {
    Component[] cmps = container.getComponents();
    for(int i = 0; i < cmps.length; i++)
      if(component == cmps[i]) {
	return i;
      }
    return -1;
  }

  static class EmptyComponent extends Component {}

  public static Component newComponent() {
    return new EmptyComponent() {};
  }

  // ----------------------- async stuff
  
  private static AsyncInvocation async = new AsyncInvocation("AWT-AsyncInvocation");
  
  public static AsyncInvocation.Result invokeAsync(Object object, String method, Class argType, Object arg) {
    return async.invoke(object, method, argType, arg);
  }
  
  private static boolean check(Method m, String method, Class argType) {
    return 
      m.getName().equals(method) && 
      argType == m.getParameterTypes()[0] &&
      m.getParameterTypes().length == 1;
  }

  public static void addAsyncAccelerator(AsyncAccelerator accel) {
    async.addAsyncAccelerator(accel);
  }
  
  static {
    addAsyncAccelerator(new AsyncAccelerator() {
	public void handle(AsyncInvocation.Invocation i, AsyncInvocation.Result result) {
	  if(i.object instanceof MouseListener) {
	    MouseListener ml = (MouseListener)i.object;
	    if(     check(i.method, "mouseClicked",  MouseEvent.class))	ml.mouseClicked( (MouseEvent)i.args[0]);
	    else if(check(i.method, "mouseEntered",  MouseEvent.class))	ml.mouseEntered( (MouseEvent)i.args[0]);
	    else if(check(i.method, "mouseExited",   MouseEvent.class))	ml.mouseExited(  (MouseEvent)i.args[0]);
	    else if(check(i.method, "mousePressed",  MouseEvent.class))	ml.mousePressed( (MouseEvent)i.args[0]);
	    else if(check(i.method, "mouseReleased", MouseEvent.class)) ml.mouseReleased((MouseEvent)i.args[0]);
	    else throw new IllegalArgumentException("Unknown MouseListener method:" + i.method);
	    result.set(null);
	  } 
	}
      });
    
    addAsyncAccelerator(new AsyncAccelerator() {
	public void handle(AsyncInvocation.Invocation i, AsyncInvocation.Result result) {
	  if(i.object instanceof ActionListener) {
	    ActionListener al = (ActionListener)i.object;
	    if(check(i.method, "actionPerformed",  ActionEvent.class)) al.actionPerformed( (ActionEvent)i.args[0]);
	    else throw new IllegalArgumentException("Unknown MouseListener method:" + i.method);
	    result.set(null);
	  } 
	}
      });
  }
  
  private static MouseListener tmpMouseListener;
  public static synchronized MouseListener asyncWrapper(MouseListener listener_) {
    tmpMouseListener = listener_;
    
    return new MouseListener() {
	MouseListener listener = tmpMouseListener;
	
	public void mouseClicked(MouseEvent e)  {invokeAsync(listener, "mouseClicked",  MouseEvent.class, e);}
	public void mouseEntered(MouseEvent e)  {invokeAsync(listener, "mouseEntered",  MouseEvent.class, e);}
	public void mouseExited(MouseEvent e)   {invokeAsync(listener, "mouseExited",   MouseEvent.class, e);}
	public void mousePressed(MouseEvent e)  {invokeAsync(listener, "mousePressed",  MouseEvent.class, e);}
	public void mouseReleased(MouseEvent e) {invokeAsync(listener, "mouseReleased", MouseEvent.class, e);}
      };
  }

  private static ActionListener tmpActionListener;
  public static synchronized ActionListener asyncWrapper(ActionListener listener_) {
    tmpActionListener = listener_;
    
    return new ActionListener() {
	ActionListener listener = tmpActionListener;
	
	public void actionPerformed(ActionEvent e)  {invokeAsync(listener, "actionPerformed",  ActionEvent.class, e);}
      };
  }
}
/*
  $Log: Awt.java,v $
  Revision 1.1  2002/07/11 09:20:36  ohitz
  Initial checkin

  Revision 1.16  2001/03/09 15:30:50  schubige
  Added markers to graph panel

  Revision 1.15  2001/03/02 09:06:43  schubige
  interim checkin for soundium

  Revision 1.14  2001/03/01 10:42:48  schubige
  interim checkin for soundium

  Revision 1.13  2001/02/23 11:03:15  schubige
  try to recover table_source.png

  Revision 1.12  2001/02/12 17:50:05  schubige
  still working on soundium gui

  Revision 1.11  2001/01/14 13:21:13  schubige
  Win NT update

  Revision 1.10  2001/01/04 16:28:28  schubige
  Header update for 2001 and DIUF

  Revision 1.9  2001/01/03 08:30:39  schubige
  graph stuff beta

  Revision 1.8  2000/12/28 09:29:09  schubige
  SourceWatch beta

  Revision 1.7  2000/12/18 12:39:08  schubige
  Added ports to iiuf.util.graph

  Revision 1.6  2000/11/20 17:36:56  schubige
  tinja project ide

  Revision 1.5  2000/11/09 07:48:43  schubige
  early checkin for DCJava

  Revision 1.4  2000/07/14 13:40:54  schubige
  Various updates/fixes

  Revision 1.3  1999/12/02 16:07:32  schubige
  updated block, general cleanup

  Revision 1.2  1999/11/26 09:14:29  schubige
  intermediate commit

  Revision 1.1  1999/11/26 08:51:15  schubige
  *** empty log message ***

  Revision 1.6  1999/09/10 06:54:18  schubige
  Dialogs & requesters are now placed at 1/2 x and 1/3 y of the screen.

  Revision 1.5  1999/09/09 14:31:20  schubige
  Added DateChooser, Dilog and Line, updated Awt.

  Revision 1.4  1999/09/03 15:50:08  schubige
  Changed to new header & log conventions.

  Revision 1.3  1999/09/03 14:06:14  schubige
  MacCVS testing
  
  Revision 1.2  1999/09/02 14:15:28  schubige
  added @serial tag or transient to make javadoc happy
*/