// Peter Ballo

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.applet.AudioClip;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.util.Random;
import java.awt.image.MemoryImageSource;
import java.lang.Math;
import java.awt.event.*;

class GasCanvas extends Canvas {
    Gas pg;
    GasCanvas(Gas p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(300,400);
    }
    public void update(Graphics g) {
	pg.updateGas(g);
    }
    public void paint(Graphics g) {
	pg.updateGas(g);
    }
};

class HistogramCanvas extends Canvas {
    Gas pg;
    HistogramCanvas(Gas p) {
	pg = p;
    }
    public Dimension getPreferredSize() {
	return new Dimension(125,50);
    }
    public void update(Graphics g) {
	pg.updateHistogram(g);
    }
};

class GasLayout implements LayoutManager {
    public GasLayout() {}
    public void addLayoutComponent(String name, Component c) {}
    public void removeLayoutComponent(Component c) {}
    public Dimension preferredLayoutSize(Container target) {
	return new Dimension(500, 500);
    }
    public Dimension minimumLayoutSize(Container target) {
	return new Dimension(100,100);
    }
    public void layoutContainer(Container target) {
	int cw = target.size().width * 2/3;
	target.getComponent(0).move(0, 0);
	target.getComponent(0).resize(cw, target.size().height);
	int i;
	int h = 0;
	for (i = 1; i < target.getComponentCount(); i++) {
	    Component m = target.getComponent(i);
	    if (m.isVisible()) {
		Dimension d = m.getPreferredSize();
		if (m instanceof Scrollbar)
		    d.width = target.size().width - cw;
		int c = 0;
		if (m instanceof Label) {
		    h += d.height/3;
		    c = (target.size().width-cw-d.width)/2;
		}
		m.move(cw+c, h);
		m.resize(d.width, d.height);
		h += d.height;
	    }
	}
    }
};


public class Gas extends Applet
  implements ComponentListener, ActionListener, AdjustmentListener {
    
    Thread engine = null;
    int molcount;

    Dimension winSize;
    Image dbimage;
    
    public static final int defaultPause = 10;
    public static final int wallSegments = 64;
    int heaterSize;
    int pause;
    Random random;
    
    public String getAppletInfo() {
	return "Plyn Peter Ballo";
    }

    double mx[];
    double my[];
    double mdx[];
    double mdy[];
    double walltemps_left[];
    double walltemps_right[];
    double walltemps_top[];
    double walltemps_bottom[];
    short grid[][];
    boolean stopped;
    Button stopButton;
    Button startButton;
    Button resetButton;
    Button equalButton;
    Button extremeButton;
    Button wallTempButton;
    Scrollbar volumeBar;
    Scrollbar tempBar;
    Scrollbar gravityBar;
    double gravity;
    int upperBound;
    int areaHeight;
    double heatstate;
    double temp;
    double tempMove;
    Color tempcolor;
    Color colors[];
    int heaterTop;
    int heaterLeft;
    int heaterRight;

    int getrand(int x) {
	int q = random.nextInt();
	if (q < 0) q = -q;
	return q % x;
    }
    GasCanvas cv;
    HistogramCanvas hist_cv;

    public void init() {
	int ci = 0;
	heatstate = 0;
	colors = new Color[16];
	colors[ci++] = new Color(46,60,255);
	colors[ci++] = new Color(79,88,254);
	colors[ci++] = new Color(113,116,253);
	colors[ci++] = new Color(147,145,252);
	colors[ci++] = new Color(181,105,178);
	colors[ci++] = new Color(215,64,103);
	colors[ci++] = new Color(249,23,28);
	colors[ci++] = new Color(250,101,44);
	colors[ci++] = new Color(251,139,33);
	colors[ci++] = new Color(252,178,22);
	colors[ci++] = new Color(253,216,11);
	colors[ci++] = new Color(255,255,0);
	colors[ci++] = new Color(255,255,63);
	colors[ci++] = new Color(255,255,127);
	colors[ci++] = new Color(255,255,191);
	colors[ci++] = new Color(255,255,255);
	stopped = false;
	gravity = .001;
	//setLayout(new GridLayout(3, 1, 10, 10));
	setLayout(new GasLayout());
	//setLayout(new GridBagLayout());
	cv = new GasCanvas(this);
	cv.addComponentListener(this);
	add(cv);
	add(stopButton = new Button("Stop"));
	stopButton.addActionListener(this);
	add(startButton = new Button("Start"));
	startButton.addActionListener(this);
	add(resetButton = new Button("Znovu"));
	resetButton.addActionListener(this);
	add(equalButton = new Button("Znovu do rovnovahy"));
	equalButton.addActionListener(this);
	add(extremeButton = new Button("Znovu do extremu"));
	extremeButton.addActionListener(this);
	add(wallTempButton = new Button("Nastav teplotu steny"));
	wallTempButton.addActionListener(this);
	add(new Label("Objem", Label.CENTER));
	add(volumeBar = new Scrollbar(Scrollbar.HORIZONTAL, 100, 1, 10, 100));
	volumeBar.addAdjustmentListener(this);
	add(new Label("Teplota ohrievaca", Label.CENTER));
	add(tempBar = new Scrollbar(Scrollbar.HORIZONTAL, 15, 1, 0, 100));
	tempBar.addAdjustmentListener(this);
	add(new Label("Gravitacia", Label.CENTER));
	add(gravityBar = new Scrollbar(Scrollbar.HORIZONTAL, 20, 1, 0, 100));
	gravityBar.addAdjustmentListener(this);
	add(new Label("Histogram rychlosti", Label.CENTER));
	hist_cv = new HistogramCanvas(this);
	hist_cv.addComponentListener(this);
	add(hist_cv);
	cv.setBackground(Color.black);
	cv.setForeground(tempcolor = Color.lightGray);
	hist_cv.setBackground(Color.black);
	hist_cv.setForeground(tempcolor = Color.lightGray);
	random = new Random();
	pause = defaultPause;
	adjustTemp();
	try {
	    String param = getParameter("PAUSE");
	    if (param != null)
		pause = Integer.parseInt(param);
	} catch (Exception e) { }
	reinit(0);
	repaint();
    }

    void reinit(int type) {
        Dimension d = winSize = cv.getSize();
	if (winSize.width == 0)
	    return;
	molcount = (winSize.width*winSize.height)/200; // tu nastav pocet molekul
	upperBound = winSize.height * (100-volumeBar.getValue()) / 100;
	areaHeight = winSize.height-upperBound;
	mx = new double[molcount];
	my = new double[molcount];
	mdx = new double[molcount];
	mdy = new double[molcount];
	walltemps_left = new double[wallSegments];
	walltemps_right = new double[wallSegments];
	walltemps_top = new double[wallSegments];
	walltemps_bottom = new double[wallSegments];
	dbimage = createImage(d.width, d.height);
	grid = new short[winSize.width][winSize.height];
	int i, j;
	for (i = 0; i != winSize.width; i++)
	    for (j = 0; j != winSize.height; j++)
		grid[i][j] = -1;
	for (i = 0; i != wallSegments; i++)
	    walltemps_left[i] = walltemps_right[i] =
	    walltemps_top[i] = walltemps_bottom[i] = .5;
	for (i = 0; i != molcount; i++) {
	    mx[i] = getrand(winSize.width);
	    my[i] = getrand(areaHeight)+upperBound;
	    mdx[i] = (getrand(5)/4.0-.5);
	    if (type != 0) {
		mdy[i] = java.lang.Math.sqrt(1-mdx[i]*mdx[i]);
		if (getrand(10) > 4)
		    mdy[i] = -mdy[i];
		if (type == 2) {
		    double q = ((i & 1) > 0) ? 3 : .2;
		    mdx[i] *= q;
		    mdy[i] *= q;
		}
	    } else {
		mdy[i] = (getrand(5)/4.0-.5);
	    }
	    grid[(int)mx[i]][(int)my[i]] = (short)i;
	}
	heaterTop = winSize.height-5;
	heaterSize = winSize.width/9;
	heaterLeft = (winSize.width-heaterSize*3)/2;
	heaterRight = (winSize.width+heaterSize*3)/2;
    }

    public void updateGas(Graphics realg) {
	if (winSize == null)
	    return;
	Graphics g = dbimage.getGraphics();
	g.setColor(cv.getBackground());
	g.fillRect(0, 0, winSize.width, winSize.height);
	int j;
	for (short i = 0; i != molcount; i++) {
	    double x = mx[i];
	    double y = my[i];
	    double dx = mdx[i];
	    double dy = mdy[i];
	    boolean bounce = false;
	    int ix = (int) x;
	    int iy = (int) y;
	    j = (stopped) ? 5 : 0;
	    for (; j < 5; j++) {
		dy += gravity;
		x += dx;
		y += dy;
		if (x < 0 || x >= winSize.width) {
		    dx = -dx;
		    if (x < 0) x = 0;
		    if (x >= winSize.width)
			x = winSize.width-1;
		    double v = java.lang.Math.sqrt(dx*dx+dy*dy);
		    int wallidx = iy*wallSegments/winSize.height;
		    double walltemp = (x == 0) ?
			walltemps_left[wallidx] :
			walltemps_right[wallidx];
		    double wto = walltemp;
		    walltemp = walltemp * .8 + v*.2;
		    //System.out.print("v " + v + " wto " + wto +
		    //	     " teplota steny " + walltemp + "\n");
		    double mix = getrand(10)/9.0;
		    double newv = mix + walltemp/v*(1-mix);
		    dx *= newv;
		    dy *= newv;
		    if (x == 0)
			walltemps_left[wallidx] = walltemp;
		    else
			walltemps_right[wallidx] = walltemp;
		    bounce = true;
		}
		if (y < upperBound || y >= winSize.height) { // zrazka s piestom
		    dy = -dy;
		    if (y < upperBound) y = upperBound;
		    if (y >= winSize.height)
			y = winSize.height-1;
		    double v = java.lang.Math.sqrt(dx*dx+dy*dy);
		    int wallidx = ix*wallSegments/winSize.width;
		    double walltemp = (y == upperBound) ?
			walltemps_top[wallidx] :
			walltemps_bottom[wallidx];
		    double wto = walltemp;
		    walltemp = walltemp * .8 + v*.2;
		    //System.out.print("v " + v + " wto " + wto +
		    //	     " teplota steny " + walltemp + "\n");
		    double mix = getrand(10)/9.0;
		    double newv = mix + walltemp/v*(1-mix);
		    dx *= newv;
		    dy *= newv;
		    if (y == upperBound)
			walltemps_top[wallidx] = walltemp;
		    else
			walltemps_bottom[wallidx] = walltemp;
		    bounce = true;
		}
		int nix = (int) x;
		int niy = (int) y;
		if (!bounce && nix >= heaterLeft && nix <= heaterRight &&
		    niy >= heaterTop-1) {
		    double v = java.lang.Math.sqrt(dx*dx+dy*dy);
		    double mxv = temp;
		    double mix = getrand(10)/9.0;
		    double newv = v*mix + mxv*(1-mix);
		    dx = getrand(101)/50.0-1;
		    dy = -(1-dx*dx)*newv;
		    dx *= v;
		    bounce = true;
		    y = heaterTop-2;
		    niy = (int) y;
		}
		if (nix != ix || niy != iy) {
		    try {
			grid[ix][iy] = -1;
		    } catch (Exception e) {
			System.out.print(ix + " " + iy + "\n");
		    };
		    int q = grid[nix][niy];
		    if (q != -1 && !bounce) {
			x -= dx; y -= dy;
			double dx2 = mdx[q];
			double dy2 = mdy[q];
			double adx = (dx+dx2)/2;
			double ady = (dy+dy2)/2;
			//System.out.print("zrazka " + dx + " " + dy + " "
				//	 + dx2 + " " + dy2 + "\n");
			double v = java.lang.Math.
			    sqrt((dx-adx)*(dx-adx)+(dy-ady)*(dy-ady));
			dx = getrand(101)/50.0-1;
			dy = (1-dx*dx)*v;
			dx *= v;
			mdx[q] = -dx + adx;
			mdy[q] = -dy + ady;
			dx += adx;
			dy += ady;
		    } else {
			ix = nix; iy = niy;
		    }
		    grid[ix][iy] = i;
		}
	    }
	    mx[i] = x; my[i] = y; mdx[i] = dx; mdy[i] = dy;
	    int col = (int)((((dx<0) ? -dx : dx) + ((dy < 0) ? -dy : dy))*7);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.fillRect((int)x, (int)y, 2, 2);
	}
	int heatstateint = ((int) heatstate);
	for (j = 0; j != heaterSize; j++, heatstateint++) {
	    int x = heaterLeft + j*3;
	    int y = heatstateint & 3;
	    if ((heatstateint & 4) == 4) y = 4-y;
	    g.setColor(tempcolor);
	    g.fillRect(x, heaterTop+y, 2, 2);
	}
	for (j = 0; j != wallSegments; j++) {
	    int col = (int) (walltemps_left[j]*7.0);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.drawLine(0, j*winSize.height/wallSegments,
		       0, (j+1)*winSize.height/wallSegments-1);
	    col = (int) (walltemps_right[j]*7.0);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.drawLine(winSize.width-1, j*winSize.height/wallSegments,
		       winSize.width-1, (j+1)*winSize.height/wallSegments-1);
	    col = (int) (walltemps_top[j]*7.0);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.drawLine(j*winSize.width/wallSegments, upperBound,
		       (j+1)*winSize.width/wallSegments-1, upperBound);
	    col = (int) (walltemps_bottom[j]*7.0);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.drawLine(j*winSize.width/wallSegments, winSize.height-1,
		       (j+1)*winSize.width/wallSegments-1, winSize.height-1);
	}
        for (j = 0; j != wallSegments-1; j++) {
            double wt = (walltemps_left[j] + walltemps_left[j+1])/2;
            walltemps_left[j] = (walltemps_left[j]+wt)/2;
            walltemps_left[j+1] = (walltemps_left[j+1]+wt)/2;
            wt = (walltemps_right[j] + walltemps_right[j+1])/2;
            walltemps_right[j] = (walltemps_right[j]+wt)/2;
            walltemps_right[j+1] = (walltemps_right[j+1]+wt)/2;
            wt = (walltemps_top[j] + walltemps_top[j+1])/2;
            walltemps_top[j] = (walltemps_top[j]+wt)/2;
            walltemps_top[j+1] = (walltemps_top[j+1]+wt)/2;
            wt = (walltemps_bottom[j] + walltemps_bottom[j+1])/2;
            walltemps_bottom[j] = (walltemps_bottom[j]+wt)/2;
            walltemps_bottom[j+1] = (walltemps_bottom[j+1]+wt)/2;
        }
	for (j = heaterLeft*wallSegments/winSize.width;
	     j <= heaterRight*wallSegments/winSize.width; j++)
	    walltemps_bottom[j] = tempMove;
	g.setColor(Color.lightGray);
	g.fillRect(winSize.width/2 - 20, 0, 40, upperBound);
	realg.drawImage(dbimage, 0, 0, this);
	if (!stopped) {
	    heatstate += tempMove;
	    cv.repaint(pause);
	}
    }

    public void updateHistogram(Graphics realg) {
	if (winSize == null)
	    return;
	Dimension d = hist_cv.size();
	Graphics g = dbimage.getGraphics();
	g.setColor(hist_cv.getBackground());
	g.fillRect(0, 0, d.width, d.height);
	g.setColor(hist_cv.getForeground());
	int i;
	int slots = d.width/2;
	int graph[] = new int[slots];
	for (i = 0; i != slots; i++)
	    graph[i] = 0;
	int graphmax = 0;
	double maxv = 0;
	for (i = 0; i != molcount; i++) {
	    double q = java.lang.Math.sqrt(mdx[i]*mdx[i]+mdy[i]*mdy[i]);
	    if (q > maxv)
		maxv = q;
	}
        maxv += .5;
	for (i = 0; i != molcount; i++) {
	    double q = java.lang.Math.
		sqrt(mdx[i]*mdx[i]+mdy[i]*mdy[i]) * slots/maxv;
	    int r = (int) q;
	    if (r >= slots)
		continue;
	    graph[r]++;
	    if (graphmax < graph[r])
		graphmax = graph[r];
	}
	for (i = 0; i != slots; i++) {
	    if (graph[i] == 0)
		continue;
	    int y = d.height-(graph[i] * d.height / graphmax);
	    if (y == d.height)
		y--;
	    int col = (int) (i*7*maxv/slots);
	    if (col > 15) col = 15;
	    g.setColor(colors[col]);
	    g.fillRect(i*2, y, 2, d.height);
	}
	realg.drawImage(dbimage, 0, 0, this);
	hist_cv.repaint(1000);
    }

    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e){}
    public void componentResized(ComponentEvent e) {
	reinit(0);
	cv.repaint(100);
	hist_cv.repaint(100);
    }
    public void actionPerformed(ActionEvent e) {
	if (e.getSource() == startButton) {
	    stopped = false;
	    cv.repaint();
	}
	if (e.getSource() == stopButton) {
	    stopped = true;
	}
	if (e.getSource() == resetButton) {
	    stopped = false;
	    reinit(0);
	    cv.repaint();
	}
	if (e.getSource() == equalButton) {
	    stopped = false;
	    reinit(1);
	    cv.repaint();
	}
	if (e.getSource() == extremeButton) {
	    stopped = false;
	    reinit(2);
	    cv.repaint();
	}
	if (e.getSource() == wallTempButton) {
	    for (int j = 0; j != wallSegments; j++) {
		walltemps_left[j] = tempMove;
		walltemps_right[j] = tempMove;
		walltemps_top[j] = tempMove;
		walltemps_bottom[j] = tempMove;
	    }
	}
    }
    public void adjustmentValueChanged(AdjustmentEvent e) {
	if (e.getSource() == volumeBar) {
	    upperBound = winSize.height * (100-volumeBar.getValue()) / 100;
	    areaHeight = winSize.height - upperBound;
	}
	if (e.getSource() == gravityBar)
	    gravity = gravityBar.getValue() * (.001/20);
	if (e.getSource() == tempBar)
	    adjustTemp();
    }

    void adjustTemp() {
	temp = (tempBar.getValue() * .029111971)*3 + .01;
	tempMove = (tempBar.getValue() * .029111971) + .3;
	tempcolor = colors[(tempBar.getValue()*15)/100];
    }
}

