/* 
 * DLA Generator - Java Applet
 * 
 * (C) 2007 by Bernhard Pollak
 * made for the fractals lecture 186.186
 * hold by Dr. Traxler at the TU Vienna
 * 
 */

 
import java.applet.Applet; 
import java.awt.*;
import java.util.Random;
import java.util.Vector;
import java.util.Enumeration;


public class appletDLA extends Applet {
    
  private Button btnStartStop,btnPauseResume;
  private Scrollbar scrollZoom;
  private Choice choiceStart,choiceTarget;
  private CanvasDLA canvasDLA;  
  
  public void init(){
    setBackground(Color.white); 
    createGUI();    
  }
  
  public void destroy() {
    
  }
  
  public void updateUIState() {
    if (canvasDLA.isStarted()) {
      btnStartStop.setLabel("Stop");
    } else {
      btnStartStop.setLabel("Start");
    }
    /*if (canvasDLA.isPaused()) {
      btnPauseResume.setLabel("Resume");
    } else {
      btnPauseResume.setLabel("Pause");
    }*/
  
  }
  
  public boolean action(Event e, Object arg) {
    System.out.println("actionPerformed!");
    if (e.target == btnStartStop) {
      System.out.println("btnStartStop pressed!");      
      canvasDLA.toogleThread(choiceStart.getSelectedIndex(),choiceTarget.getSelectedIndex());     
    } else if (e.target == btnPauseResume) {
      System.out.println("btnPause pressed!");
      canvasDLA.tooglePause();            
    } else return false;
    updateUIState();
    return true;
  } 
  
  public void paint(Graphics g)     { 
    
  } 
  
  private Label createLabel(String text, int fontStyle, int fontSize) {
    Label l=new Label(text);
    l.setFont(new Font("Arial",fontStyle,fontSize));
    return l;
  }
  
  private void createGUI() {
    System.out.println("createGUI");
    // set main layout
    setLayout(new BorderLayout(10,10));
    // set title
    Panel p1 = new Panel();  
    p1.add(createLabel("DLA - Diffusion Limited Aggregation",Font.BOLD,18));
    add("North",p1);    
    
    // set control/parameters panel
    Panel p2 = new Panel();  
    p2.setLayout(new GridLayout(0,1));    
    // controls
    p2.add(createLabel("Controls",Font.BOLD,14));
    p2.add(btnStartStop=new Button(""));
    
    // not implemented yet
    //p2.add(btnPauseResume=new Button(""));   
    //p2.add(createLabel("Zoom:",0,14));
    //p2.add(scrollZoom=new Scrollbar(Scrollbar.HORIZONTAL,50,10,1,100));
    
    p2.add(new Label(""));      
    // parameters
    p2.add(createLabel("Parameters",Font.BOLD,14));
    p2.add(createLabel("Start region:",0,14));      
    // start region
    p2.add(choiceStart=new Choice());
    choiceStart.add("Circle");
    choiceStart.add("Rectangle");
    // target region
    p2.add(createLabel("Target region:",0,14));
    p2.add(choiceTarget=new Choice());
    choiceTarget.add("Point");
    choiceTarget.add("Line");
    // choiceTarget.add("Circle");    
    p2.add(new Label(""));      
    add("East",p2);
    
    canvasDLA = new CanvasDLA(); 
    add("Center",canvasDLA);
    
    updateUIState();
  } 
  
}


class PointDLA {
  int x,y;  
  
  PointDLA(int dx, int dy) {
    x=dx;
    y=dy;
  } 

}


class CanvasDLA extends Canvas implements Runnable {
  
  int MAXPOINTS=1000;              // maximal points
  
  private boolean running=false;
  private boolean paused=false;
  private int updateInterval = 1;
  private Thread threadDLA;
  private Vector pointsDLA;       // vector with the DLA points
  private Vector targetPoints;    // vector with the target points
  private Vector walkerPoints;    // vector with the last walker points
  Graphics graphics;  
  double zoomFactor=1.0;          // zoom not implemented yet
  double centerX,centerY;
  double virtualCenterX,virtualCenterY;
  int screenDim,virtualScreenDim; // physical and virtual screen dimension
  int virtualPixelSize=10;        // the current size of a virtual pixel (a DLA point)
  int startRegion=0;    // 0:circle; 1:rectangle
  int currentStartRegionDim=10;
  int incStartRegion=10;
  int targetRegion=0;   // 0:point; 1:line
  PointDLA walker,walkerold;
  
  CanvasDLA(){    
    pointsDLA = new Vector(2000);
    targetPoints = new Vector(10);
    walkerPoints = new Vector(1000);
    graphics = getGraphics();
    virtualScreenDim=20;
    incStartRegion=20;
    update(graphics);
  }
  
  
  // STATE FUNCTIONS
  
  synchronized boolean isStarted() {
    return (running);   
  }
  
  synchronized boolean isPaused() {
    return (paused);        
  }
  
  
  // ACTION FUNCTIONS
  
  synchronized void toogleThread(int start, int target) {             
    if (!running)  {      
      startRegion=start;
      targetRegion=target;
      threadDLA = new Thread(this);
      running=true;
      paused=false;
      virtualScreenDim=50;
      currentStartRegionDim=50;
      pointsDLA.removeAllElements();
      setupTargetRegion();
      graphics = getGraphics();
      clearDLA();
      repaint();
      threadDLA.start();      
    } else {
      running=false;
      //clearDLA();
      //threadDLA=null;      
    }
  }      
  
  synchronized void tooglePause() {
    if (paused) {
      threadDLA.notify();
      paused=false;
    } else {
      try{
        threadDLA.wait();
        paused=true;
      } catch (InterruptedException e) { }
    }   
  }   
  
  
  // INTERNAL FUNCTIONS (calculation, graphics) 
  
  // DLA calculation
  
  public void run() {   
    while (running) {
      //try {
        walkPoint();
        //Thread.sleep(updateInterval);       
        Thread.yield();
      //} catch (InterruptedException e) {
        //return;
      //}
    }     
  }    
  
  // fill the targetPoints Vector with the required geometry (point or line)
  private void setupTargetRegion() {
    targetPoints.removeAllElements();
    switch (targetRegion) {
    case 0:
      targetPoints.addElement(new PointDLA(0,0));
      break;
    case 1:
      for (int i=0;i<20;i++) {
        System.out.println("setupTargetRegion: "+i);
        targetPoints.addElement(new PointDLA(i-10,0));        
      }
      break;    
    case 2:
      int x,y,r2;
      int radius=10;
      r2=radius*radius;
      for (x=-radius;x<radius;x++) {
        y = (int) (Math.sqrt(r2 - x*x) + 0.5);
        targetPoints.addElement(new PointDLA(x,y));
        targetPoints.addElement(new PointDLA(x,-y));     
      }
      break;
    }
  }
  
  // generate a walker point on the start region
  private void generateWalkerPoint() { 
    
    int x=0,y=0;
    Random randInt = new Random();
    switch (startRegion) {
    case 0:       
      double alpha=Math.toRadians((1+Math.abs(randInt.nextInt())) % 361);
      x=(int) (currentStartRegionDim/2*Math.cos(alpha));
      y=(int) (currentStartRegionDim/2*Math.sin(alpha));      
      break;   
    case 1:       
      int pos=Math.abs(randInt.nextInt())%currentStartRegionDim*4;
      if (pos<currentStartRegionDim) {
        x=pos;y=-currentStartRegionDim/2;
      } else if (pos<currentStartRegionDim*2) {
        x=currentStartRegionDim/2;y=pos-currentStartRegionDim-currentStartRegionDim/2;
      } else if (pos<currentStartRegionDim*3) {
        x=pos-currentStartRegionDim*2-currentStartRegionDim/2;y=currentStartRegionDim/2;
      } else if (pos<=currentStartRegionDim*4) {
        x=-currentStartRegionDim/2;y=pos-currentStartRegionDim*3-currentStartRegionDim/2;
      }
      break;
    }
    walker=new PointDLA(x,y);   
    walkerold=new PointDLA(x,y);   
    //drawPointDLA(walker,Color.yellow);
  }
  
  // check if the walker is inside the start region (shrink is a correction factor)
  private boolean checkWalkerRadius(int shrink) {           
    switch (startRegion) {
    case 0:
      int rad=Math.round(currentStartRegionDim/2)-shrink;
      //System.out.println("validWalkerPoint radius:"+rad+" x:"+walker.x+" y:"+walker.y);
      if ((Math.abs(walker.x)>rad) || (Math.abs(walker.y)>rad) || 
          ((walker.x*walker.x+walker.y*walker.y)>(rad*rad))) {
        //System.out.println("validWalkerPoint  NOT VALID!");
        return false;       
      }
      break;
    case 1:
      if ((Math.abs(walker.x)>currentStartRegionDim/2-shrink) || (Math.abs(walker.y)>currentStartRegionDim/2-shrink)) return false;
      break;
    }
    return true;    
  }
  
  // is a specific DLA point hit? (is neighboured field)
  private boolean isPointHit(PointDLA p, int x, int y) {
    //System.out.println("isPointHit x:"+p.x+"/"+x+" y:"+p.y+"/"+y);
    boolean hitPoint=false;
    
    if (!hitPoint) hitPoint=((p.x==x) && (p.y==y)); 
    
    if (!hitPoint) hitPoint=((p.x==x-1) && (p.y==y)); // left
    if (!hitPoint) hitPoint=((p.x==x+1) && (p.y==y)); // right
    if (!hitPoint) hitPoint=((p.x==x) && (p.y==y-1)); // top
    if (!hitPoint) hitPoint=((p.x==x) && (p.y==y+1)); // bottom
    
    if (!hitPoint) hitPoint=((p.x==x-1) && (p.y==y-1)); // left-top
    if (!hitPoint) hitPoint=((p.x==x+1) && (p.y==y-1)); // right-top
    if (!hitPoint) hitPoint=((p.x==x-1) && (p.y==y+1)); // left-bottom
    if (!hitPoint) hitPoint=((p.x==x+1) && (p.y==y+1)); // right-bottom      
    return hitPoint;
  }
  
  // do the random walking
  private void walkPoint() {
    boolean hitPoint=false;
    int direction=0;
    int counter=0;
    Random randInt = new Random();
    generateWalkerPoint();    
    walkerPoints.removeAllElements();
    do {
      Thread.yield();
      
      walkerold.x=walker.x;
      walkerold.y=walker.y;
      // get a random direction 
      direction = Math.abs(randInt.nextInt()) % 4; 
      switch (direction) {
        case 0: walker.y--; break;  // North
        case 1: walker.x++; break;  // East;
        case 2: walker.y++; break;  // South;
        case 3: walker.x--; break;  // West;      
      }   
            
      if (!running) return;
                              
      // check if a DLA point is hit
      for(Enumeration e = pointsDLA.elements();e.hasMoreElements();) {
        hitPoint=isPointHit((PointDLA)e.nextElement(),walker.x,walker.y);
        if (hitPoint) break;
        Thread.yield();
      }  
      if (!hitPoint) {
        // if not check the target points
        for(Enumeration e = targetPoints.elements();e.hasMoreElements();) {
          hitPoint=isPointHit((PointDLA)e.nextElement(),walker.x,walker.y);     
          if (hitPoint) break;
          Thread.yield();
        }
      }
      //drawPointDLA(walkerold,Color.black);      
      
      if ((counter>1000) || !checkWalkerRadius(0)) {
        // if the walker is walking to long or is outside the radius -> start new walker
        drawCurrentStartRegion();
        walker=null;
        return;
      } else if (!hitPoint) {       
        // add the walker to the walker vector list
        //drawPointDLA(walker,Color.cyan);
        //try { Thread.sleep(20);} catch (InterruptedException Ie) {}
        walkerPoints.add(new PointDLA(walker.x,walker.y));
      }
      
      counter++;
    } while (!hitPoint);
    if (!checkWalkerRadius(5)) {
      currentStartRegionDim=currentStartRegionDim+incStartRegion; 
      if (currentStartRegionDim>=virtualScreenDim) 
          virtualScreenDim=virtualScreenDim+(currentStartRegionDim-virtualScreenDim)+2;                 
      clearDLA();
    }
    // if hit, draw all the walker points...
    drawWalkers(Color.yellow);
    try { Thread.sleep(20);} catch (InterruptedException Ie) {}
    drawWalkers(Color.black);
    // add the walker to the DLA list
    addPoint(walker);
    walker=null;    
    // if larger than MAXPOINTS stop!
    if (pointsDLA.size()>=MAXPOINTS) toogleThread(0,0);
  }  
  
  private void addPoint(PointDLA p) {
    pointsDLA.addElement(p);    
    // repaint();   
    update(graphics);
  }
    
  // Graphic Functions
  
  // calculate real x coordinate depending on virtual pixel size
  private int getX(double x) {
    return (int) (x*virtualPixelSize);    
  }  
  
  private int getY(double y) {
    return (int) (y*virtualPixelSize);
  }
  
  // like getX, but add the center
  private int getXc(double x) {
    return (int) (getX(x)+centerX);   
  }  
  
  private int getYc(double y) {
    return (int) (getY(y)+centerY);
  }
  
  // draw a single point (pixel size)
  private void drawPointDLA(PointDLA p, Color c) { 
    if (p!=null) {
      //System.out.println("drawPointDLA "+p.x+" - "+p.y);
      graphics.setColor(c);
      graphics.fillRect(getXc(p.x),getYc(p.y),virtualPixelSize,virtualPixelSize);
      //graphics.fillOval(getXc(p.x),getYc(p.y),virtualPixelSize,virtualPixelSize);
    }
  }
    
  // draw the star region
  private void drawCurrentStartRegion() {
    graphics.setColor(Color.blue);
    switch (startRegion) {
      case 0:
        int d = Math.round(virtualPixelSize/3);       
        graphics.drawOval(getXc(-currentStartRegionDim/2)+d,getYc(-currentStartRegionDim/2)+d,getX(currentStartRegionDim)+d,getY(currentStartRegionDim)+d);
        break;
      case 1:
        graphics.drawRect(getXc(-currentStartRegionDim/2),getYc(-currentStartRegionDim/2),getX(currentStartRegionDim),getY(currentStartRegionDim));
        break;
    }   
  }

  public void paint(Graphics g) {
    update(g);
  }
  
  // draw the vector of walker points
  public void drawWalkers(Color c) {    
    // draw all walker points...
    for(Enumeration e = walkerPoints.elements();e.hasMoreElements();) {
      PointDLA p=(PointDLA)e.nextElement();
      drawPointDLA(p,c);
      Thread.yield();
    }     
  }
  
  // draw the top left info block
  public void drawInfo() {
    int i=1;
    int step=15;
    int left=8;
    int top=3;
    
    graphics.setColor(Color.black);
    graphics.fillRect(0,0,180,35);
    graphics.setColor(Color.black);
    graphics.fillRect(0,35,100,50);
    
    graphics.setColor(Color.red);
    Font f = new Font("Arial",0,12);
    graphics.setFont(f);
    graphics.drawString("Diffusion Limited Aggregation",left,top+i++*step);
    graphics.drawString("# points: "+pointsDLA.size()+" ("+MAXPOINTS+")",left,top+i++*step);
    graphics.drawString("ScreenDim:"+virtualScreenDim,left,top+i++*step);
    graphics.drawString("PixelSize:"+virtualPixelSize,left,top+i++*step);
    graphics.drawString("StartRegion:"+currentStartRegionDim,left,top+i++*step);
    
  }
  
  // the canvas update method
  public void update(Graphics g) {  
    //graphics = g;
    setBackground(Color.black);
    
    
    // get canvas size
    Dimension d=getSize();
    centerX = d.width/2;
    centerY = d.height/2;
    if (d.width>d.height) { screenDim=d.height; } else { screenDim=d.width;}
    
    // virtual pixel size (depending on screen dimension and zoom factor)
    virtualPixelSize = (int) Math.round(((screenDim/virtualScreenDim)-1)*zoomFactor);
    if (virtualPixelSize<2) virtualPixelSize=2;
    
    // correct center with virtual pixel size
    centerX=(centerX - virtualPixelSize/2);
    centerY=(centerY - virtualPixelSize/2);
    
    if (threadDLA!=null) {      
      drawCurrentStartRegion();
            
      // draw target points
      for(Enumeration e = targetPoints.elements();e.hasMoreElements();) {
        PointDLA p=(PointDLA)e.nextElement();
        drawPointDLA(p,Color.green);
        Thread.yield();
      }   
      
      // draw all DLA points...
      double s=pointsDLA.size();
      if (s!=0) s=255/(s+s/2); 
      int color=0;
      int i=0;
      for(Enumeration e = pointsDLA.elements();e.hasMoreElements();) {        
        PointDLA p=(PointDLA)e.nextElement();
        color=255-(int) Math.round(s*i);  // calculate the color (gets darker if newer...)
        if (color>255) color=255;
        if (color<0) color=0;
        drawPointDLA(p,new Color(color,0,0));
        Thread.yield();
        i++;
      }            
      drawInfo();
      
    }
  }
  
  public void clearDLA() {
    setBackground(Color.black);
    graphics.setColor(Color.black);
    Dimension d=getSize();
    graphics.fillRect(0,0,d.width,d.height); 
    
  }

   
}