import java.applet.*;
import java.util.*;
import java.awt.*;
import java.net.*;
import java.io.*;

public class net extends Applet implements Runnable {

	public int	W ;
	public int	H ;
	public int	NGEONEURON;
	public double  px,py;
	public static final double	COUNTRY = 1.00;
	
  public static final Color bkC = new Color(0x000090); 	
  public static final Color bk2C = new Color(0x000050); 	
  public static final Color lnC = new Color(0xff0000); 	
  public static final Color ln2C = new Color(0xcccc00); 	
  public static final Color fgC = new Color(0xffffff); 	

	public Image   homeI,offscreen;
  public int imagewidth ,imageheight;
	public Thread  animator    = null;
	public boolean please_stop = false;
	Font mF = new Font("Courier", Font.BOLD, 12);
	Font sF = new Font("Courier", Font.BOLD, 8);
  public int counter;
	
	public geoNeuron gn[];
	
	public double r[][];
	
	public double theta, phi, momentum;
	
	public Scrollbar cscroll;

    ///////////////////////////////////////////////////////////////////
    //
    //  Init section
    //
    ///////////////////////////////////////////////////////////////////

		public void kohonenInit(){
			theta = 0.5;
    	phi   = 0.5;
    	momentum = 0.999;

			W = cscroll.getValue()/10;
			H = W;
			NGEONEURON = W * H;
			
      gn = new geoNeuron[NGEONEURON];
      for(int x = 0; x<W; x++)
        for(int y = 0; y<H; y++){
          gn[x*W+y] = new geoNeuron((double)x/(double)(W-1), (double)y/(double)(H-1));
      }
      
      r = new double[NGEONEURON][NGEONEURON];
      
      makeR(theta);        

			counter = 0;
		
		}

    ///////////////////////////////////////////////////////////////////
    //
    //  Problem section
    //
    ///////////////////////////////////////////////////////////////////

		public void makeR(double th){
		//System.out.println("");
      for(int i=0; i<NGEONEURON; i++){
      	r[i][i]= 1.0;
      	for(int j=i+1; j<NGEONEURON; j++){
      		r[i][j] = Math.exp( -1.0 * ( gn[i].dist(gn[j])*gn[i].dist(gn[j]) )/(2.0*th*th));
      		r[j][i] = r[i][j];
     			//System.out.print(" "+r[i][j]);
      	}
      	//System.out.println("");
      }
		}

    // The body of the animator thread.
    public void run() {
    	int idx,j;
    	double x1,x2,mindist;
    	int count = 0;
        while(!please_stop) {
            
            counter++;
            
            // CHOSE A RANDOM PATTERN
            x1 = Math.random()* COUNTRY;
            x2 = Math.random()* COUNTRY;

            while( (x1*x1+x2*x2) >1.0){
              x1 = Math.random()* COUNTRY;
              x2 = Math.random()* COUNTRY;
            }
            
            px = x1;
            py = x2;
            
            // SEARCH FOR MINIMAL
            mindist = 100000.0;
            j = -1;
            for(int i=0; i<NGEONEURON;i++){
            	double d = (x1 - gn[i].wx)*(x1 - gn[i].wx) + (x2 - gn[i].wy)*(x2 - gn[i].wy);
            	//double d = x1*gn[i].wx + x2*gn[i].wy;
            	//System.out.println("d="+d);
            	if(d < mindist){
            		mindist = d;
            		j = i;
            	}
            }
                        
            gn[j].update++;
                     
            // UPDATE WEIGHTS
            for(int i=0; i<NGEONEURON;i++){
            	gn[i].wx += (phi * r[i][j] * (x1 - gn[i].wx));
            	gn[i].wy += (phi * r[i][j] * (x2 - gn[i].wy));
            }
                        	              
            // DECREASE LEARNING PARAMETERS
            phi *= momentum;
            theta *= momentum;
            
            // RE-COMPUTE r MATRIX
      		  makeR(theta);
       		  
       		  
       		  // PLOT RESULT EVERY 10 SESSIONS
       		  count = (count++)%10;
       		  
      			if(count==0){
      				//System.out.println("theta = "+theta+"  phi = "+phi);

							paint(this.getGraphics());
            
							// Call Garbage Collect
							//System.gc();
			
            	try {Thread.sleep(10);} catch (InterruptedException e){};
            }
        }
        animator = null;
    }
    
    ///////////////////////////////////////////////////////////////////
    //
    //  Functional section
    //
    ///////////////////////////////////////////////////////////////////
    
    public void init() {
    
    	cscroll = new Scrollbar(Scrollbar.HORIZONTAL,50, 10, 30, 200);
    	cscroll.setLineIncrement(10);
    	cscroll.setPageIncrement(10);
    	add(cscroll);
    	
    	kohonenInit();	
    }

    private int toXReal(double val){int w = this.size().width;return (int)(val *((double)w-50.0) / COUNTRY +25.0);}
    private int toYReal(double val){int h = this.size().height;return (int)(val *((double)h-50.0) / COUNTRY +25.0);}
   
    public void paintLeft(Graphics g) {
        		Dimension size = this.size();
						int w = size.width, h = size.height;
						
						g.setFont(mF);
						
						// CLEAR ALL
            g.setColor(bkC);
            g.fillRect(0, 0, w, h);
            // DRAW GRID
            g.setColor(bk2C);
            for(double i=0; i<=COUNTRY; i+=(COUNTRY/20.0)){
            	g.drawLine(toXReal(0.0),toYReal(i),toXReal(COUNTRY),toYReal(i));
            	g.drawLine(toXReal(i),toYReal(0.0),toXReal(i),toYReal(COUNTRY));
            }
            
             //DRAW PATH
            g.setColor(lnC);
            
            for(int x=0; x<(W-1); x++)
              for(int y=0; y<(H-1); y++){
             	  g.drawOval( toXReal(gn[x*W+y].wx)-2, toYReal(gn[x*W+y].wy)-2,4,4);
                g.drawLine( toXReal(gn[x*W+y].wx),toYReal(gn[x*W+y].wy),toXReal(gn[(x+1)*W+y].wx),toYReal(gn[(x+1)*W+y].wy));
                g.drawLine( toXReal(gn[x*W+y].wx),toYReal(gn[x*W+y].wy),toXReal(gn[x*W+y+1].wx),toYReal(gn[x*W+y+1].wy));
            }
            for(int x=0; x<(W-1); x++){
             	  g.drawOval( toXReal(gn[x*W+H-1].wx)-2, toYReal(gn[x*W+H-1].wy)-2,4,4);
                g.drawLine( toXReal(gn[x*W+H-1].wx),toYReal(gn[x*W+H-1].wy),toXReal(gn[(x+1)*W+H-1].wx),toYReal(gn[(x+1)*W+H-1].wy));
	          }
	          for(int y=0; y<(H-1); y++){
             	  g.drawOval( toXReal(gn[(W-1)*W+y].wx)-2, toYReal(gn[(W-1)*W+y].wy)-2,4,4);
                g.drawLine( toXReal(gn[(W-1)*W+y].wx),toYReal(gn[(W-1)*W+y].wy),toXReal(gn[(W-1)*W+y+1].wx),toYReal(gn[(W-1)*W+y+1].wy));
	          }            
            g.drawOval( toXReal(gn[(W-1)*W+H-1].wx)-2, toYReal(gn[(W-1)*W+H-1].wy)-2,4,4);
            
            g.setColor(fgC);
            g.drawLine( toXReal(0.0), toYReal(0.0),toXReal(COUNTRY),toYReal(0.0));
            g.drawLine( toXReal(0.0), toYReal(0.0),toXReal(0.0),toYReal(COUNTRY));
            g.drawOval( toXReal(0.0)-toXReal(COUNTRY), toYReal(0.0)-toXReal(COUNTRY), toXReal(2* COUNTRY), toYReal(2*COUNTRY));
            
            g.drawOval( toXReal(px)-3, toYReal(py)-3, 6,6);

   	}    

    public void paint(Graphics g) {
        		Dimension size = this.size();
						int w = size.width, h = size.height;
						
						this.setBackground(bkC);

						
						if ((offscreen == null) || ((imagewidth != w) || (imageheight != h))) {
                offscreen = this.createImage(w, h);
                imagewidth = w;
                imageheight = h;
            }
						
						Rectangle clip = new Rectangle(toXReal(0),toYReal(0),toXReal(COUNTRY),toYReal(COUNTRY));
												
						Graphics goff = offscreen.getGraphics();
						goff.clipRect(clip.x, clip.y, clip.width, clip.height);
            Graphics g1 = this.getGraphics();
						g1.clipRect(clip.x, clip.y, clip.width, clip.height);
						
            paintLeft(goff);
            g1.drawImage(offscreen, 0, 0, this);
                        
            clip = null;
            goff = null;
            g1 = null;
            System.gc();
            
            // CLEAR ALL
            g.setColor(bkC);
            g.fillRect(w/2+30,0,w/2+130, 20);
            g.setColor(fgC);

            g.drawString("net width/height:"+cscroll.getValue()/10,w/2+30,20);

    }
 
    // Start the animation
    public void start() { 
        animator = new Thread(this);
        
        animator.start();
        
    }
    // Stop it.
    public void stop() { 
        if (animator != null) animator.stop();
        animator = null;
    }
    
    // Stop and start animating on mouse clicks.
    public boolean mouseDown(Event e, int x, int y) {
    // if running, stop it.  Otherwise, start it.
      if (animator != null){
      	please_stop = true;
      }else{ 
      	please_stop = false; 
				animator = new Thread(this);
				
				kohonenInit();
        animator.start();
      } 
      return true;
    }

}
