import java.awt.*;
import java.awt.event.*;
import java.applet.Applet; 
import java.awt.image.*; 


/**
 * Class NobleGas - calculates and displays movements of gas-atoms (kinetic gas theory)
 * created with BlueJ
 * 
 * @author (Martin Lieberherr) 
 * @version (29. April 2011)
 */
public class NobleGas extends Applet implements ActionListener, Runnable
{  
    // instance variables and attributes  (all private)
    int nx = 800;       // number of pixels in x-direction for picture
    int ny = 800;      //  number of pixels in y-direction for picture
    int n_atoms = 200;       // number of atoms in simulation
    int max_n_atoms = 2000;   // maximum number of atoms
    double temp = 300.0;           // absolute temperature in Kelvin
    double e_min = (1.04e-2)*(1.6022e-19);   // energy-minimum in Lennard-Jones potential in Joule (Argon gas)
    double r_min = 304.0e-12;    // distance (m) for energy-minimum in Lennard-Jones potential (gaseous Argon)
    double r_atom = 71.0e-12;    // radius of the atom (meter)  (for collision and for display) (Argon)
    double kB = 1.381e-23;    // Boltzmann-constant in J/K
    double m_atom = 39.95*(1.661e-27);    // mass of the atoms (in kg)  
    double v_thermal = Math.sqrt(kB*temp/m_atom);  // thermal speed (in m/s, in one dimension!)
    double scale = 1.25e-11;   // scale factor pixels*scale = real distances in meters
    double dt = 1.0e-15;          // step size for numerical integration  (seconds)
    boolean new_n_atoms = true;     // true, if a new n_atom is entered
    boolean new_temp = true;        // true, if a new temp is entered
    double x[] = new double[max_n_atoms];  // position of atoms in meters
    double y[] = new double[max_n_atoms];
    double vx[] = new double[max_n_atoms]; // velocity of atoms in m/s
    double vy[] = new double[max_n_atoms];
    double ax[] = new double[max_n_atoms]; // acceleration of atoms in m/s^2
    double ay[] = new double[max_n_atoms];
    double t_mean;          // calculated mean temperature
    double e_pot;           // calculated mean potential energy
    double r_near;          // shortest distance bewteen any two atoms
    int n_paint;            // number of time steps per frame displayed
    
    // attributes
    Label titleLabel;           // holds a title and the author's name
    Label n_atomsLabel;         // text "n_atoms: "
    TextField n_atomsField;     // display / input n_atoms
    Label tempLabel;            // text "T  (K): "
    TextField tempField;        // display/input temperature
    Label statusLine;           // display messages / status
    Label diagnosticsLabel;     // displays "Diagnostics:"
    Label meanTempLabel;        // displays the mean temperature
    Label meanEpotLabel;        // display the mean potential energy
    Label rNearLabel;           // display the shortest distance between any two atoms
    Label stepsPerFrameLabel;   // display the number of time-steps per frame in display
   
    Thread thread;              // loop to numerically integrate the movement
    
    // buffered image for display of atoms
    BufferedImage  BuffImage1 = new BufferedImage(nx, ny, BufferedImage.TYPE_INT_RGB);
    Graphics g1 = BuffImage1.createGraphics(); 
    
    
    

 
     /**
     * Called by the browser or applet viewer to inform this Applet that it
     * has been loaded into the system. It is always called before the first 
     * time that the start method is called.
     */
    public void init()
    {   
        setLayout(null);  // for "hard" layout in pixels
        
        int xText = nx+20;  // position of next element on window
        int yText = 30;  // position of next element on window
       
        titleLabel = new Label("NobleGas (by Martin Lieberherr)");
        titleLabel.setBounds(xText, yText, 260, 20);// setBounds(x_left, y_top, width, height) for display
        add(titleLabel);  //added to ContentPane to the right of picture 
        
        yText = yText+30;
        n_atomsLabel = new Label("n_atoms: ");
        n_atomsLabel.setBounds(xText, yText, 80, 20);  // setBounds(x_left, y_top, width, height) for display
        add(n_atomsLabel);             //added to ContentPane to the right of picture
        n_atomsField = new TextField(""+n_atoms);    
        n_atomsField.setBounds(xText+80, yText, 180, 20);
        add(n_atomsField);
        n_atomsField.addActionListener(this); // ActionListener is called if "enter" is pressed in TextField
        
        yText = yText+30;
        tempLabel = new Label("T  (K): ");
        tempLabel.setBounds(xText, yText, 80, 20);
        add(tempLabel);
        tempField = new TextField(""+temp);
        tempField.setBounds(xText+80, yText, 180, 20);
        add(tempField);
        tempField.addActionListener(this);
        
        yText = yText+40;
        statusLine = new Label("Status: running");
        statusLine.setBounds(xText, yText, 260, 20);
        add(statusLine);
        
        yText = yText+60;
        diagnosticsLabel = new Label("Diagnostics:");
        diagnosticsLabel.setBounds(xText, yText, 260, 20);
        add(diagnosticsLabel);
        
        yText = yText+30;
        meanTempLabel = new Label("mean temperature:");
        meanTempLabel.setBounds(xText, yText, 260, 20);
        add(meanTempLabel); 
        
        yText = yText+30;
        meanEpotLabel = new Label("mean Epot:");
        meanEpotLabel.setBounds(xText, yText, 260, 20);
        add(meanEpotLabel); 
        
        yText = yText+30;
        rNearLabel = new Label("nearest neighbor: ");
        rNearLabel.setBounds(xText, yText, 260, 20);
        add(rNearLabel);
       
        yText = yText+30;
        stepsPerFrameLabel = new Label("steps per frame: ");
        stepsPerFrameLabel.setBounds(xText, yText, 260, 20);
        add(stepsPerFrameLabel);
        
        thread = new Thread(this); // thread is a new thrad of this applet
        thread.start(); // start the thread
       
    }  // end init ()
 
  
    public void destroy()
    {
        thread = null; // set thread to zero if applet is stopped
    } 

    
    /** update()-method for applet
     *  (overridden to avoid flicker)
     *  @param g  the Grapics object for this applet
     */
    public void update( Graphics g )  {
        //display buffered image with atoms
        g.drawImage( BuffImage1, 0, 0, null);
    } // end update()
    
    /**
     * Paint method for applet.
     * 
     * @param  g   the Graphics object for this applet
     */
    public void paint(Graphics g)
    {
        update( g );
    } // end paint()
    
    
    
     /**
     * ActionListener Interface method.
     * Called when action events occur with registered components that
     * can fire action events.
     * @param  ae   the ActionEvent object created by the event
     */
    public void actionPerformed(ActionEvent evt)
    {
        // if a value for n_atoms is entered
        if (evt.getSource() == n_atomsField) {
            try { 
                n_atoms = (int) Double.parseDouble(n_atomsField.getText()); 
                if ((n_atoms < 1) || (n_atoms > max_n_atoms)) {
                    if (n_atoms < 1) {
                        n_atoms = 1;
                        n_atomsField.setText(""+n_atoms);
                        statusLine.setText("n_atoms must be > 0!");
                    }
                    if (n_atoms > max_n_atoms) {
                        n_atoms = max_n_atoms;
                        n_atomsField.setText(""+n_atoms);
                        statusLine.setText("n_atoms must be <=" + max_n_atoms);
                    }
                }
                else {
                    n_atomsField.setText(""+n_atoms);
                    statusLine.setText("Status: n_atoms changed to "+n_atoms);
                 }
                new_n_atoms = true;
            }
               
            catch (NumberFormatException e) {
                statusLine.setText("n_atoms had wrong number format");
                n_atomsField.setText(""+n_atoms); // System.exit(0);
                 }
            repaint();
            }
        
       // if a value for temp is entered
        if (evt.getSource() == tempField) {
            try { 
                temp = Double.parseDouble(tempField.getText()); 
                if (temp < 0) {
                    temp = 0.0;
                    tempField.setText(""+temp);
                    statusLine.setText("temperature must be >= 0 !");
                }
                else {
                    tempField.setText(""+temp);
                    statusLine.setText("Status: temp. changed to "+temp+" K");
                 }
                new_temp=true;
                }
            catch (NumberFormatException e) {
                statusLine.setText("temp. had wrong number format");
                tempField.setText(""+temp); // System.exit(0);
                 }
            repaint();
            }
        
    } // end actionPerformed()
 
    
    /**
     * Returns information about this applet. 
     * An applet should override this method to return a String containing 
     * information about the author, version, and copyright of the Applet.
     *
     * @return a String representation of information about this Applet
     */
    public String getAppletInfo()
    {
        // provide information about the applet
        return "Title:NobleGas   \nAuthor: Martin Lieberherr  \nkinetic gas theory \nApril 29th, 2011 ";
    }

    
  public void run(){
    double xx, yy, rr, qq, c, cc, ccc, cccc;  // temporary variables
    long t0;  // System.currentTimeMillis()
    int k;  // temporary variable
      
    try{
        
        while (true) {  // endless loop
           
            if (new_n_atoms) {
                // initialize and draw positions
                g1.setColor(Color.white);
                g1.fillRect(0,0,nx,ny);
                g1.setColor(Color.blue);
                rr = Math.ceil(Math.sqrt( n_atoms)); 
                k = 0;
                make_startpositions: // a label
                for (int i=0; i < rr; i++) {  // distribute atoms on a regular lattice with small scatter
                    for (int j=0; j < rr ; j++) {
                        x[k] = (19.9 + ((double) nx-40.0+Math.random())/rr*j)*scale;
                        y[k] = ((double) ny-19.9 -((double) ny-40.0+Math.random())/rr*i)*scale;
                        g1.fillOval( (int) ((x[k]-r_atom)/scale), (int) ((y[k]-r_atom)/scale), (int) (2.0*r_atom/scale), (int) (2.0*r_atom/scale));
                        k = k+1;
                        if (k >= n_atoms) { break make_startpositions; }
                    }
                }   
                repaint();
                new_n_atoms = false;
                new_temp = true; 
            }
            
            if (new_temp) {
                // initialize velocities and accelerations of atoms
                v_thermal = Math.sqrt(kB*temp/m_atom);  // for 1 degree of freedom! 
                for (int i=0; i<n_atoms; i++) {
                    vx[i] = normalRandom()*v_thermal; 
                    vy[i] = normalRandom()*v_thermal;
                    ax[i] = 0.0;
                    ay[i] = 0.0;
                }
                new_temp = false; 
            }
            repaint();
            // statusLine.setText("Status: running");
            t0 = System.currentTimeMillis();
            n_paint = 0; 
            
            // velocity Verlet-integrator:  (according to wikipedia one order better than Euler-Cromer)
            //  1) compute x(t+dt) = x(t) + v(t)*dt + 0.5*a(t)*dt^2
            //  2) calculate a(t+dt) from the positions x(t+dt)
            //  3) calculate v(t+dt) = v(t) + 0.5*( a(t)+a(t+dt) )*dt
        
            while ((!new_temp)&&(!new_n_atoms)) {
                
                // first step in velocity-Verlet
                c = 0.5*dt;
                for (int i=0; i<n_atoms; i++) {
                    x[i] = x[i] + (vx[i] +c*ax[i])*dt;
                    y[i] = y[i] + (vy[i] + c*ay[i])*dt;
                    vx[i] = vx[i] + c*ax[i]; // first part in third step in velocity-Verlet
                    vy[i] = vy[i] + c*ay[i];
                    ax[i] = 0.0;
                    ay[i] = 0.0; 
                } 
                
                // second step in velocity-Verlet
                cc = 12*e_min/m_atom;
                ccc = r_min*r_min; 
                e_pot = 0.0;  
                r_near = (double) scale*nx;
                r_near = r_near*r_near;
                qq = r_min/(2*r_atom);
                qq = qq*qq*qq;
                cccc =qq*qq;   // for argon: (304 pm / (2*71 pm) )^6 = 96
                if (n_atoms>=2) { // calculate the inter-atomar forces and the net-accelerations
                    for (int i=0; i<n_atoms-1; i++) {
                        for (int j = i+1; j<n_atoms; j++) {
                            xx = x[j]-x[i];
                            yy = y[j]-y[i];
                            rr = xx*xx + yy*yy; 
                            if (rr<r_near) {r_near = rr;}
                            qq = ccc/rr;
                            if (rr < 25*ccc) {  // cut-off for big separations
                                qq = qq*qq*qq;
                                if (qq>cccc) {qq=cccc;}  // prevent instabilities for very small separations
                                e_pot = e_pot + qq*qq-2*qq;  // Lennard-Jones potentials (up to a constant factor)
                                qq = cc*(qq*qq-qq)/rr;
                                ax[j] = ax[j] +qq*xx;
                                ax[i] = ax[i] -qq*xx;
                                ay[j] = ay[j] +qq*yy;
                                ay[i] = ay[i] -qq*yy;
                            }
                        }
                    }
                }
                e_pot = e_pot*e_min/(n_atoms/2)/1.6022e-22;  // energy per atom in milli-electonvolt
                e_pot = (double) Math.round(e_pot*100)/100;
                r_near = Math.sqrt(r_near);
                r_near = Math.round(r_near*1e12);  // shortest distance in picometer
                
                // complete third step in velocity-Verlet
                t_mean = 0; 
                for (int i=0; i<n_atoms; i++) {
                    vx[i] = vx[i]+c*ax[i];
                    vy[i] = vy[i]+c*ay[i];
                    t_mean = t_mean + vx[i]*vx[i] + vy[i]*vy[i]; 
                }
                t_mean = 0.5*m_atom*t_mean/kB/n_atoms; // "measured" mean temperature
                t_mean = (double) Math.round(t_mean*10.0)/10; // display later
                 
                // test, if boundary is reached 
                xx = nx*scale;
                yy = ny*scale;
                for (int i=0; i<n_atoms; i++) {
                    if (x[i] >= xx) {
                        x[i] = xx;
                        vx[i] = -vx[i]; //hard wall (elastic collision) 
                    }
                    else if (x[i] < 0) {
                        x[i] = 0;
                        vx[i] = -vx[i];
                    }
                    
                    if (y[i] >= yy) {
                        y[i] = yy;
                        vy[i] = -vy[i]; // elastic reflection
                    }
                    else if (y[i] < 0) {
                        y[i] = 0;
                        vy[i] = -vy[i]; 
                    }
                }
                
                // draw new positions after 100 steps OR 10 milliseconds
                n_paint = n_paint+1; // number of temporal steps in simulation
                if ((n_paint>99) || (System.currentTimeMillis()-t0 > 20)) {
                    g1.setColor(Color.white);
                    g1.fillRect(0,0,nx,ny);
                    g1.setColor(Color.blue);
                    k = (int) Math.round(2.0*r_atom/scale); // diameter of atom in pixels
                    for (int i=0; i<n_atoms; i++) {
                        g1.fillOval( (int) ((x[i]-r_atom)/scale), (int) ((y[i]-r_atom)/scale), k , k );
                    }
                    // g1.drawString("r_near = "+r_near, 20, 20); // for tests only
                    meanTempLabel.setText("mean temperature: "+t_mean+" K");
                    meanEpotLabel.setText("mean Epot: "+e_pot+" meV");
                    rNearLabel.setText("nearest neighbor: "+r_near+" pm");
                    stepsPerFrameLabel.setText("steps per frame: "+n_paint);
                    g1.drawLine(0,0,nx-1,0); // draw window border
                    g1.drawLine(nx-1,0,nx-1,ny-1);
                    g1.drawLine(nx-1,ny-1,0,ny-1);
                    g1.drawLine(0,ny-1,0,0);
                    repaint();
                    thread.sleep(20);
                    n_paint = 0; 
                    t0 = System.currentTimeMillis();
                }
            }//end inner while
        }// end outer while
    } // end try
    catch (Exception e) {
      System.out.println(e);
      System.exit(0);
    }//end catch
  }//end run

  
    /**
     * Generates random numbers with normal (gaussian) distribution 
     * @return  a double with normal distribution
     */
    public double normalRandom() {
      double z = Math.random();
      while (z == 0.0) {
        z = Math.random();}
        return Math.cos(2.0*Math.PI*Math.random())*Math.sqrt(-2.0*Math.log(z));
    } // end normalRandom()
  
}
