package edu.cornell.lassp.houle.HysSym;

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

/**
*
* <b>HysCanvas</b> displays the square lattice used by <b>HysLattice</b>
* but will someday be generalized to work with other lattice simulations.
* For an example of use,  see the applet <b>hys.</b>
*
* <P>
* <A HREF="../src/edu/cornell/lassp/houle/HysSym/HysCanvas.java" TARGET="edu.cornell.lassp.houle.source">
* Source code </A> is available.
*
* @see HysLattice
* @see hys
* 
* @author <A HREF="http://www.msc.cornell.edu/~houle" TARGET="edu.cornell.lassp.houle.author"> Paul Houle </A> (E-mail: <A HREF="mailto:ph18@cornell.edu">ph18@cornell.edu</A>)
* @version 0.9a
*/

public class HysCanvas extends Canvas implements AvalancheConsumer,RunStopConsumer {
    
    Image dbuff;
    Graphics buffg; 
    Dimension d;
    Color downcolor=Color.blue;
    Color upcolor=Color.green;
    Color flashcolor=Color.red;
 
    boolean imgChanged=true;
    int cellXsize=4;
    int cellYsize=4;
    int Xmargin=2;
    int Ymargin=2;
  

    HysLattice lattice;    
    int cellsX;
    int cellsY;
    int direction;

    int aNumber=5;            // number of avalanches to batch in display
    int aThreshold=20;        // threshold to display big avalanche right away
    
    Vector oldAvalanches,newAvalanches;	

    int yieldTime=5;          // yield time in milliseconds.

    short cell[][];           // diagnostic code

/**
*
* Constructs a new HysCanvas attached to <b>HysLattice</b> <tt>l</tt>.
*
* @param l a <b>HysLattice</b> that will be displayed by HysCanvas.
* 
*/	

    public HysCanvas(HysLattice l) {
	    	    
	lattice=l;
        cellsX=lattice.getXSize();
	cellsY=lattice.getYSize();
	
	d=new Dimension(cellXsize*cellsX+2*Xmargin,cellYsize*cellsY+2*Ymargin);
        resize(d);
        
        direction=l.getDirection();
	l.registerConsumer(this);

        oldAvalanches=new Vector(aNumber);
        newAvalanches=new Vector(aNumber);

	cell=new short[cellsX][cellsY];

	for(int i=0;i<cellsX;i++)       // diagnostic code
            for(int j=0;i<cellsY;i++)
	        cell[i][j]=0;

    };

/**
* draws an avalanche into the double-buffer dbuff
*
*/

    void colorAvalanche(Vector avalanche,Color c) {

        if (dbuff!=null) {
            buffg.setColor(c);


	if (!verifyAvalanche(avalanche,-1*direction) && c !=flashcolor)
	     System.out.println("HysCanvas: Changed color w/o flash");

        if (!verifyAvalanche(avalanche,direction) && c == flashcolor)
	
             System.out.println("HysCanvas: I just painted a bad avalanche! color = "+c);

	    for (Enumeration e = avalanche.elements(); e.hasMoreElements() ;) {
                CellIndex z=(CellIndex) e.nextElement();
		
                if (c==flashcolor) cell[z.i][z.j]= (short) direction;
                buffg.fillRect(cellXsize*z.i+Xmargin,cellYsize*z.j+Ymargin,cellXsize,cellYsize);   
            } 
        }
    };

/*
 * diagnostic subroutine 
 *
 */

    boolean verifyAvalanche(Vector avalanche,int dir) {

        for (Enumeration e = avalanche.elements(); e.hasMoreElements() ;) {
            CellIndex z=(CellIndex) e.nextElement();
            if (cell[z.i][z.j]==dir) return false;
        };

        return true;
    };
/**
* @see RunStopConsumer
*/
   public void receiveRunStop(boolean b) {
	flushAvalanches();

        flushAvalanches();

        direction=lattice.getDirection();
   };

/**
* @see AvalancheConsumer
*/

   synchronized public void receiveAvalanche(Vector avalanche,int dee) {
	
        if (!verifyAvalanche(avalanche,direction))
             System.out.println("HysCanvas: I got a bad avalanche from HysLattice!");
	newAvalanches.addElement(avalanche);

	if (newAvalanches.size()>aNumber || avalanche.size()>aThreshold)
		flushAvalanches();


};

    synchronized void flushAvalanches() {

        int opCount=0;

        for(Enumeration e=oldAvalanches.elements();e.hasMoreElements();)
        {
            colorAvalanche((Vector) e.nextElement(),(direction == 1) ? upcolor : downcolor);


        };

        for(Enumeration e=newAvalanches.elements();e.hasMoreElements();)
        {
	    colorAvalanche((Vector) e.nextElement(),flashcolor);

        };

	oldAvalanches=null;
	oldAvalanches=newAvalanches;
        newAvalanches=null;
	newAvalanches=new Vector(aNumber);
	repaint();

        yield2Netscape(100);
};

// redraw actually updates the double buffer from scratch
// while paint actually displays the double buffer to the screen
// paint calls redraw if the image has changed.

// the code containg opCount is required to keep Netscape responsive while
// we do heavy computations.  This hopefully will disappear in later
// versions

    void redraw(Graphics g) {
        int i,j,opCount=0;

	System.out.println("HysCanvas: redraw!");
   	
    	for(i=0;i<cellsX;i++)
    		for(j=0;j<cellsY;j++)
    			{
    				if (lattice.getCell(i,j)==1) {g.setColor(upcolor);} else {g.setColor(downcolor);};
    				g.fillRect(cellXsize*i+Xmargin,cellYsize*j+Ymargin,cellXsize,cellYsize);

    			};
    };


// The paint method has a bit of weirdness that was explained by some
// people at sun.  In a perfect world,  we'd create the image that we
// are using for "double-buffering" in the constructor.  This doesn't
// work.  createImage returns a null if it is called ~before~ a
// component has been incorporated on screen.  Since you have to construct
// a component before you can add it,  this is a problem.

// Solution: the paint method checks to see if the buffer exists (by
// seeing if it's value is "null") and then if it doesn't it creates
// it.

    public void paint(Graphics g) {


	if (dbuff == null) {

           dbuff=createImage(d.width,d.height);
           buffg=dbuff.getGraphics();
	   redraw(buffg);
        };

	g.drawImage(dbuff,0,0,null);

    };


// the default update method clears the component back to background
// before we draw.  We don't need this because we're going to paint the
// whole thing over anyway and don't need an unsightly grey flash as
// the image is being computed.

public void update(Graphics g){
 paint(g);
 }

// yield2Netscape puts us to sleep after we've consumed a lot of CPU
// to give NetScape a chance to update the screen,  respond to the user,
// etc.  I hope we can get rid of this someday.

    void yield2Netscape()
    {
        try
        {
	    Thread.currentThread().sleep(yieldTime);
        } catch(InterruptedException e) {};

    };	

    void yield2Netscape(int yT)
    {
        try
        {
	    Thread.currentThread().sleep(yT);
        } catch(InterruptedException e) {};

    };	
	
};

















