import java.awt.*;
import java.applet.*;

// @(#)polezero.java	1.9 96/09/28

/*
 * Copyright (c) 1996 Manfred Thole
 *      thole@nst.ing.tu-bs.de
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 */

/**
    Pole - Zero
    @author Manfred Thole, thole@nst.ing.tu-bs.de
    @version 96/09/28
  */

public class polezero extends Applet {

  final static int IDIM = 200;
  final static int DI = 10;
  final static int FK = 2;
  final static int BUTTON = 35;

  final static double MAX_RI = 5.0;
  final static double MAX_H  = 5.0;
  final static double MAX_PHI  = Math.PI;
  final static double W_MULT = 10.0;

  private static Font font = new Font("TimesRoman", Font.PLAIN, 12);

  // Use UniCode-Characters or emulate them
  private static boolean use_unicode = false;

  private int pole_r;
  private int pole_i;
  private int zero_r;
  private int zero_i;

  private Image pz_bg_image;
  private Graphics pz_bg_g;

  private Image pz_image;
  private Graphics pz_g;

  private Image jw_bg_image;
  private Graphics jw_bg_g;

  private Image jw_image;
  private Graphics jw_g;

  private Image phi_bg_image;
  private Graphics phi_bg_g;

  private Image phi_image;
  private Graphics phi_g;

  private String p_name;
  private String z_name;

  private Button b_ap;
  private PlotFrame plotFrame;
  private Thread calcPlot;
  private ThreadGroup threadGroup;   // For Netscape ...


  public static void kkpz( Graphics g, Color b, Color r) {
    g.setColor(b);
    g.drawLine(0, IDIM/2, IDIM-1, IDIM/2);
    g.drawLine(IDIM/2, IDIM/2, IDIM/2, IDIM-1);
    for (int i = 0; i < IDIM/2; i+=(IDIM/2)/MAX_RI)
      for (int j = 0; j < IDIM/2; j+=(IDIM/2)/MAX_RI)
	{
	  g.drawLine(i+IDIM/2, j+IDIM/2, i+IDIM/2, j+IDIM/2);
	  g.drawLine(i+IDIM/2, IDIM/2-j, i+IDIM/2, IDIM/2-j);
	  g.drawLine(IDIM/2-i, j+IDIM/2, IDIM/2-i, j+IDIM/2);
	  g.drawLine(IDIM/2-i, IDIM/2-j, IDIM/2-i, IDIM/2-j);
	}
    arrowRight(g, IDIM-1, IDIM/2);
    g.setFont(font);
    if (use_unicode) {
      g.drawString("\u03C9", IDIM/2+6, 13);
      g.drawString("\u03C3", IDIM-13, IDIM/2-6);
    }
    else {
      g.drawString("w", IDIM/2+6, 13);
      g.drawString("ó", IDIM-13, IDIM/2-6);
    }
    g.setColor(r);
    g.drawLine(IDIM/2, 0, IDIM/2, IDIM/2);
    arrowUp(g, IDIM/2, 0);
  } 


  private static void arrowUp(Graphics g, int x, int y) {
    int xa[] = {x, x+2*FK, x-2*FK};
    int ya[] = {y, y+3*FK, y+3*FK};
    g.fillPolygon(xa, ya, 3);
  }


  private static void arrowRight(Graphics g, int x, int y) {
    int xa[] = {x, x-3*FK, x-3*FK};
    int ya[] = {y, y+2*FK, y-2*FK};
    g.fillPolygon(xa, ya, 3);
  }


  public void init() {

    String tmp = null;

    p_name = getParameter("p_name");
    if ( p_name == null )
      p_name = "P: ";
    z_name = getParameter("z_name");
    if ( z_name == null )
      z_name = "Z: ";
    tmp = getParameter("use_unicode");
    if ( tmp != null )
      use_unicode = Boolean.valueOf(tmp).booleanValue();

    pz_bg_image = createImage(IDIM, IDIM);
    pz_bg_g = pz_bg_image.getGraphics();
    pz_bg_g.setColor(Color.white);
    pz_bg_g.fillRect(0, 0, IDIM, IDIM);
    kkpz(pz_bg_g, Color.black, Color.red);

    pz_image = createImage(IDIM, IDIM);
    pz_g = pz_image.getGraphics();

    jw_bg_image = createImage(IDIM, IDIM);
    jw_bg_g = jw_bg_image.getGraphics();
    jw_bg_g.setColor(Color.white);
    jw_bg_g.fillRect(0, 0, IDIM, IDIM);
    jw_bg_g.setColor(Color.black);
    jw_bg_g.drawLine(0, IDIM-1, IDIM-1, IDIM-1);
    jw_bg_g.drawLine(0, 0, 0, IDIM-1);
    for (int i = 0; i < IDIM; i+=IDIM/W_MULT)
      for (int j = 0; j < IDIM; j+=IDIM/MAX_H)
	jw_bg_g.drawLine(i, IDIM-j, i, IDIM-j);
    arrowUp(jw_bg_g, 0, 0);
    arrowRight(jw_bg_g, IDIM-1, IDIM-1);
    jw_bg_g.setFont(font);
    if (use_unicode) {
      jw_bg_g.drawString("|H(j\u03C9)|", 6, 15);
      jw_bg_g.drawString("j\u03C9", IDIM-26, IDIM-5);
    }
    else {
      jw_bg_g.drawString("|H(jw)|", 6, 15);
      jw_bg_g.drawString("jw", IDIM-26, IDIM-5);
    }

    jw_image = createImage(IDIM, IDIM);
    jw_g = jw_image.getGraphics();

    phi_bg_image = createImage(IDIM, IDIM);
    phi_bg_g = phi_bg_image.getGraphics();
    phi_bg_g.setColor(Color.white);
    phi_bg_g.fillRect(0, 0, IDIM, IDIM);
    phi_bg_g.setColor(Color.black);
    phi_bg_g.drawLine(0, IDIM/2, IDIM-1, IDIM/2);
    phi_bg_g.drawLine(0, 0, 0, IDIM-1);
    for (int i = 0; i < IDIM; i+=IDIM/W_MULT)
      for (int j = 0; j < IDIM/2; j+=(IDIM/2)/MAX_PHI)
	{
	  phi_bg_g.drawLine(i, j+IDIM/2, i, j+IDIM/2);
	  phi_bg_g.drawLine(i, IDIM/2-j, i, IDIM/2-j);
	}
    arrowUp(phi_bg_g, 0, 0);
    arrowRight(phi_bg_g, IDIM-1, IDIM/2);
    phi_bg_g.setFont(font);
    if (use_unicode) {
      phi_bg_g.drawString("\u03C6(j\u03C9)", 6, 15);
      phi_bg_g.drawString("j\u03C9", IDIM-26, IDIM/2-5);
    }
    else {
      phi_bg_g.drawString("phi(jw)", 6, 15);
      phi_bg_g.drawString("jw", IDIM-26, IDIM/2-5);
    }

    phi_image = createImage(IDIM, IDIM);
    phi_g = phi_image.getGraphics();

    pole_r = IDIM*4/10;
    pole_i = IDIM/10;
    zero_r = (IDIM*3)/4;
    zero_i = IDIM/4;

    plot_pz();
    plot_jw_phi();

    b_ap = new Button("Plot: |H(s)|");
    add(b_ap);

    resize(IDIM+DI+IDIM+DI+IDIM, IDIM+BUTTON);
  }


  private final void plot_pz() {
    pz_g.drawImage(pz_bg_image, 0, 0, null);

    pz_g.setColor(Color.blue);
    pz_g.drawLine(pole_r-FK, pole_i-FK, pole_r+FK, pole_i+FK);
    pz_g.drawLine(pole_r-FK, pole_i+FK, pole_r+FK, pole_i-FK);
    pz_g.drawLine(pole_r-FK, IDIM-pole_i-FK, pole_r+FK, IDIM-pole_i+FK);
    pz_g.drawLine(pole_r-FK, IDIM-pole_i+FK, pole_r+FK, IDIM-pole_i-FK);

    pz_g.setColor(Color.green);
    pz_g.drawArc(zero_r-FK, zero_i-FK, 2*FK, 2*FK, 0, 360);
    pz_g.drawArc(zero_r-FK, IDIM - zero_i -FK, 2*FK, 2*FK, 0, 360);
  }


  private final void plot_jw_phi() {
    int y_jw_a = IDIM;
    int y_jw = 0;
    int y_phi_a = IDIM/2;
    int y_phi = 0;
    double tmp = 0;

    double r0 = (zero_r-IDIM/2)*MAX_RI/(IDIM/2);
    double i0 = (IDIM/2-zero_i)*MAX_RI/(IDIM/2);
    double r8 = (pole_r-IDIM/2)*MAX_RI/(IDIM/2);
    double i8 = (IDIM/2-pole_i)*MAX_RI/(IDIM/2);

    double r02 = r0 * r0;
    double r04 = r02 * r02;
    double i02 = i0 * i0;
    double i04 = i02 * i02;
    double r82 = r8 * r8;
    double r84 = r82 * r82;
    double i82 = i8 * i8;
    double i84 = i82 * i82;

    jw_g.drawImage(jw_bg_image, 0, 0, null);
    jw_g.setColor(Color.red);
    phi_g.drawImage(phi_bg_image, 0, 0, null);
    phi_g.setColor(Color.red);


    for (int i = 0; i < IDIM; i++)
      {
	double w = i*W_MULT/IDIM;
	double w2 = w * w;
	double w3 = w2 * w;
	double w4 = w2 * w2;

	// |H(jw)|
	double z_jw = r04 + 2*w2*r02 + 2*i02*r02 + w4 - 2*i02*w2 + i04;
	double n_jw = r84 + 2*w2*r82 + 2*i82*r82 + w4 - 2*i82*w2 + i84;
	tmp = Math.sqrt(z_jw/n_jw)*IDIM/MAX_H;
	if ( Double.isInfinite(tmp) )
	  y_jw = -1;
	else
	  {
	    y_jw = IDIM - (int)tmp;
	    if ( y_jw < 0 )
	      y_jw = -1;
	  }
	// phi(jw)
	double re_phi = r02*r82 - w2*r82 + i02*r82 + 4*w2*r0*r8 - w2*r02 + i82*r02 + w4 - i82*w2 - i02*w2 + i02*i82;
	double im_phi = -2*w*r0*r82 + 2*w*r02*r8 - 2*w3*r8 + 2*i02*w*r8 + 2*w3*r0 - 2*i82*w*r0;
	// For HP-UX 9, SunOS 4.1, ... ! atan2(0,0) -> Domain Error!
	if ( ( re_phi == 0 ) && ( im_phi == 0 ) )
	  y_phi = IDIM/2;
	else
	  y_phi = IDIM/2 - (int)(Math.atan2(im_phi, re_phi)*(IDIM/2)/MAX_PHI);

	if ( i == 0 )
	  {
	    y_jw_a = y_jw;
	    y_phi_a = y_phi;
	  }
	else
	  {
	    jw_g.drawLine(i-1, y_jw_a, i, y_jw);
	    y_jw_a = y_jw;
	    phi_g.drawLine(i-1, y_phi_a, i, y_phi);
	    y_phi_a = y_phi;
	  }
      }
  }


  private final boolean track(int x, int y) {
    if ( y > IDIM/2 )
	y = IDIM - y;

    int xp = x - pole_r;
    int yp = y - pole_i;
    int xz = x - zero_r;
    int yz = y - zero_i;

    if ( ( xp*xp + yp*yp ) < ( xz*xz + yz*yz ) )
      {
	pole_r = x;
	pole_i = y;
      }
    else
      {
	zero_r = x;
	zero_i = y;
      }
    repaint();
    return true;
  }


  public boolean mouseUp(Event e, int x, int y) {
    double r0 = (zero_r-IDIM/2)*MAX_RI/(IDIM/2);
    double i0 = (IDIM/2-zero_i)*MAX_RI/(IDIM/2);
    double r8 = (pole_r-IDIM/2)*MAX_RI/(IDIM/2);
    double i8 = (IDIM/2-pole_i)*MAX_RI/(IDIM/2);
    showStatus(p_name+" ("+r8+", "+i8+"), "+z_name+" ("+r0+", "+i0+")");
    return true;
  }


  public boolean mouseDown(Event e, int x, int y) {
    if ( ( x >= IDIM ) || ( y >= IDIM+BUTTON ) || ( x < 0 ) || ( y < BUTTON ) )
      return super.mouseDown(e, x, y);
    return track(x, y-BUTTON);
  }


  public boolean mouseDrag(Event e, int x, int y) {
    if ( ( x >= IDIM ) || ( y >= IDIM+BUTTON ) || ( x < 0 ) || ( y < BUTTON ) )
      return super.mouseDrag(e, x, y);
    return track(x, y-BUTTON);
  }


  public boolean action(Event e, Object o) {
    if (e.target instanceof Button)
      if (e.target.equals(b_ap))
	{
	  Graphics g;
	  if (plotFrame == null)
	    {
	      plotFrame = new PlotFrame("Plot: |H(s)|");
	    }
	  plotFrame.hide = false;
	  plotFrame.show();
	  double r0 = (zero_r-IDIM/2)*MAX_RI/(IDIM/2);
	  double i0 = (IDIM/2-zero_i)*MAX_RI/(IDIM/2);
	  double r8 = (pole_r-IDIM/2)*MAX_RI/(IDIM/2);
	  double i8 = (IDIM/2-pole_i)*MAX_RI/(IDIM/2);
	  if (calcPlot != null)
	    calcPlot.stop();
          // Netscape needs the same ThreadGroup for setPriority()
	  calcPlot = new Thread(threadGroup, plotFrame, "calcPlot");
	  calcPlot.setPriority(Thread.MIN_PRIORITY);
	  showStatus("Plot started!");
	  plotFrame.set(r0, i0, r8, i8);
	  calcPlot.start();
	  return true;
	}
    return super.action(e, o);
  }


  public void update(Graphics g) {
    plot_pz();
    plot_jw_phi();
    paint(g);
  }


  public void paint(Graphics g) {
    g.drawImage(pz_image, 0, BUTTON, null);
    g.drawImage(jw_image, DI+IDIM, BUTTON, null);
    g.drawImage(phi_image, DI+IDIM+DI+IDIM, BUTTON, null);
  }


  public void start() {
    // Netscape needs this (see above)
    threadGroup = Thread.currentThread().getThreadGroup();
    if (plotFrame != null)
      if (!plotFrame.hide && plotFrame.complete)
	plotFrame.show();
  }


  public void stop() {
    if (calcPlot != null)
      calcPlot.stop();
    if (plotFrame != null)
      plotFrame.dispose();
  }


  public void destroy() {
    pz_bg_image.flush();
    pz_image.flush();
    jw_bg_image.flush();
    jw_image.flush();
    phi_bg_image.flush();
    phi_image.flush();
    b_ap = null;
    if (calcPlot != null)
      calcPlot.stop();
    if (plotFrame != null)
      plotFrame.dispose();
    plotFrame = null;
    removeAll();
  }


  public String getAppletInfo()
    {
      return "polezero.class 96/09/28, Copyright (C) 1996 Manfred Thole\n" +
	     "This program is free software; you can redistribute it and/or modify it\n" +
             "under the terms of the GNU General Public License as published by the\n" +
             "Free Software Foundation; either version 2 of the License, or (at your\n" +
             "option) any later version.\n" +
             "\n" +
             "This program is distributed in the hope that it will be useful, but\n" +
             "WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
             "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n" +
             "General Public License for more details.\n" +
             "\n" +
             "You should have received a copy of the GNU General Public License along\n" +
             "with this program; if not, write to the Free Software Foundation, Inc.,\n" +
             "675 Mass Ave, Cambridge, MA 02139, USA.\n";
    }


  public String[][] getParameterInfo() {
    String[][] info = { { "p_name", "String", "Pole: / Pol: / ..." },
			{ "z_name", "String", "Zero: / Nullstelle: / ..." },
			{ "use_unicode", "Boolean", "[true/false] Use unicode characters?" }
		      };
    return info;
  } 

}

//
// PlotFrame
//
class PlotFrame extends Frame implements Runnable {
  private Image pi;
  private Graphics pg;
  double z_r, z_i, p_r, p_i;
  boolean please_stop;
  boolean hide;
  boolean complete;
  Insets pfinset;


  public PlotFrame(String s) {
    super(s);
    // Approx. size, some implementations will otherwise show
    // temporarily a very big frame!
    resize(polezero.IDIM+polezero.DI+polezero.BUTTON+30,
	   polezero.IDIM+30);
    // show it
    show();
    // now insets() gives correct values
    pfinset = insets();
    resize(polezero.IDIM+polezero.DI+polezero.BUTTON+pfinset.left+pfinset.right,
	   polezero.IDIM+pfinset.top+pfinset.bottom);
    setResizable(false);
    please_stop = true;
    hide = true;
    complete = false;
  }


  public void set(double z_r, double z_i, double p_r, double p_i) {
    this.z_r = z_r;
    this.z_i = z_i;
    this.p_r = p_r;
    this.p_i = p_i;
    please_stop = false;
    complete = false;
  }


  public static Color hb2C(double hb) {
    int cl;
    if (hb < 1.0)
      cl = (int)(hb*200.0)+56;
    else
      if (hb < 5)
	{
	  cl = (int)(hb*40.0)+56;
	  cl <<= 8;
	}
      else
	if (hb > 20)
	  cl = 0xFFFFFF;
	else
	  {
	    cl = (int)(hb*10.0)+56;
	    cl <<= 16;
	  }
    return new Color(cl);
  }


  public void run() {
    double si, om;
    double az, an, b, c, hb;
    double t_1, t_2, t_3, t_4, t_5, t_si2, t_om2;
    Color ca;
    Color cl;

    setCursor(Frame.WAIT_CURSOR);
    if ( pi == null )
      {
	pi = createImage(polezero.IDIM+polezero.DI+polezero.BUTTON,
			 polezero.IDIM);
	pg = pi.getGraphics();
	pg.setColor(getBackground());
	pg.fillRect(0, 0,
		    polezero.IDIM+polezero.DI+polezero.BUTTON,
		    polezero.IDIM);
	for (int i = 0; i < polezero.IDIM; i++)
	  {
	    pg.setColor(hb2C((21.0*i)/polezero.IDIM));
	    pg.drawLine(polezero.IDIM+polezero.DI,
			polezero.IDIM-1-i,
			polezero.IDIM+polezero.DI+polezero.BUTTON,
			polezero.IDIM-1-i);
	  }
      }
    t_1 = z_r*z_r+z_i*z_i;
    t_2 = p_r*p_r+p_i*p_i;
    cl = new Color(0);
    ca = cl;
    pg.setColor(cl);
    for (int x = 0; x < polezero.IDIM; x++)
      {
	si = (x-polezero.IDIM/2)*polezero.MAX_RI/(polezero.IDIM/2);
	t_si2 = si*si;
	t_3 = 2*si*z_r;
	t_4 = 2*si*p_r;
	for (int y = 0; y <= polezero.IDIM/2; y++)
	  {
	    om = (polezero.IDIM/2-y)*polezero.MAX_RI/(polezero.IDIM/2);
	    t_om2 = om*om;
	    t_5 = 2*om*si;
	    // |H(s)|
	    b = t_si2 - t_3 - t_om2 + t_1;
	    c = t_5 - 2*om*z_r;
	    az = b*b+c*c;
	    b = t_si2 - t_4 - t_om2 + t_2;
	    c = t_5 - 2*om*p_r;
	    an = b*b+c*c;
	    hb = Math.sqrt(az/an);
	    cl = hb2C(hb);
	    if ( !ca.equals(cl) )
	      {
		pg.setColor(cl);
		ca = cl;
	      }
	    pg.drawLine(x, y, x, y);
	    pg.drawLine(x, polezero.IDIM-y, x, polezero.IDIM-y);
	    Thread.yield();
	  }
	if (please_stop)
	  return;
	repaint();
      }
    polezero.kkpz(pg, Color.white, Color.white);
    setCursor(Frame.DEFAULT_CURSOR);
    repaint();
    complete = true;
  }


  public void update(Graphics g) {
    paint(g);
  }


  public void paint(Graphics g) {
    if (pi != null )
      g.drawImage(pi, pfinset.left, pfinset.top, null);
  }


  public boolean handleEvent(Event event) {
    if (event.id == Event.WINDOW_DESTROY) {
	dispose();
	please_stop = true;
	hide = true;
    }   
    return super.handleEvent(event);
  }

}
