/* ===================================================================== */
/** Interactive aircraft performance chart.
    @author Emmanuel Gustin, gustin@uia.ua.ac.be
    @version 1.1 of 17 October 1997
*/
/* ===================================================================== */
import java.io.*;
import java.util.*;
import java.awt.*;
import java.applet.Applet;
import java.net.*;
import PlotCanvas;
import TwoSliders;
import AuxMath;

/* ===================================================================== */
/** Interactive chart of top speeds
*/
/* ===================================================================== */
public class FighterSpeed extends Applet {

  // --- status variables ---
  private boolean initialised = false;
  private boolean dataread = false;
  // --- GUI members ---
  private PlotCanvas can = null;
  private TwoSliders tws = null;
  private TextArea   cmt = null; 
  private Label      l1  = null,  l2   = null, l3   = null; 
  private TextField  flt1 = null, flt2 = null, flt3 = null; 
  // --- data members ---
  private Vector speeds = null;
  private Vector current = null;
  private int minyear = 1900;
  private int maxyear = 1990;
  private double minspeed =     0.0;
  private double maxspeed =  3500.0;
  private double minalt   =     0.0;
  private double maxalt   = 25000.0;

  // --- init -------------------------------------------------------
  // Is used to set up the screen elements and read the data file. 
  public void init() {
    int i;
    // --- prepare screen elements ---
    if (! initialised) {
      SetUpScreen();
      initialised = true;
    }
    // --- Now read data ---
    if (! dataread) {
      speeds = new Vector();
      i = ReadData(speeds, true);
      showStatus("Read " + i + " records");
      dataread = true;
      // prepare scales for first plotting
      current = YearFilter(speeds);    
      current = StringFilter(current);
      Rescale(current);
    }
  }

  // --- set up screen ---------------------------------------------
  // Puts GUI elements in the applet
  public void SetUpScreen() {
    GridBagLayout gbl;
    GridBagConstraints cns;
    Insets ins;
    int i;
    // --- background ---
    setBackground(Color.lightGray);
    // --- set Layout ---
    ins = new Insets(4, 4, 4, 4);
    gbl = new GridBagLayout();
    cns = new GridBagConstraints();
    setLayout(gbl);
    // --- set up windows ---
    // 0. init
    cns.insets    = ins;
    cns.fill      = cns.BOTH;
    // 1. canvas
    cns.weightx   = 0.9;
    cns.weighty   = 1.0;
    cns.gridx     = 0;
    cns.gridy     = 0;
    cns.gridwidth = cns.REMAINDER;
    can = new PlotCanvas();
    can.resize(500, 250);
    gbl.setConstraints(can, cns);
    add(can);
    // 2. Sliders
    tws = new TwoSliders(1900, 1990, 8, 1.0);
    cns.gridx   = 0;
    cns.weightx = 0.0;
    cns.weighty = 0.0;
    cns.gridy   = 1;
    gbl.setConstraints(tws, cns);
    add(tws);
    // 3. Comment field
    cmt = new TextArea(3, 35);
    cmt.setEditable(false);
    cns.gridx     = 0;
    cns.gridy     = 2;
    cns.weightx   = 0.6;
    cns.gridwidth = 1;
    cns.gridheight = cns.REMAINDER;
    gbl.setConstraints(cmt, cns);
    add(cmt);    
    // 4. Labels
    l1 = new Label("Name : ", Label.RIGHT);
    cns.gridx   = 1;
    cns.gridy   = 2;
    cns.gridheight = 1;
    cns.weightx   = 0.0;
    gbl.setConstraints(l1, cns);
    add(l1);
    l2 = new Label("Country : ", Label.RIGHT);
    cns.gridx   = 1;
    cns.gridy   = 3;
    gbl.setConstraints(l2, cns);
    add(l2);
    l3 = new Label("Engine : ", Label.RIGHT);
    cns.gridx   = 1;
    cns.gridy   = 4;
    gbl.setConstraints(l3, cns);
    add(l3);
    // 5. Filter fields
    flt1 = new TextField(20);
    cns.gridx   = 2;
    cns.gridy   = 2;
    cns.gridheight = 1;
    cns.weightx   = 0.4;
    gbl.setConstraints(flt1, cns);
    add(flt1);
    flt2 = new TextField(20);
    cns.gridx   = 2;
    cns.gridy   = 3;
    gbl.setConstraints(flt2, cns);
    add(flt2);
    flt3 = new TextField(20);
    cns.gridx   = 2;
    cns.gridy   = 4;
    gbl.setConstraints(flt3, cns);
    add(flt3);
    // --- show ---
    resize(600, 480);
    validate();
  }

  // --- Read Data -----------------------------------------------------
  // Reads data from a data file. Stores all of them in the vec Vector. 
  public int ReadData(Vector vec, boolean FromNet) {
    TopSpeed s;
    FileInputStream fin;
    DataInputStream din;
    URL uin;
    String fname;
    boolean ok;
    int count;
    // read from net. Source is Applet paramater
    if (FromNet) {
      fname = getParameter("Source");
      try {
        uin = new URL(getDocumentBase(), fname);
        din = new DataInputStream(uin.openStream());
      }
      catch(MalformedURLException e) {return 0;}
      catch(IOException e) {return 0;}
    // read from local disk. 
    } else {
      try {
        fin = new FileInputStream("FighterPerf.data");
        din = new DataInputStream(fin);
      }
      catch(FileNotFoundException e) {return 0;}
      catch(IOException e) {return 0;}
    }
    // --- read records ---
    ok = true;
    while (ok) {
      s = new TopSpeed();
      ok = s.read(din);
      if (ok) {
        vec.addElement(s);
      }
    }
    return vec.size();
  }

  // --- start -----------------------------------------------------
  public void start() {
  }

  // --- paint ------------------------------------------------------
  public void paint(Graphics g) {
    Graphics gc;
    TopSpeed ts;
    double xtick, ytick;
    int xsub, ysub;
    int i;
    
    if (initialised) {
      // --- set up PlotCanvas ---
      can.clear();
      can.SetBorder(can.DEFAULT | can.GRID, can.DEFAULT | can.GRID);
      can.SetViewport(0.10, 0.95, 0.05, 0.90);
      can.SetWindow(minspeed, maxspeed, minalt, maxalt);
      can.SetTicks(4.0, 3.0);
      can.SetColor(Color.black);
      can.SetWidth(3);
      can.PlotBorder();
      // --- plot data ---
      if (dataread) {
        can.SetColor(Color.red);
        can.SetStyle(can.SYMBOLS);
        can.SetSymbol('\u2756', new Font("sansserif", Font.PLAIN, 10));
        can.SetWidth(1);
        gc = can.getGraphics();
        for (i = 0; i < current.size(); i++) {
          ts = ((TopSpeed) current.elementAt(i));
          ts.PlotCanvas(can, gc);
        }
      }
    }
    // --- validate ---
    validate();
  }

  // --- Event Handler ----------------------------------------------
  // Handled events:
  //  WINDOW_EXPOSE and WINDOW_DESTROY, of course.
  //  ACTION_EVENT returned by TwoSliders ruler.
  //  ACTION_EVENT returend by PlotCanvas in ZOOM, UNZOOM
  //  MOUSE_DOWN in PlotCanvas
  //  KEYPRESS '\n' to replot with matching
  // These events modify the list of elements that are to be
  // plotted by calling the filters.
  //
  public boolean handleEvent(Event evt) {
    TopSpeed ref;
    switch (evt.id) {
    case Event.WINDOW_EXPOSE:
      repaint(250);
      return(true);
    case Event.WINDOW_DESTROY:
      System.exit(0);
      return(true);
    case Event.ACTION_EVENT:
      if (evt.target == can) {
        if (((String) evt.arg).equals("ZOOM")) {
          minspeed = can.wx0;
          maxspeed = can.wx1;
          minalt   = can.wy0;
          maxalt   = can.wy1;
          current = RangeFilter(current);
          repaint();
          return(true);
        } else if (((String) evt.arg).equals("UNZOOM")) {
          current = YearFilter(speeds);    
          current = StringFilter(current);
          Rescale(current);
          repaint();
          return(true);
        }
      } else if (evt.target == tws) {
        minyear = (int) Math.round(tws.getLeft());    
        maxyear = (int) Math.round(tws.getRight());    
        current = YearFilter(speeds);    
        current = StringFilter(current);
        Rescale(current);
        repaint(250);
        return(true);
      }
    case Event.MOUSE_DOWN:
      if (evt.target == can) {
        ref = Closest(evt.x, evt.y);
        cmt.setText(ref.getText());
      }
    case Event.KEY_PRESS:
      if (evt.key == '\n') {
        current = YearFilter(speeds);    
        current = StringFilter(current);
        Rescale(current);
        repaint(50);
      }
    }
    return(false);
  }

  // --- Filter for year --------------------------------------------
  // From a vector, generates a new vector containing those elements
  // that fall between minyear and maxyear. 
  public Vector YearFilter(Vector speeds) {
    Vector current = null;
    TopSpeed ts;
    int i;
    if (dataread) {
      // --- reset ---
      current = new Vector();
      // --- calculate limits. Add found elements to new list ---
      minalt = 0.0;
      for (i = 0; i < speeds.size(); i++) {
        ts = ((TopSpeed) speeds.elementAt(i));
        if ((minyear <= ts.year) && (ts.year <= maxyear)) {
          current.addElement(ts);
        }
      }
    }
    return(current);
  }

  // --- Filter for range -------------------------------------------
  // From a vector, generates a new vector containing those elements
  // that fall between the plotting limits
  public Vector RangeFilter(Vector speeds) {
    Vector current = null;
    TopSpeed ts;
    int i;
    if (dataread) {
      // --- reset ---
      current = new Vector();
      // --- calculate limits. Add found elements to new list ---
      for (i = 0; i < speeds.size(); i++) {
        ts = ((TopSpeed) speeds.elementAt(i));
        if ((minspeed <= ts.speed) && (ts.speed <= maxspeed)
              && (minalt <= ts.level) && (ts.level <= maxalt)) {
          current.addElement(ts);
        }
      }
    }
    return(current);
  }

  // --- Seek closest element on the canvas -------------------------
  // Only the current array is sought.
  public TopSpeed Closest(int x, int y) {
    TopSpeed ref, ts;
    double sp, le;
    double r, s, dist, t;
    int i;
    // --- first convert ---
    sp = can.toWorldX(x);
    le = can.toWorldY(y);
    dist = 0.0;
    ref = null;
    // --- next seek closest ---
    for (i = 0; i < current.size(); i++) {
      ts = ((TopSpeed) current.elementAt(i));
      r = (sp - ts.speed) / 1000.0;
      s = (le - ts.level) / 10000.0;
      t = r * r + s * s;
      if ((i == 0) || (t < dist)) {
        dist = t;
        ref = ts;
      }
    }
    // --- return ---
    return(ref);
  }

  // --- Filter with the filter strings ------------------------------
  // From a vector, generates a new vector containing those elements
  // that match one of the filter strings on each line. 
  public Vector StringFilter(Vector speeds) {
    Vector current = null;
    TokenMatcher Mname, Mcountry, Mengine;
    TopSpeed ts;
    int i;
    // --- first get the token strings ---
    Mname    = new TokenMatcher(flt1.getText());
    Mcountry = new TokenMatcher(flt2.getText());
    Mengine  = new TokenMatcher(flt3.getText());
    // --- now match ---
    if (dataread) {
      // --- reset ---
      current = new Vector();
      // --- calculate limits. Add found elements to new list ---
      minalt = 0.0;
      for (i = 0; i < speeds.size(); i++) {
        ts = ((TopSpeed) speeds.elementAt(i));
        if (Mname.matches(ts.name)
                    && Mcountry.matches(ts.getCountry())
                    && Mengine.matches(ts.engine)) {
          current.addElement(ts);
        }
      }
    }
    return(current);
  }

  // --- rescale ---------------------------------------------------------
  // Calculate min and max to plot. 
  public void Rescale(Vector current) {
    TopSpeed ts;
    int i;
    boolean fnd = false;
    // --- now match ---
    if (dataread) {
      // --- calculate limits. Add found elements to new list ---
      minalt = 0.0;
      for (i = 0; i < current.size(); i++) {
        ts = ((TopSpeed) current.elementAt(i));
        if (fnd) {
          if (maxalt   < ts.level) {maxalt = ts.level;}
          // if (minalt   > ts.level) {minalt = ts.level;}
          if (maxspeed < ts.speed) {maxspeed = ts.speed;}
          if (minspeed > ts.speed) {minspeed = ts.speed;}
        } else {
          maxalt   = ts.level;
          // minalt   = ts.level;
          maxspeed = ts.speed;
          minspeed = ts.speed;
          fnd      = true;
        }
      }
    }
  }

}

/* ===================================================================== */
/** Represents an aircraft with its top speed at a given level. Has 
    reading and plotting procedures, and a few helper methods.
*/
/* ===================================================================== */
class TopSpeed {

  // --- country flag constants ---
  public final int COUNTRY_INTERNATIONAL =  1;
  public final int COUNTRY_BRITAIN       =  2;
  public final int COUNTRY_CZECH         =  3;
  public final int COUNTRY_FRANCE        =  4;
  public final int COUNTRY_GERMANY       =  5;
  public final int COUNTRY_ITALY         =  6;
  public final int COUNTRY_JAPAN         =  7;
  public final int COUNTRY_NETHERLANDS   =  8;
  public final int COUNTRY_POLAND        =  9;
  public final int COUNTRY_SWEDEN        = 10;
  public final int COUNTRY_USA           = 11;
  public final int COUNTRY_USSR          = 12;
  public final int COUNTRY_AUSTRIA       = 13;
  public final int COUNTRY_CANADA        = 14;
  public final int COUNTRY_AUSTRALIA     = 15;
  public final int COUNTRY_ISRAEL        = 16;
  public final int COUNTRY_FINLAND       = 17;
  
  // --- data members -----------------------------------------------
  protected String name;
  protected String engine;
  protected int year;
  protected int country;
  protected double speed;
  protected double level;

  // --- read a member from a stream --------------------------------
  public boolean read(DataInputStream di) {
    String str;
    StringTokenizer stk;
    int k, i, j;
    char c;
    // --- try to read ---
    try {
      // first loop to skip over comment lines
      do  {
        str = di.readLine();
        if (str.length() == 0) {return(false);}
        c = str.charAt(0);
      } while (c == '#');
      // If ok, assign it as name of this record
      name = str;
      // Read descriptive line
      str = di.readLine();
      if (str == null) {return(false);}
      stk = new StringTokenizer(str);
      if (stk.countTokens() < 4) {return(false);}
      year    = Integer.valueOf(stk.nextToken()).intValue();
      speed   = Double.valueOf(stk.nextToken()).doubleValue();
      level   = Double.valueOf(stk.nextToken()).doubleValue();
      country = Country2Flag(stk.nextToken());
      // Read engine line
      str = di.readLine();
      if (str == null) {return(false);}
      engine = str.trim();
      // done
      return(true);
    }
    // --- catch blocks ---
    // for the moment, just stop reading the record. No clean up.
    // Caller should not try to store the results, and they will
    // go away... 
    catch(IOException e) {
      System.out.println("  ! IO " + e);
      return(false);
    }
    catch(NumberFormatException e) {
      System.out.println("  ! NumberFormat " + e);
      return(false);
    }
    catch(StringIndexOutOfBoundsException e) {
      System.out.println("  ! StringIndexOutOfBounds " + e);
      return(false);
    }
  }

  // --- plot on canvas -----------------------------
  public void PlotCanvas(PlotCanvas can, Graphics g) {
    int ix, iy;
    double si = 12.0;
    // --- get point ---
    ix = can.toPhysX(speed);
    iy = can.toPhysY(level);
    // --- draw ---
    switch (country) {
    case COUNTRY_BRITAIN :     DrawHelper.drawBritain(g, ix, iy, si); break;
    case COUNTRY_FRANCE :      DrawHelper.drawFrance(g, ix, iy, si);  break;
    case COUNTRY_GERMANY :     DrawHelper.drawGermany(g, ix, iy, si); break;
    case COUNTRY_ITALY :       DrawHelper.drawItaly(g, ix, iy, si);   break;
    case COUNTRY_JAPAN :       DrawHelper.drawJapan(g, ix, iy, si);   break;
    case COUNTRY_POLAND :      DrawHelper.drawPoland(g, ix, iy, si);  break;
    case COUNTRY_USSR :        DrawHelper.drawUSSR(g, ix, iy, si);    break;
    case COUNTRY_USA :         DrawHelper.drawUSA(g, ix, iy, si);     break;
    case COUNTRY_NETHERLANDS : DrawHelper.drawNetherlands(g, ix, iy, si); break;
    case COUNTRY_CZECH :       DrawHelper.drawCzech(g, ix, iy, si);   break;  
    case COUNTRY_SWEDEN :      DrawHelper.drawSweden(g, ix, iy, si);  break;  
    case COUNTRY_AUSTRIA :     DrawHelper.drawAustria(g, ix, iy, si); break;  
    case COUNTRY_INTERNATIONAL :
    case COUNTRY_CANADA :
    case COUNTRY_AUSTRALIA :
    case COUNTRY_ISRAEL :
    case COUNTRY_FINLAND :
    default :
      DrawHelper.drawUnknown(g, ix, iy, si);
      break;
    }
  }

  // --- gettext ---------------------------------------------
  public String getText() {
    String str =   name + "    ("+ getCountry() + ", "+ year + ")\n"
                 + speed + "km/h at " + level + "m\n"
                 + engine;
    return(str);                 
  }                    

  // --- country to flag -----------------------------------------
  public int Country2Flag(String str) {
    int flag;
    if (str.equals("International")) {         flag = COUNTRY_INTERNATIONAL;
    } else if (str.equals("Austria")) {        flag = COUNTRY_AUSTRIA;
    } else if (str.equals("Britain")) {        flag = COUNTRY_BRITAIN;
    } else if (str.equals("Czechoslovakia")) { flag = COUNTRY_CZECH;
    } else if (str.equals("France")) {         flag = COUNTRY_FRANCE;
    } else if (str.equals("Germany")) {        flag = COUNTRY_GERMANY;
    } else if (str.equals("Italy")) {          flag = COUNTRY_ITALY;
    } else if (str.equals("Japan")) {          flag = COUNTRY_JAPAN;
    } else if (str.equals("Netherlands")) {    flag = COUNTRY_NETHERLANDS;
    } else if (str.equals("Poland")) {         flag = COUNTRY_POLAND;
    } else if (str.equals("Sweden")) {         flag = COUNTRY_SWEDEN;
    } else if (str.equals("USA")) {            flag = COUNTRY_USA;
    } else if (str.equals("USSR")) {           flag = COUNTRY_USSR;
    } else if (str.equals("Israel")) {         flag = COUNTRY_ISRAEL;
    } else if (str.equals("Australia")) {      flag = COUNTRY_AUSTRALIA;
    } else if (str.equals("Canada")) {         flag = COUNTRY_CANADA;
    } else if (str.equals("Finland")) {        flag = COUNTRY_FINLAND;
    } else {
      flag = 0;
    }
    return(flag);
  }

  // --- country to flag -----------------------------------------
  public String getCountry() {
    switch (country) {
    case COUNTRY_INTERNATIONAL : return("International");
    case COUNTRY_AUSTRIA :       return("Austria");
    case COUNTRY_BRITAIN :       return("Britain");
    case COUNTRY_CZECH :         return("Czechoslovakia");
    case COUNTRY_FRANCE :        return("France");
    case COUNTRY_GERMANY :       return("Germany");
    case COUNTRY_ITALY :         return("Italy");
    case COUNTRY_JAPAN :         return("Japan");
    case COUNTRY_NETHERLANDS :   return("The Netherlands");
    case COUNTRY_POLAND :        return("Poland");
    case COUNTRY_SWEDEN :        return("Sweden");
    case COUNTRY_USA :           return("USA");
    case COUNTRY_USSR :          return("USSR");
    case COUNTRY_CANADA :        return("Canada");
    case COUNTRY_AUSTRALIA :     return("Australia");
    case COUNTRY_ISRAEL :        return("Israel");
    case COUNTRY_FINLAND :       return("Finland");
    default :                    break;
    }
    return("Unknown");
  }

}

/* ===================================================================== */
/** A class to draw nationality signs. Static members only. 
*/
/* ===================================================================== */
abstract class DrawHelper {

  // --- Unknown ----------------------------------------------------
  static void drawUnknown(Graphics g, int x, int y, double size) {
    int r1 = (int) Math.round(size / 2.0);
    int r2 = (int) Math.round(size);
    int s1 = r1 / 2;
    int s2 = r2 / 2;
    g.setColor(Color.black);
    g.fillOval(x - s2, y - s2, r2+1, r2+1);
    g.setColor(Color.white);
    g.fillOval(x - s1, y - s1, r1+1, r1+1);
  }

  // --- German cross -----------------------------------------------
  static void drawGermany(Graphics g, int x, int y, double size) {
    int vx[] = new int[12];
    int vy[] = new int[12];
    int a = (int) Math.round(size / 2.0);
    int b = (int) Math.round(size / 8.0);
    vx[ 0] = x-b; vy[ 0] = y+b;   vx[ 6] = x+b; vy[ 6] = y-b;    
    vx[ 1] = x-b; vy[ 1] = y+a;   vx[ 7] = x+b; vy[ 7] = y-a;    
    vx[ 2] = x+b; vy[ 2] = y+a;   vx[ 8] = x-b; vy[ 8] = y-a;    
    vx[ 3] = x+b; vy[ 3] = y+b;   vx[ 9] = x-b; vy[ 9] = y-b;    
    vx[ 4] = x+a; vy[ 4] = y+b;   vx[10] = x-a; vy[10] = y-b;    
    vx[ 5] = x+a; vy[ 5] = y-b;   vx[11] = x-a; vy[11] = y+b;
    g.setColor(Color.black);
    g.fillPolygon(vx, vy, 12);
  }

  // --- Japanese Sun -----------------------------------------------
  static void drawJapan(Graphics g, int x, int y, double size) {
    int r = (int) Math.round(size);
    int s = r / 2;
    g.setColor(Color.white);
    g.fillOval(x-s-1, y-s-1, r+3, r+3);
    g.setColor(Color.red);
    g.fillOval(x-s, y-s, r+1, r+1);
  }

  // --- British marking --------------------------------------------
  static void drawBritain(Graphics g, int x, int y, double size) {
    int r1 = (int) Math.round(size / 2.0);
    int r2 = (int) Math.round(size);
    int s1 = r1 / 2;
    int s2 = r2 / 2;
    g.setColor(Color.blue);
    g.fillOval(x-s2, y-s2, r2+1, r2+1);
    g.setColor(Color.red);
    g.fillOval(x-s1, y-s1, r1+1, r1+1);
  }

  // --- French marking ---------------------------------------------
  static void drawFrance(Graphics g, int x, int y, double size) {
    int r1 = (int) Math.round(size / 3.0);
    int r2 = (int) Math.round(2.0 * size / 3.0);
    int r3 = (int) Math.round(size);
    int s1 = r1 / 2;
    int s2 = r2 / 2;
    int s3 = r3 / 2;
    g.setColor(Color.red);
    g.fillOval(x-s3, y-s3, r3+1, r3+1);
    g.setColor(Color.white);
    g.fillOval(x-s2, y-s2, r2+1, r2+1);
    g.setColor(Color.blue);
    g.fillOval(x-s1, y-s1, r1+1, r1+1);
  }

  // --- Italian marking --------------------------------------------
  static void drawItaly(Graphics g, int x, int y, double size) {
    int r1 = (int) Math.round(size / 3.0);
    int r2 = (int) Math.round(2.0 * size / 3.0);
    int r3 = (int) Math.round(size);
    int s1 = r1 / 2;
    int s2 = r2 / 2;
    int s3 = r3 / 2;
    g.setColor(Color.red);
    g.fillOval(x-s3, y-s3, r3+1, r3+1);
    g.setColor(Color.white);
    g.fillOval(x-s2, y-s2, r2+1, r2+1);
    g.setColor(Color.green);
    g.fillOval(x-s1, y-s1, r1+1, r1+1);
  }

  // --- Polish marking ---------------------------------------------
  static void drawPoland(Graphics g, int x, int y, double size) {
    int r = (int) Math.round(size / 2.0);
    int s = (int) Math.round(size);
    g.setColor(Color.red);
    g.fillRect(x-r, y-r, s, s);
    g.setColor(Color.white);
    g.fillRect(x-r, y, r, r);
    g.fillRect(x, y-r, r, r);
  }

  // --- Red Star --------------------------------------------
  static void drawUSSR(Graphics g, int ox, int oy, double size) {
    double r = 1.5 * size / 2.0;
    double R = r * 0.38196601;
    int ir   = (int) Math.round(r);
    int iR   = (int) Math.round(R);
    int ar   = (int) Math.round( 0.9511 * r);
    int aR   = (int) Math.round( 0.9511 * R);
    int cr   = (int) Math.round( 0.5878 * r);
    int cR   = (int) Math.round( 0.5878 * R);
    int br   = (int) Math.round( 0.3090 * r);
    int bR   = (int) Math.round( 0.3090 * R);
    int dr   = (int) Math.round( 0.8090 * r);
    int dR   = (int) Math.round( 0.8090 * R);
    int x[] = new int[10];
    int y[] = new int[10];
    x[0] = ox + ar;  x[1] = ox + cR;  x[2] = ox;       x[3] = ox - cR;
    x[4] = ox - ar;  x[5] = ox - aR;  x[6] = ox - cr;  x[7] = ox;
    x[8] = ox + cr;  x[9] = ox + aR;  y[0] = oy - br;  y[1] = oy - dR;
    y[2] = oy - ir;  y[3] = oy - dR;  y[4] = oy - br;  y[5] = oy + bR;
    y[6] = oy + dr;  y[7] = oy + iR;  y[8] = oy + dr;  y[9] = oy + bR;
    g.setColor(Color.red);
    g.fillPolygon(x, y, 10);
  }

  // --- US Marking ------------------------------------------
  static void drawUSA(Graphics g, int ox, int oy, double size) {
    double r  = 1.2 * size / 2.0;
    double R  = r * 0.38196601;
    int x[] = new int[10];
    int y[] = new int[10];
    // --- bar ---
    int r3 = (int) Math.round(2.0 * r);
    int s3 = (int) Math.round(0.40 * r);
    int t3 = 2 * s3 / 5;
    g.setColor(Color.blue);
    g.fillRect(ox-r3,    oy-s3,      2*r3+1,      2*s3+1);
    g.setColor(Color.white);
    g.fillRect(ox-r3+t3, oy-s3+t3,   2*r3-2*t3+1, 2*s3-2*t3+1);
    g.setColor(Color.red);
    g.fillRect(ox-r3+t3, oy-s3+2*t3, 2*r3-2*t3+1, 2*s3-4*t3+1);
    // -- circle ---
    int r2 = (int) Math.round(r);
    g.setColor(Color.blue);
    g.fillOval(ox-r2, oy-r2, 2*r2+1, 2*r2+1);
    // star
    int ir   = (int) Math.round(r);
    int iR   = (int) Math.round(R);
    int ar   = (int) Math.round( 0.9511 * r);
    int aR   = (int) Math.round( 0.9511 * R);
    int cr   = (int) Math.round( 0.5878 * r);
    int cR   = (int) Math.round( 0.5878 * R);
    int br   = (int) Math.round( 0.3090 * r);
    int bR   = (int) Math.round( 0.3090 * R);
    int dr   = (int) Math.round( 0.8090 * r);
    int dR   = (int) Math.round( 0.8090 * R);
    x[0] = ox + ar;  x[1] = ox + cR;  x[2] = ox;       x[3] = ox - cR;
    x[4] = ox - ar;  x[5] = ox - aR;  x[6] = ox - cr;  x[7] = ox;
    x[8] = ox + cr;  x[9] = ox + aR;  y[0] = oy - br;  y[1] = oy - dR;
    y[2] = oy - ir;  y[3] = oy - dR;  y[4] = oy - br;  y[5] = oy + bR;
    y[6] = oy + dr;  y[7] = oy + iR;  y[8] = oy + dr;  y[9] = oy + bR;
    g.setColor(Color.white);
    g.fillPolygon(x, y, 10);
  }

  // --- Dutch Marking ---------------------------------------
  static void drawNetherlands(Graphics g, int x, int y, double size) {
    int r3 = (int) Math.round(size);
    int s3 = r3 / 2;
    int r2 = r3 / 4;
    int s2 = r2 / 2;
    g.setColor(Color.blue);
    g.fillArc(x-s3, y-s3, r3+1, r3+1,  90, 120);
    g.setColor(Color.white);
    g.fillArc(x-s3, y-s3, r3+1, r3+1, 210, 120);
    g.setColor(Color.red);
    g.fillArc(x-s3, y-s3, r3+1, r3+1, 330, 120);
    g.setColor(Color.red);
    g.fillOval(x-s2, y-s2, r2+1, r2+1);
  }

  // --- Dutch Marking ---------------------------------------
  static void drawCzech(Graphics g, int x, int y, double size) {
    int r3 = (int) Math.round(size);
    int s3 = r3 / 2;
    g.setColor(Color.white);
    g.fillArc(x-s3, y-s3, r3+1, r3+1, 0, 120);
    g.setColor(Color.blue);
    g.fillArc(x-s3, y-s3, r3+1, r3+1, 120, 120);
    g.setColor(Color.red);
    g.fillArc(x-s3, y-s3, r3+1, r3+1, 240, 120);
  }

  // --- Swedish Marking -------------------------------------
  // Squares instead of little crowns...
  static void drawSweden(Graphics g, int x, int y, double size) {
    int r = (int) Math.round(size);
    int s = r / 2;
    int p = r / 4;
    int q = r / 6;
    // circles
    g.setColor(Color.yellow);
    g.fillOval(x-s-2, y-s-2, r+5, r+5);
    g.setColor(Color.blue);
    g.fillOval(x-s, y-s, r+1, r+1);
    //crowns
    g.setColor(Color.yellow);
    g.fillRect(x-p-q, y-q, p, q);
    g.fillRect(x+q, y-q, p, q);
    g.fillRect(x - p / 2, y+2*q, p, q);
  }

  // --- Austrian Marking -------------------------------------------
  static void drawAustria(Graphics g, int x, int y, double size) {
    int r = (int) Math.round(size);
    int s = r / 2;
    int q = (int) Math.round(0.866025 * r);
    int vx[] = new int[3];
    int vy[] = new int[3];
    // circle 
    g.setColor(Color.red);
    g.fillOval(x-s, y-s, r+1, r+1);
    // triangle
    vx[0] = x+q;   vy[0] = y-s;
    vx[1] = x-q;   vy[1] = y-s;
    vx[2] = x;     vy[2] = y+r;
    g.fillPolygon(vx,vy,3);
  }

}

/* ===================================================================== */
/** Class to separate tokens in a string and put them in an array,
    then compare with a string to see whether the String contains
    one of the tokens.
*/
/* ===================================================================== */

class TokenMatcher {

  String tokens[];
  int countTokens;

  // --- constructor ---------------------------------------------------
  public TokenMatcher(String s) {
    StringTokenizer st = new StringTokenizer(s);
    int i;
    // --- init ---
    countTokens = st.countTokens();
    // --- loop ---
    if (countTokens != 0) {
      tokens = new String[countTokens];
      for (i = 0; i < countTokens; i++) {
        tokens[i] = st.nextToken();
      }
    }
  }

  // --- matcher -------------------------------------------------------
  public boolean matches(String s) {
    int i = 0;
    boolean b = false;
    if (countTokens == 0) return(true);
    while ((! b) && (i < countTokens)){
      b = (s.indexOf(tokens[i]) != -1);
      i++;
    }
    return(b);
  }

}

