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

public class Projet extends java.applet.Applet {
  
  GraphCanvas graphcanvas = new GraphCanvas(this);
  Options options = new Options(this); 
  Panel sbpanel = new Panel();
  Scrollbar sb = new Scrollbar(Scrollbar.HORIZONTAL, graphcanvas.n, 5,
2, 
graphcanvas.maxN);
  Scrollbar sc = new Scrollbar(Scrollbar.HORIZONTAL,
graphcanvas.nbclass, 
3, 2, graphcanvas.maxC);

  public void init() {
    setLayout(new BorderLayout(10, 10));
    sbpanel.setLayout(new GridLayout(2, 1, 0, 5));
    add("Center", graphcanvas);
    add("East", options);
    add("South", sbpanel);
    sbpanel.add(sb);
    sbpanel.add(sc);
  }

  public boolean handleEvent(Event evt) { 

    if (evt.target == sb) {
      switch (evt.id) {
      case Event.SCROLL_LINE_UP: case Event.SCROLL_LINE_DOWN:
      case Event.SCROLL_PAGE_UP: case Event.SCROLL_PAGE_DOWN:
      case Event.SCROLL_ABSOLUTE:
        graphcanvas.setn(sb.getValue());
        break;
      default:
        break;
      }
    } // endif
    
    if (evt.target == sc) {
      switch (evt.id) {
      case Event.SCROLL_LINE_UP: case Event.SCROLL_LINE_DOWN:
      case Event.SCROLL_PAGE_UP: case Event.SCROLL_PAGE_DOWN:
      case Event.SCROLL_ABSOLUTE:
        graphcanvas.setclass(sc.getValue());
        break;
      default:
        break;
      }
    } // endif

    return(true);
  }

  public void start() {
    graphcanvas.initialisation();
  }

  public void lock() {
    graphcanvas.lock();
    options.lock();
  } 
  
  public void unlock() {
    graphcanvas.unlock();
    options.unlock();
  }
  
}

class Options extends Panel {
  
// set of options at the right of the screen

  private TextField mes = new TextField("Set Points",10);
  private Button b1 = new Button("clear");
  private Button b2 = new Button("run");
  private Button b3 = new Button("unlock");
  private Button b4 = new Button("reset");
  private Button b5 = new Button("exit");
  private Button b6 = new Button("<>");
  private Panel  NumCl = new Panel();
  private Label label = new Label("#Class : ");
  private TextField  numclass = new TextField("2", 3);;
  private Projet parent;   
  private boolean Locked = false;

  public Options(Projet myparent) {
    parent = myparent;
    mes.setEditable(false);
    setLayout(new GridLayout(8, 1, 0, 10));
  
    NumCl.add(label);
    NumCl.add(numclass);
    add(mes);
    add(b1);
    add(b2);
    add(b3);
    add(b4);
    add(b5); 
    add(b6);
    add(NumCl);
  }
  
  public boolean action(Event evt, Object arg) {
    if (evt.target instanceof Button) {
      if (((String)arg).equals("next step")) {
        if (Locked == true)
          parent.graphcanvas.kmeans();
      }
      if (((String)arg).equals("reset")) {
        parent.unlock();
        parent.graphcanvas.initialisation();
      }
      if (((String)arg).equals("unlock")) {
        parent.unlock();
      }
      if (((String)arg).equals("run")) {
        parent.lock();
        parent.graphcanvas.init_kmeans();
	parent.graphcanvas.kmeans();
      }
      if (((String)arg).equals("clear")) {
        parent.unlock();
        parent.graphcanvas.setn(0);
        parent.graphcanvas.initialisation();
      }
      if (((String)arg).equals("exit")) { 
        System.exit(0);
      }  
    }

    if (evt.target instanceof TextField && evt.id == Event.ACTION_EVENT 
&& Locked == false) {  
      
parent.graphcanvas.setclass(Integer.valueOf(numclass.getText()).intValue());
      }
    
    return true; 
  }
  
  public void lock() {
    mes.setText("Running...");
    b2.setLabel("next step");
    Locked=true;
  }
  
  public void unlock() {
    mes.setText("Set Points");
    b2.setLabel("run");
    Locked=false;
  } 

  public void setclass(int i) {
    numclass.setText(String.valueOf(i).toString());
  }
}    

class GraphCanvas extends Canvas {

// drawing area for the graph

  public  final int     maxN = 100;      // the max number of terminals
  public  int           n = 10;         // the current number of terminals
  private final int     r = 4;          // the radius of a terminal
  private Point         p[];            // the terminals
  private Point         current;        // the current one and its old location
  private float         m[][];          // the minimum spanning tree edges
  private Rectangle     border, inner;  // applet borders
  private Image         offScreenImage; // offScreenImage for double-offScreenImage
  private Graphics      offScreenGraphics; // offScreenImage's Graphics
  private Dimension     offScreenSize;
  private boolean       Locked = false;
  private int           step = 1;
  private Projet        parent;
  public  final int     maxC = 9;
  public  int           nbclass = 2;
  private float         mean_dist[];
  private Point         mean_pt[];
  private Point         old_mean_pt[];
  private final float   maxdist = 9999;
  private Point         current_m;

  public GraphCanvas(Projet myparent) {
    parent = myparent;
  }
  
  private Color WhichClass(int numclass) {
    switch (numclass) {
    case 0:
      return Color.blue;
    case 1:
      return Color.green;
    case 2:
      return Color.lightGray;
    case 3:
      return Color.red;
    case 4:
      return Color.orange;
    case 5:
      return Color.magenta;
    case 6:
      return Color.yellow;
    case 7:
      return Color.darkGray;
    case 8:
      return Color.pink;
    default:
      return Color.white;
    }
  }

  public void lock() {
    // lock screen while running an algorithm
    Locked=true;
    step = 1;
  }
  
  public void unlock() {
    Locked=false;
    step = 1;
  }

  public void setclass(int i) {
    if (Locked == false) { 
      if (i > 2) nbclass = i;
      else nbclass = 2;
      parent.sc.setValue(nbclass);
      parent.options.setclass(nbclass);
      if (i > n) {
        setn(nbclass);
      }
      repaint();
      System.out.println("Class :"+nbclass);
    }
  }
  
  public void setn(int i) {
    if (Locked == false) {
      if (i > 2) n = i;
      else n = 2;
      parent.sb.setValue(n);
      if (n < nbclass) {
        setclass(n);
      }
      repaint();
      System.out.println("Point :"+n);
    }
  }
  
  public final void update(Graphics g) {
    
    // prepare new image offscreen
    Dimension d=size();
    if ((offScreenImage == null) || (d.width != offScreenSize.width) ||
        (d.height != offScreenSize.height)) {
      offScreenImage = createImage(d.width, d.height);
      offScreenSize = d;
      offScreenGraphics = offScreenImage.getGraphics();
    }
    offScreenGraphics.setColor(Color.white);
    offScreenGraphics.fillRect(0, 0, d.width, d.height);

    // redraw the terminals
    
     for (int i = 0; i < n; i++) {
       offScreenGraphics.setColor(WhichClass((int) m[i][1]));
       offScreenGraphics.fillOval(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
       offScreenGraphics.setColor(Color.black);
       offScreenGraphics.drawOval(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
     }
    
    // redraw the mean points of each class 
    for (int j = 0; j < nbclass; j++) {
      offScreenGraphics.setColor(WhichClass(j));
      if (step % 2 == 1) 
	offScreenGraphics.draw3DRect(mean_pt[j].x - r , mean_pt[j].y - r ,2 *
r, 2 * r, true);
      else  {
	offScreenGraphics.drawLine (old_mean_pt[j].x, old_mean_pt[j].y,
mean_pt[j].x, mean_pt[j].y );
	offScreenGraphics.drawString("x", mean_pt[j].x-4, mean_pt[j].y+4);
      } 
    }
    
    // draw the current one in cyan
    if (current != null) {
      offScreenGraphics.setColor(Color.cyan);
      offScreenGraphics.fillOval(current.x - r, current.y - r, 2 * r, 2
* 
				 r);
      offScreenGraphics.setColor(Color.black);
      offScreenGraphics.drawOval(current.x - r, current.y - r, 2 * r, 2
* 
				 r);
    } 
    
    g.drawImage(offScreenImage, 0, 0, null);
  }
  
  
  public void initialisation() {
    p = new Point[maxN];
    current = null;
    current_m = null;
    m = new float[maxN][2];
    mean_dist = new float[maxC];
    mean_pt = new Point[maxC];
    old_mean_pt = new Point[maxC];
    
    border = new Rectangle(0, 0, size().width , size().height );
    inner = new Rectangle(r , r , size().width - 2*r , size().height - 
2*r);
    
    // initialize the terminals to random locations
    for (int i = 0; i < maxN; i++) {
      p[i] = new Point((int) Math.round(Math.random()
                                        * (size().width - r )),
                       (int) Math.round(Math.random()
                                        * (size().height - r)) );
      // Attribution de classe
      m[i][1] = 0;
    }  
    
    for (int j = 0; j < maxC; j++) {
       mean_pt[j] = new Point((int) Math.round(Math.random()
                                        * (size().width - r )),
                       (int) Math.round(Math.random()
                                        * (size().height - r)) ); 
       old_mean_pt[j] = new Point (mean_pt[j].x, mean_pt[j].y);
     }

    setBackground(Color.white); 
    repaint();
    
  } // init()

  public void paint(Graphics g) {
    update(g);
  } // paint(Graphics)
  
  
  public boolean handleEvent(Event event) {
    if (Locked == true) return true;

    switch (event.id) {
      
    case Event.MOUSE_DOWN: {
      if (event.modifiers == Event.META_MASK) {
        Rectangle rect = new Rectangle();

        current = null;
        for (int i = 0; (i < n) && (current == null); i++) {
          rect.reshape(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
          if (rect.inside(event.x, event.y)) {
            current = p[i];
          }
        }
        if (current == null) break;
        Point old = new Point(current.x,current.y);
        n = n-1;
        current.move(p[n].x,p[n].y);
        p[n].move(old.x,old.y);
        setn(n);
        current = null;
	current_m = null;
        
      } else { 
        Rectangle rect = new Rectangle();
        
        current = null;
	current_m = null;

        for (int i = 0; (i < n) && (current == null); i++) {
          rect.reshape(p[i].x - r, p[i].y - r, 2 * r, 2 * r);
          if (rect.inside(event.x, event.y)) {
            current = p[i];
          }
        }

	for (int i = 0; (i < nbclass) && (current_m == null) && (current ==
null); i++) {
          rect.reshape(mean_pt[i].x - r, mean_pt[i].y - r, 2 * r, 2 *
r);
          if (rect.inside(event.x, event.y)) {
            current_m = mean_pt[i];
          }
        }

        if (current == null && current_m == null && n < (maxN-1)) {
          current = p[n];
          current.move(event.x, event.y);
          setn((n+1));
        }
      }
      repaint();
      break;
    }

    case Event.MOUSE_UP: {
      current = null;
      current_m = null;
      repaint();
      break;
    }
      
    case Event.MOUSE_DRAG: {
      if (current != null) {
        if (inner.inside(event.x, event.y)) {
          current.move(event.x, event.y);
        }
        else {
          current.move(Math.max(Math.min(event.x, inner.x +
inner.width), 
				inner.x),
                       Math.max(Math.min(event.y, inner.y + 
					 inner.height), inner.y));
        }
        repaint();
      }
      if (current_m != null) {
        if (inner.inside(event.x, event.y)) {
          current_m.move(event.x, event.y);
        }
        else {
          current_m.move(Math.max(Math.min(event.x, inner.x +
inner.width), 
				  inner.x),
			 Math.max(Math.min(event.y, inner.y + 
					   inner.height), inner.y));
        }
        repaint();
      }
      break;
    }
      
    default: {
      break;
    }
      
    } // switch
    
    return(true);
  } // handleEvent(Event)
  
  
  // Euclidean distance between two points (x1,y1) and (x2,y2)
  private int distance(int x1, int y1, int x2, int y2) {
    return((int) Math.round(Math.sqrt(
                                      (double) (x1 - x2) * (x1 - x2) + 
(y1 - y2) * (y1 - y2))));
  } // distance(int,int,int,int)
  

// fonction parfaitement inutile

  private void mean_distance () {
     float sum = 0;
     float nb = 0;
     
     for (int j = 0; j < nbclass; j++) {
       nb = 0;
       sum = 0;
       for (int i = 0; i < n; i++)
         if ((int) m[i][1] == j) {
           sum += m[i][0];
           nb = nb + 1;
         }
       mean_dist[j] = sum / nb;
     }         
   }  // mean_distance



  private void init_distance() {
    float dist;
        
    for (int i = 0; i< n; i++)
      for (int j = 0; j < nbclass; j++)
        if ( (dist=distance(p[i].x,p[i].y,mean_pt[j].x,mean_pt[j].y)) < 
m[i][0] ) {
          m[i][0] = dist;
          m[i][1] = j;
        }
  } // init_distance



  private void first_classes () {
    
    for (int i = 0; i < n; i++) {
      m[i][0] = maxdist;
      m[i][1] = 1;
    }
          
     for (int j = 0; j < nbclass; j++) {
      old_mean_pt[j].x = mean_pt[j].x;
      old_mean_pt[j].y = mean_pt[j].y;
     }  
  } // first_classes


  
  private void new_mean_coord() {
    float sumx = 0;
    float sumy = 0;
    float nb = 0;

    for (int j = 0; j < nbclass; j++) {
      old_mean_pt[j].x = mean_pt[j].x;
      old_mean_pt[j].y = mean_pt[j].y;
    }  

    for (int j = 0; j <nbclass; j++){
      nb = 0;
      sumx = 0;
      sumy = 0;

      for (int i = 0; i < n; i++) 
        if ((int) m[i][1] == j) {
          sumx += p[i].x;
          sumy += p[i].y;
          nb = nb + 1;
        }

      if (nb != 0) {
	mean_pt[j].x = (int) Math.round(sumx / nb);
	mean_pt[j].y = (int) Math.round(sumy / nb);
      }
    }
 
  }  //  new_mean_coord()
       
       

  private boolean find_new_classes () {
    boolean change = false;  
    float dist;
    float temp;
    
    for (int i = 0; i < n; i++) 
      m[i][0] = maxdist;
      
    for (int i = 0; i < n; i++) {
      temp = m[i][1];
      for (int j = 0; j < nbclass; j++)
        if ( (dist=distance(p[i].x,p[i].y,mean_pt[j].x,mean_pt[j].y)) < 
m[i][0])  {
          m[i][0] = dist;
          m[i][1] = j;
        }
      if ( m[i][1] != temp) 
        change = true;
    }    
    repaint();
    return (change);
  }  // find_new_classes

  public void init_kmeans () {
    first_classes();
    init_distance();
    repaint();
  }


  public void kmeans() {
    if (Locked == true) {
      step++;
      if (step % 2 == 1) repaint();
      else alg_kmeans();
    }
  }

  private void alg_kmeans() {
    new_mean_coord();            
    if ( (find_new_classes()) == false )
      end();
  }
  
  private void end() {
    setn(n);
    setclass(nbclass);
    parent.unlock();
  }  
    
}
