import java.applet.Applet;
import java.awt.*;
import java.util.*;

// -----------------------------------------------------------------
// Making MapDrawable an abstract class rather than an interface permits
// methods and variables to be inherited
abstract class MapDrawable {
  int x1, y1;
  Color c;
  boolean fill=false;
  public abstract void draw(Graphics g);
}

// -----------------------------------------------------------------
class MapLine extends MapDrawable {
  int x2, y2;
  public MapLine(Color c, int x1, int y1, int x2, int y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
    this.c = c;
  }
  public void draw(Graphics g) {
    g.setColor(c);
    g.drawLine(x1, y1, x2, y2);
  }
}

// -----------------------------------------------------------------
class MapRect extends MapDrawable {
  int width, height;
  public MapRect(Color c, boolean fill, int x1, int y1, int x2, int y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.width = x2 - x1;
    this.height = y2 - y1;
    this.c = c;
    this.fill = fill;
  }
  public void draw(Graphics g) {
    g.setColor(c);
    if (fill)
      g.fillRect(x1, y1, width, height);
    else
      g.drawRect(x1, y1, width, height);
  }
}

// -----------------------------------------------------------------
class MapPoly extends MapDrawable {
  Vector pts;
  boolean close = false;
  public MapPoly(Color c, Vector v, boolean close, boolean fill) {
    this.c = c;
    this.pts = v;
    this.close = close;
    this.fill = fill;
  }
  public void draw(Graphics g) {
    g.setColor(c);
    // We need to convert the Vector of points to a pair of
    // arrays holding the coordinates
    int [] x = new int [pts.size() / 2];
    int [] y = new int [pts.size() / 2];
    for(int i=0; i<pts.size(); i+=2) {
      x[i/2] = ((Integer) pts.elementAt(i)).intValue();
      y[i/2] = ((Integer) pts.elementAt(i+1)).intValue();
    }
    if (close) {
      if (fill) {
        g.fillPolygon(x,y,x.length);
      } else {
        g.drawPolygon(x,y,x.length);
      }
    } else {
      g.drawPolyline(x,y,x.length);
    }
  }
}

// -----------------------------------------------------------------
class MapText extends MapDrawable {
  String s = "";
  Font f;
  public MapText(Color c, int x1, int y1, String s, Font f) {
    this.x1 = x1;
    this.y1 = y1;
    this.c = c;
    this.s = s;
    this.f = f;
  }
  public void draw(Graphics g) {
    g.setColor(c);
    g.setFont(f);
    g.drawString(s, x1, y1);
  }
}

// -----------------------------------------------------------------
class MapCircle extends MapDrawable {
  int radius = 5;
  public MapCircle(Color c, boolean fill, int x1, int y1, int r) {
    this.x1 = x1;
    this.y1 = y1;
    this.c = c;
    this.fill = fill;
    this.radius = r;
  }
  public void draw(Graphics g) {
    g.setColor(c);
    if (fill)
      g.fillArc(x1, y1, radius, radius, 0, 359);
    else
      g.drawArc(x1, y1, radius, radius, 0, 359);
  }
}


// -----------------------------------------------------------------
public class MapZest extends Applet {
  String map = "";
  Vector list = null;

  public void init () {
    list = new Vector();
    setBackground(Color.white);
    map = getParameter("map");
    if (map != null) {
      StringTokenizer st = new StringTokenizer(map, ":");
      while (st.hasMoreTokens()) {
        String operation = st.nextToken();
        MapDrawable drawable = parseOperation(operation);
        list.addElement(drawable);
      }
    }
  }

  // Very simple paint method
  public void paint (Graphics g) {
    for(int i=0; i<list.size(); i++) {
      MapDrawable x = (MapDrawable) list.elementAt(i);
      x.draw(g);
    }
  }

  
  // -----------------------------------------------------------------
  // Admittedly this is an ugly method.  In addition to being rather
  // long and unwieldy it assumes the input is valid.  A better approach
  // would be to throws exceptions if the input is not parsable.
  // To simplify the switch logic we could have seperate <PARAM> tags
  // for each type of drawable (the applet would know how many of each type
  // using defined parameter tags).  However, we would lose any order
  // information between types, and that may be important.  For example, the
  // name of a lake (text) should appear _on top_ of the lake itself (polygon)

  public static MapDrawable parseOperation (String operation) {
      char c = operation.charAt(0);  // 1st character
      int x1=0, y1=0, x2=0, y2=0;
      int ptsize=12;
      int radius = 5;
      String label = ""; 
      Color color = getMapColor(operation.charAt(1));  // 2nd character

      // Tokenize individual string by commas.  It is always the case
      // that the first two values are an x,y location
      StringTokenizer st2 = new StringTokenizer(operation.substring(2), ",");
      x1 = Integer.parseInt(st2.nextToken());  
      y1 = Integer.parseInt(st2.nextToken());

      switch (c) {
        case 'R':
        case 'r':
          x2 = Integer.parseInt(st2.nextToken());
          y2 = Integer.parseInt(st2.nextToken());
          if (c == 'R') return new MapRect(color, false, x1, y1, x2, y2);
          if (c == 'r') return new MapRect(color, true, x1, y1, x2, y2);
        case 'L':
        case 'l':
          x2 = Integer.parseInt(st2.nextToken());
          y2 = Integer.parseInt(st2.nextToken());
          return new MapLine(color, x1, y1, x2, y2);
        case 'P':
        case 'p':
          boolean close = false;
          Vector v = new Vector();
          v.addElement(new Integer(x1));
          v.addElement(new Integer(y1));
          while (st2.hasMoreTokens()) {
            String s = st2.nextToken();
            if (!Character.isDigit(s.charAt(0))) {
              close = true;
            } else {
              v.addElement(new Integer(Integer.parseInt(s)));
            }
          }
          if (c == 'P') return new MapPoly(color, v, close, false);
          if (c == 'p') return new MapPoly(color, v, close, true);
        case 'T':
        case 't':
          // read string
          String s = st2.nextToken();
          label = s.substring(s.indexOf('\'')+1, s.lastIndexOf('\''));
          // read ptsize
          ptsize = Integer.parseInt(st2.nextToken());
          Font f = new Font("SansSerif", Font.PLAIN, ptsize);
          return new MapText(color, x1, y1, label, f);
        case 'C':
        case 'c':
          radius = Integer.parseInt(st2.nextToken());
          if (c == 'C') return new MapCircle(color, false, x1, y1, radius);
          if (c == 'c') return new MapCircle(color, true, x1, y1, radius);
          break;
        }
      return null;
    }


  // Static variables are shared among all instances of a class, in a sense
  // they are like global variables, although they can be private if needed

  static Hashtable colors = new Hashtable();
  static {
    colors.put("R", Color.red);
    colors.put("G", Color.green);
    colors.put("B", Color.blue);
    colors.put("Y", Color.yellow);
    colors.put("L", Color.black);
    colors.put("W", Color.white);
    colors.put("O", Color.orange);
    colors.put("A", Color.lightGray);
  }
  
  public static Color getMapColor(char c) {
    Color x = (Color) colors.get(""+c);  // convert character to string and lookup
    if (x == null)
      return Color.black;
    return x;
  }
}

// -----------------------------------------------------------------=
//
// Operations
//   : colon seperates operations
//   L=line, R=rectangle, C=circle, S=star, P=polygon, T=text
//   (r,c,s,p indicate filled) 
//
// Colors:
//    R=red, G=green, B=blue, Y=yellow, L=black, W=white, N=brown
//
// ---------------------------------------------
// Line Color x1,y1,x2,y2 
// 
// Rectange Color x1, y1, x2, y2
//
// Circle Color x1, y1, r
//
// Star Color x1, y1, r
//
// Polygon Color x1,y1,x2,y2,x3,y3,...,xn,yn, close
//
// Text Color x1, y1, 'string',ptsize
//   (string may not contain quotes, commas, or colons; spaces are ok)
// -----------------------------------------------------------------
