
import java.util.*;
import java.awt.*;

public class DemoApplet extends BufferedApplet
{
   int W=0, H, shiftState=0, dx, dy, r, clickPage=-1, page=0, border;
   Font font = new Font("Courier", Font.BOLD, 20);
   Font labelFont = new Font("TimesRoman", Font.PLAIN, 14);
   Font patternFont, smallerFont;
   Color darkInk = new Color(150,150,150);
   Color faintInk = new Color(200,200,200);
   Color veryFaintInk = new Color(220,220,220);
   Color unselectedColor = new Color(230,230,255);
   Color selectedColor = new Color(255,230,250);
   Color pressedColor = new Color(220,210,230);
   boolean isMouseDown = false;
   int terseness = 1;
   boolean isClickingPatternRect = false;
   boolean isControlState = false;
   AlphaDraw z = new AlphaDraw();
   TextEditor text = new TextEditor();
   Rectangle R, tersenessRect, pageRects[];
   Polygon dot;
   Graphics g;
   String fileName = "";
   int panX = 0, panY = 0;

   public void render(Graphics g) {
      this.g = g;
      if (W == 0) {
	 load(page);
	 setPitch(40);
	 pageRects = new Rectangle[20];
      }
      if (W != bounds().width) {
         W = bounds().width;
         H = bounds().height;
	 border = W / 25;
	 text.setBounds(W - border, H);
	 tersenessRect = new Rectangle(0,H-40,80,20);
	 int w = (W-border) / 20;
	 for (int i = 0 ; i < pageRects.length ; i++)
	    pageRects[i] = new Rectangle(w*i,H-20,w,20);
      }

      g.setColor(Color.white);
      g.fillRect(0,0,W,H);

      g.setColor(veryFaintInk);
      int parity = 0;
      for (int y = 0 ; y < H ; y += dy) {
         for (int x = parity*dx/2 ; x < W ; x += dx)
	    fillDot(x - panX,y - panY);
         parity = 1 - parity;
      }

      g.setColor(Color.black);

      if (isDrawing && terseness>0)
         drawLetterPattern(g);

      for (int i = 0 ; i < n-2 ; i += 2)
         g.drawLine(xy[i],xy[i+1],xy[i+2],xy[i+3]);

      g.setFont(patternFont);
      for (int i = 0 ; i < nw ; i++)
         drawLetterIcon(cToIcon(wayC[i]), wayX[i], wayY[i], true);

      g.setFont(patternFont);
      g.setColor(darkInk);
      g.drawString("" + dx, W-border-25,14);
      g.setFont(labelFont);
      g.drawString(fileName, W-border-5 - TextEditor.stringWidth(g, fileName), H-25);

      g.setFont(font);
      text.render(g);

      g.setColor(faintInk);
      g.fillRect(W - border, 0, border, H);
      g.setColor(darkInk);
      fillTriangle(W - 3*border/4, H/2 - border, 
		   W -   border/2, H/2 - 3*border/2,
		   W -   border/4, H/2 - border);
      fillTriangle(W - 3*border/4, H/2 + border, 
		   W -   border/2, H/2 + 3*border/2,
		   W -   border/4, H/2 + border);

      drawButton(tersenessRect, "=> " + (terseness==0 ? "TERSE" : terseness==1 ? "WORDY" : "EXPERT"), false,
      isClickingPatternRect);
      for (int i = 0 ; i < pageRects.length ; i++)
         drawButton(pageRects[i], "" + (i+1), i == page, i == clickPage);
   }

   void drawButton(Rectangle R,String label,boolean isSelected,boolean isDown) {
      g.setColor(isSelected ? selectedColor : unselectedColor);
      g.fill3DRect(R.x,R.y,R.width,R.height, ! isDown);
      g.setColor(Color.black);
      g.setFont(labelFont);
      drawString(label, R.x + R.width/2, R.y + 15);
   }

   int X3[] = new int[3], Y3[] = new int[3];
   void fillTriangle(int x0, int y0, int x1, int y1, int x2, int y2) {
      X3[0] = x0; X3[1] = x1; X3[2] = x2;
      Y3[0] = y0; Y3[1] = y1; Y3[2] = y2;
      g.fillPolygon(X3,Y3,3);
   }

   void setPitch(int p) {
      if (p >= 10) {
         xCenter = yCenter = nw = n = 0;
         dx = p;
         dy = (int)(dx * Math.sqrt(3) / 2);
         r = (int)(.23 * dx);
         patternFont = new Font("Courier", Font.PLAIN, 5*r/3);
         smallerFont = new Font("Courier", Font.PLAIN, r);
         damage = true;

	 for (int i = 0 ; i < 6 ; i++) {
	    double theta = 2 * Math.PI * i / 6; 
	    DX[i] = (int)(r * Math.sin(theta) + .5);
	    DY[i] = (int)(r * Math.cos(theta) + .5);
	 }
	 dot = new Polygon(DX,DY,6);
      }
   }

   boolean onDot(int x, int y) {
      y += 201*dy/2;
      x += 201*dx/2;
      if (y / dy % 2 == 1)
         x += dx/2;
      x = x % dx - dx/2;
      y = y % dy - dy/2;
      return dot.inside(x - xToDot(x,y), y - yToDot(x,y));
   }

   int xy[] = new int[10000];
   int n = 0;

   int xDown = -1, yDown = -1;
   int xCenter, yCenter;
   boolean isSelecting = false;
   boolean isChangingPitch = false;
   boolean isDrawing = false;

   int nw = 0, wayX[] = new int[50], wayY[] = new int[50], wayC[] = new int[50];

   public boolean mouseDown(Event e, int x, int y) {
      isMouseDown = true;
      xDown = x;
      yDown = y;
      nw = n = 0;

      for (clickPage = pageRects.length-1 ; clickPage >= 0 ; clickPage--)
	 if (pageRects[clickPage].inside(x,y))
	    break;

      if ( clickPage < 0 &&
           ! (isClickingPatternRect = tersenessRect.inside(x,y)) &&
           ! (isChangingPitch = x > W - border) ) {
         g.setFont(font);
         if (isSelecting = text.isOverText(x,y))
            text.select(xDown, yDown, x, y);
         else {
            if (onDot(x,y))
	       startStroke(x,y);
            xy[n++] = x;
            xy[n++] = y;
         }
      }
      damage = true;
      return true;
   }

   void startStroke(int x, int y) {
      isDrawing = true;
      nw = 0;
      wayX[nw  ] = xCenter = xToDot(x,y);
      wayY[nw  ] = yCenter = yToDot(x,y);
      wayC[nw++] = -1;
      panX = panY = 0;
      mx = x;
      my = y;
   }

   int xToDot(int x, int y) {
      x -= panX;
      y -= panY;
      x += dx/2;
      y += dy/2;
      return y / dy % 2 == 0 ?  x         / dx * dx
                             : (x + dx/2) / dx * dx - dx/2;
   }
   int yToDot(int x, int y) {
      y += dy/2;
      return y / dy * dy;
   }

   int n0, n1;
   int xTravel, yTravel, heading = -1;
   int mx, my;
   public boolean mouseDrag(Event e, int x, int y) {

      if (clickPage >= 0)
	 ;
      else if (isClickingPatternRect)
	 ;
      else if (isChangingPitch) {
	 if (y > yDown + border/2) {
	    setPitch(dx - 1);
	    yDown = y;
	 }
	 else if (y < yDown - border/2) {
	    setPitch(dx + 1);
	    yDown = y;
	 }
      }
      else if (isSelecting) {
         g.setFont(font);
         text.select(xDown, yDown, x, y);
      }

      else if (nw == 0 && ! onDot(x,y))
	 ;
      else {
	 if (nw == 0 && onDot(x,y))
	    startStroke(x,y);

         boolean wasOnDot = onDot(xy[n-2],xy[n-1]);
         boolean onDot  = onDot(x,y);

	 if (wasOnDot && ! onDot) {
	    n0 = n-2;
	    xTravel = yTravel = 0;
	    heading = -1;
         }
	 if (! wasOnDot && ! onDot) {
	    xTravel += x-xCenter;
	    yTravel += y-yCenter;
	    int X = 4 * xTravel / (n-n0), Y = 4 * yTravel / (n-n0);
	    int RR = X*X + Y*Y;
	    if (heading == -1 && RR >= .4 * dx * dx) {
	       double angle = 6.5 - 6 * Math.atan2(X,Y) / Math.PI;
	       heading = (int)angle % 12;
	       if (heading % 2 == 0 && RR < .9 * dx * dx)
		  heading = -1;
            }
	 }
	 if (! wasOnDot && onDot) {
            int xc = xToDot(x,y);
            int yc = yToDot(x,y);
	    recognize(xCenter,yCenter, xc,yc);
	    wayX[nw  ] = xCenter = xc;
	    wayY[nw  ] = yCenter = yc;
	    wayC[nw++] = ch;
	    //panX += x - mx;
	    //panY += y - my;
	    mx = x;
	    my = y;
	    heading = -1;
         }
         xy[n++] = x;
         xy[n++] = y;
      }
      damage = true;
      return true;
   }
   public boolean mouseUp(Event e, int x, int y) {
      if (clickPage >= 0) {
	 save(page);
	 load(page = clickPage);
	 clickPage = -1;
	 damage = true;
	 return true;
      }
      if (isClickingPatternRect) {
         isClickingPatternRect = false;
	 terseness = (terseness + 1) % 3;
	 damage = true;
	 return true;
      }
      isChangingPitch = false;
      isMouseDown = false;
      isDrawing = false;
      heading = -1;
      if (isSelecting) {
         g.setFont(font);
         if (! text.hasSelection())
	    text.placeCursor(xDown,yDown);
         isSelecting = false;
	 n = 0;
	 damage = true;
	 return true;
      }

      if (ch > ' ' && ch < 128 || nw > 0 && ! onDot(x,y))
         enterTextChar(' ');

      damage = true;
      return true;
   }

   int roundX(int x, int y) { return x + 100*dx - xCenter + dx/2; }

   int roundY(int x, int y) { return y + 100*dy - yCenter + dy/2; }

   int p[] = new int[1000];
   int ch = 0;
   void recognize(int x0,int y0, int x1,int y1) {

      int rr = 0;
      for (int i = 0 ; i < n-n0 ; i += 2) {
	 int x = xy[n0+i]-xy[n0], y = xy[n0+i+1]-xy[n0+1];
	 rr = Math.max(rr, x*x + y*y);
      }
      if (rr < r * r)
	 return;

      int DX = x1 - x0, DY = y1 - y0;
      int R = (int)Math.sqrt(DX * DX + DY * DY);
      int j = R < dx/2 ? 0 : R < 3*dx/2 ? 1 : R < 11*dx/6 ? 2 : 3;

      for (int i = 0 ; i < n-n0 ; i++)
         p[i] = xy[n0 + i];
      ch = z.recognize(p, n-n0, j, shiftState != 0);

      int c = ch;
      if (isControlState)
         c -= 'a'-1;
      isControlState = false;

      switch (c) {
      case -1:              // DO NOTHING
         break;
      case AlphaDraw.SHIFT: // SHIFT
         shiftState = (shiftState + 1) % 3;
         break;
      case AlphaDraw.CNTRL: // CONTROL
	 isControlState = true;
         break;
      case 'C' - '@':       // CONTROL C
         text.copy();
         break;
      case 'F' - '@':       // CONTROL F - SET FILE NAME
	 setFileName(text.getCutBuffer());
	 break;
      case 'P' - '@':       // CONTROL P - PRINT
	 for (int p = 0 ; p < 20 ; p++) {
	    load(p);
	    String s = text.toString();
	    if (s.length() > 1) {
	       System.out.println(":" + fileName + ":" + p);
               System.out.print(s);
            }
         }
	 load(page);
         break;
      case 'T' - '@':       // CONTROL T - IS PATTERN VERBOSE
         terseness = (terseness + 1) % 3;
	 damage = true;
         break;
      case 'V' - '@':       // CONTROL V
         text.paste();
         break;
      case 'X' - '@':       // CONTROL X
         text.cut();
         break;
      case AlphaDraw.SEND:  // SEND
         System.out.println("SEND");
         break;
      case AlphaDraw.END:   // END
         System.out.println("END");
         break;
      case AlphaDraw.DELWD:   // DELETE WORD
         text.deleteWord();
         break;
      default:
	 if (c == '\b' && text.hasSelection())
	    text.cut();
         else
	    enterTextChar(c);
         break;
      }

      ch = ch >= 'A' && ch <= 'Z' ? ch+'a'-'A' : ch;
   }

   void setFileName(String s) {
      for (int i = 0 ; i < s.length() ; i++)
	 if (TextEditor.isSpace(s.charAt(i)))
	    s = s.substring(0,i) + "_" + s.substring(i+1,s.length());
      save(page);
      fileName = s;
      load(page);
      damage = true;
   }

   int cToIcon(int c) {
      switch (c) {
      case -1:
	 break;
      case '\b':
         c = 'B';
         break;
      case AlphaDraw.SHIFT: // SHIFT
         c = 'C';
         break;
      case 27:
         c = 'E';
         break;
      case AlphaDraw.CNTRL: // CONTROL
         c = 'K';
         break;
      case '\n':
         c = 'N';
         break;
      case AlphaDraw.DELWD:   // DELETE WORD
         c = 'W';
      default:
         if (c < ' ')
	    c += 'a'-1;
         break;
      }
      return c;
   }

   void enterTextChar(int c) {
      text.enterText("" + (char)c);
      if (shiftState == 1)
         shiftState = 0;
      damage = true;
   }

   public boolean keyUp(Event e, int key) {
      enterTextChar(key);
      return true;
   }

   String CH[] = {"aeiotB",
                  "bflpux",
                  "ycgmrv",
                  "dhnswz",
                  " jkqNC",
                  "579A13",
                  "4680K2",
                 "\\>]}E(",
                  "</).{[",
   };
   double U[] = {1.00, 1.62, 1.62,  1.46, 0.40, 1.10, 1.10, 0.76, 0.76}; 
   double V[] = {0.00,-0.18, 0.18, -0.84,-0.22,-0.36, 0.36,-0.27, 0.27}; 

   int J[]     = {  -1,    0,    1,    0,    0,    0,    1,    5,    2};
   double JU[] = {0.00, 2.00, 1.00, 1.47, 0.00, 1.00, 0.50, 0.50,-0.50};
   double JV[] = {0.00, 0.00,-1.73,-0.85, 0.00, 0.00,-0.87, 0.87,-0.87};

   int K[] =     {    1,   -1,   -1,   -1,   -1,   -1,   -1,    0,    2};
   double KU[] = { 0.50, 2.00, 1.00, 1.47, 0.00, 1.00, 0.50, 1.00,-0.50};
   double KV[] = {-0.87, 0.00,-1.73,-0.85, 0.00, 0.00,-0.87, 0.00,-0.87};

   void drawLetterPattern(Graphics g) {
      g.setFont(patternFont);
      for (int i = 0 ; i < 6 ; i++) {
         double theta = Math.PI * (i + 4) / 3;
         double C = dx * Math.cos(theta), S = dx * Math.sin(theta);
	 for (int n = 0 ; n < CH.length ; n++)
	    if (heading < 0) {
	       if (terseness==2 || n == 0 || n == 3) {
	          int c = CH[n].charAt(i);
                  int x = (int)(xCenter+U[n]*C+V[n]*S),
	              y = (int)(yCenter-V[n]*C+U[n]*S);
                  drawLetterIcon(c, x, y, false);
               }
            }
	    else if (heading % 2 == 0) {
	       if (i == heading/2 && J[n] >= 0) {
	          int c = CH[n].charAt((i+J[n])%6);
                  int x = (int)(xCenter+JU[n]*C+JV[n]*S),
	              y = (int)(yCenter-JV[n]*C+JU[n]*S);
                  drawLetterIcon(c, x, y, false);
               }
            }
	    else {
	       if (i == heading/2 && K[n] >= 0) {
	          int c = CH[n].charAt((i+K[n])%6);
                  int x = (int)(xCenter+KU[n]*C+KV[n]*S),
	              y = (int)(yCenter-KV[n]*C+KU[n]*S);
                  drawLetterIcon(c, x, y, false);
               }
	    }
      }
   }

   int DX[] = new int[6], DY[] = new int[6];
   int X[] = new int[6], Y[] = new int[6];
   void drawDot(int x, int y) {
      for (int i = 0 ; i < 6 ; i++) {
         X[i] = x + DX[i];
         Y[i] = y + DY[i];
      }
      g.drawPolygon(X,Y,6);
   }
   void fillDot(int x, int y) {
      for (int i = 0 ; i < 6 ; i++) {
         X[i] = x + DX[i];
         Y[i] = y + DY[i];
      }
      g.fillPolygon(X,Y,6);
   }
   void drawDot(int x, int y, boolean selected, boolean pressed) {
      g.setColor(pressed ? pressedColor : selected ? selectedColor : unselectedColor);
      fillDot(x,y);
      g.setColor(faintInk);
      drawDot(x,y);
      if (pressed) {
         g.setColor(Color.black);
         for (int i = 2 ; i < 5 ; i++)
	    g.drawLine(x+DX[i], y+DY[i], x+DX[i+1], y+DY[i+1]);
      }
      else {
         g.setColor(darkInk);
         for (int i = 2 ; i < 5 ; i++) {
	    int d = (i == 2 ? 1 : 0);
	    g.drawLine(x-DX[i]+d, y-DY[i]+d, x-DX[i+1], y-DY[i+1]);
         }
      }
   }

   void drawLetterIcon(int c, int x, int y, boolean isSelected) {
      int u = xToDot(x,y) - x - panX;
      int v = yToDot(x,y) - y - panY;
      boolean isOnDot = u*u + v*v < r*r/2;
      int du = x - xCenter, dv = y - yCenter;
      if (isOnDot || du*du + dv*dv > 2*dx*dx) {
	 boolean pressed = isDrawing && dot.inside(xy[n-2]-x, xy[n-1]-y);
	 drawDot(x+u,y+v, isSelected, pressed);
      }
      Font font = isOnDot ? patternFont : smallerFont;
      if (isOnDot) {
         x += u;
         y += v;
      }
      g.setFont(font);
      g.setColor(Color.black);
      int R = isOnDot ? r/2 : r/4 + 1;
      switch (c) {
      case -1:
	 break;
      case 'A':
	 g.setFont(smallerFont);
         drawString("ALT", x, y + r/2);
	 g.setFont(font);
         break;
      case 'B':
         fillTriangle(x-R,y, x+R,y-R, x+R,y+R);
         break;
      case 'C':
         fillTriangle(x-R,y+R, x,y-R, x+R,y+R);
         break;
      case 'E':
	 g.setFont(smallerFont);
         drawString("ESC", x, y + r/2);
	 g.setFont(font);
         break;
      case 'K':
	 g.setFont(smallerFont);
         drawString("CTL", x, y + r/2);
	 g.setFont(font);
         break;
      case 'N':
         fillTriangle(x-R+1,y-R+1, x+1,y+R+1, x+R+1,y-R+1);
         break;
      case 'W':
         fillTriangle(x-3*R/2,y, x+3*R/2,y-R, x+3*R/2,y+R);
         break;
      default:
         if (! isSelected && shiftState != 0)
	    c = z.shift(c);
         drawString("" + (char)c, x, y + 2*r/3-1);
         break;
      }
   }

   void drawString(String s, int x, int y) {
      g.drawString(s, x - TextEditor.stringWidth(g,s)/2, y);
   }

   boolean isAlpha(int c) { return c >= 'a' && c <= 'z'; }

   void load(int page) {
      text.clear();
      String s = DB.get("pen" + fileName + page);
      if (s.equals("no results\n") || s.equals("\n"))
	 s = "";
      for (int i = 0 ; i < s.length() ; i++) {
	 int c = s.charAt(i);
	 if (c == '\\')
	    switch (c = s.charAt(++i)) {
	    case 'n':
	       c = '\n';
	       break;
	    }
	 text.enterChar(c);
      }
   }
   void save(int page) {
      String s = text.toString(), t = "";
      for (int i = 0 ; i < s.length()-1 ; i++) {
	 int c = s.charAt(i);
	 switch (c) {
	 case '\n':
	    t += (char)'\\';
	    c = 'n';
	    break;
	 }
	 t += (char)c;
      }
      DB.set("pen" + fileName + page, t);
   }
}


