/** DropWaterRain Applet Copyright(c) 2003 WakuWaku. All rights reserved. */
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.URL;
import java.util.Random;

public final class DropWaterRain extends Applet implements Runnable, MouseMotionListener, MouseListener
{
	private static final int WEIGHT = 4, WEIGHTO = WEIGHT - 4, HSIZE = 2, ZM = 256; //ZMは波紋の精度です。2のべき乗で表せる数にすると処理が速くなります。
	private static final int[][] POW = new int[HSIZE * 2][HSIZE * 2];
	private static final int DEFAULT_MAX=80;	//デフォルトの雨量
	private static final int MAX_LENGTH = 10, MIN_LENGTH = 2;
	private boolean[] m_mask = null;
	private int[] m_pixels;
	private Image m_img;
	private String m_url = null, m_target;
	private int m_w, m_h, m_max;
	private Thread m_th = null;
	private boolean m_drag = false;
	private Color m_back, m_fore;
	private long m_lastTime;

	static
	{
		//座標間の長さを求める
		for (int x = 0;x < POW.length;x++)
			for (int y = 0;y < POW.length;y++)
				POW[x][y] = (int)Math.round(Math.sqrt((x - HSIZE) * (x - HSIZE) + (y - HSIZE) * (y - HSIZE)));
	}


	public void init()
	{
		if ((m_back = stringToColor(getParameter("BACK"))) == null)
			m_back = Color.black;
		if ((m_fore = stringToColor(getParameter("TEXT"))) == null)
			m_fore = Color.white;
		setBackground(m_back);
		Dimension d = getSize();					//アプレットのサイズ取得
		m_img = createImage(m_w = d.width, m_h = d.height);
		drawStringToImage(m_img, "ImageLoading...", m_fore, 10, 20);
		addMouseMotionListener(this);
		addMouseListener(this);
	}

	public void start()
	{
		if ((m_url = getParameter("URL")) != null)
			setCursor(new Cursor(Cursor.HAND_CURSOR));
		else
			setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
		if ((m_target = getParameter("TARGET")) == null)
			m_target = "_self";
		m_max = DEFAULT_MAX;
		String smax = getParameter("MAX");
		if(smax != null)
		{
			try
			{
				m_max = Integer.parseInt(smax);
			}catch(NumberFormatException e) {}
		}
		if (m_th == null)
			m_th = new Thread(this);
		m_th.start();	//スレッド開始
	}
	
	public void stop()
	{	//スレッド破棄
		if (m_th != null)
			m_th = null;
	}
	
	private void drawStringToImage(Image img, String text, Color fore,int x, int y)
	{
		Graphics g = img.getGraphics();
		g.clearRect(0, 0, img.getWidth(this), img.getHeight(this));
		g.setColor(fore);
		g.drawString(text, x, y);
		g.dispose();
	}
	
	private boolean[] getMask(Image img)
	{
		int[] px = getPixels(img, new Dimension());
		if(px == null)
			return null;
		boolean[] mask = new boolean[px.length];
		for(int i = 0; i < px.length; i++)
			mask[i] = ((px[i] & 0x00ffffff) > 0x888888);
		return mask;
	}
	
	public void run()
	{
		int r, g, b;
		int w, h;
		int py, pt, j, k, wait = 0;
		int dcount;
		boolean[] mask = null;
		Drop[] drops = makeDrops();
		Random rand = new Random();
		int[] pix, ipx;	//表示用画像配列
		int[] ipr, ipg, ipb;
		/*int cnt=0;
		long start=0,end=0;*/
		int f;

		try
		{
			Dimension d = new Dimension();
			ipx = getPixels(getImage(getCodeBase(), getParameter("IMG")), d);
			if(ipx == null)
			{
				drawStringToImage(m_img, "Error: Image cannot load.", m_fore, 10,20);
				repaint();
				return;
			}
			w = m_w = d.width;
			h = m_h = d.height;
		}catch(NullPointerException e)
		{
			Dimension d = getSize();
			w = m_w = d.width;
			h = m_h = d.height;
			ipx = new int[w*h];
			for(int i = 0; i < ipx.length; i++)
				ipx[i] = 0xff888888;
		}

		try
		{
			Dimension d=new Dimension();
			mask = getMask(getImage(getCodeBase(),getParameter("MASK")));
			if(mask.length != ipx.length)
			{
				drawStringToImage(m_img, "Error: MaskImage size.", m_fore, 10, 20);
				repaint();
				return;
			}
		}catch(NullPointerException e)
		{
			mask = new boolean[ipx.length];
			for(int i = 0; i < mask.length; i++)
				mask[i] = true;
		}
		m_mask = mask;
			
		ipr = new int[ipx.length];
		ipg = new int[ipx.length];
		ipb = new int[ipx.length];
		for(int i = 0; i < ipx.length; i++)
		{
			ipr[i]=(ipx[i]>>16)&0xff;
			ipg[i]=(ipx[i]>>8)&0xff;
			ipb[i]=ipx[i]&0xff;
		}

		pix = new int[w * h];

		//背景画像を表示用にコピー
		//実際に意味のあるのは端から1ドットのみ
		for (int i = 0; i < ipx.length; i++)
			pix[i] = ipx[i];

		m_pixels = new int[w * h];
		int[] px = m_pixels; // ローカル変数にコピーするだけでパフォーマンスup
		int[] pb = new int[px.length],	//前の状態用画像配列
			pm = new int[px.length];
		MemoryImageSource source = new MemoryImageSource(w, h,
								   new DirectColorModel(24, 0xff0000, 0x00ff00, 0x0000ff),
								   pix, 0, w);
		source.setAnimated(true);
		source.setFullBufferUpdates(true);
		m_img = createImage(source);	//配列から画像生成
		dcount = (int)(Math.random() * m_max / 10) + 3;
		wait = (int)(Math.random() * 300) + 200;
		m_lastTime = System.currentTimeMillis();
		boolean isPaint = true;

		Thread thisThread = Thread.currentThread();
		//メインルーチン
		
		while (thisThread == m_th)
		{
			// 定期的に降る量を変える
			if(wait-- < 0)
			{
				dcount = (int)(Math.random() * m_max);
				wait = (int)(Math.random() * 512) + 300;
			}
			
			//波紋処理
			for (int y = 1; y < h - 1; y++)
			{
				py = w * y;
				for (int x = 1; x < w - 1; x++)
				{
					j = py + x;
					if(mask[j])
					{
						f = pm[j] + (
								//+ px[j] * WEIGHTO		//中
								+ pb[j - w] / 2			//上
								+ px[j + w] / 2			//下
								+ px[j + 1] * 3 / 2		//右
								+ pb[j - 1] * 3 / 2		//左
							) / WEIGHT;
						f -= f / 64;
						pm[j] = f - (pb[j] = px[j]);
						px[j] = f;
					}else
					{
						px[j] = 0;
						pix[j] = ipx[j];
					}
					if(isPaint)
					{
						r = ipr[j] + px[j] / ZM;
						g = ipg[j] + px[j] / ZM;
						b = ipb[j] + px[j] / ZM;
						pix[j] = ((r>255?255:(r<0?0:r))<<16)
							|((g>255?255:(g<0?0:g))<<8)
							|((b>255?255:(b<0?0:b)));
					}
				}
			}
			
			// 雨粒処理
			for(int i = 0; i < drops.length; i++)
			{
				Drop d = drops[i];
				if(isPaint && d.y > 0 && d.y + d.length < h)
				{
					j = w * d.y + d.x;
					k = d.length;
					for(int y = 0; y < d.length; y++, j += w)
					{
						r = ipr[j] + d.pal[y];
						g = ipg[j] + d.pal[y];
						b = ipb[j] + d.pal[y];
						pix[j]=((r>255?255:(r<0?0:r))<<16)
								|((g>255?255:(g<0?0:g))<<8)
								|((b>255?255:(b<0?0:b)));
					}
				}
				if(wait % 2 == 0 && (i < dcount || d.y > 0))
					d.y += d.speed;
				if(d.y > d.end)
				{
					// 消える際に波紋生成
					makeWave(d.x, d.y + d.length
					  ,-d.speed * 64 * ZM);
					d.y = -(int)(Math.random() * h);
					d.x = (int)(Math.random() * (w - 2)) + 1;
				}
			}
			
			if (isPaint)
			{
				source.newPixels();
				sleepThread();
				repaint();		//描画
			}
			isPaint = !isPaint;
			/*cnt++;
			end=System.currentTimeMillis();
			if(end-start>1000)
			{
				showStatus(cnt+"fps");
				start=end;
				cnt=0;
			}*/
		}
	}
	
	private final synchronized void sleepThread()
	{
		Thread.yield();
		//getToolkit().sync();
		final long k = System.currentTimeMillis();
		final int w = (int)(20 - (k - m_lastTime));
		if (w > 0)
		{
			try
			{
				Thread.sleep(w);
			}
			catch (InterruptedException e)
			{}
		}
		else
		{
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException e)
			{}
		}
		m_lastTime = System.currentTimeMillis(); // k + w;
	}

	private final int getOff(int x, int y)
	{
		if (x < 0 || x >= m_w - 1)
			x = 0;
		if (y < 0 || y >= m_h - 1)
			y = 0;
		return y * m_w + x;
	}
	
	public void update(Graphics g)
	{
		paint(g);
	}
	
	public void paint(Graphics g)
	{
		g.drawImage(m_img, 0, 0, this);
	}
	
	public void mouseMoved(MouseEvent e)
	{ //マウスカーソルが動いた
		if (!m_drag && m_pixels != null)
			makeWave(e.getX(), e.getY(), -250*ZM);
	}
	
	public void mouseDragged(MouseEvent e)
	{ //マウスカーソルがドラッグされた
		m_drag = true;
		if (m_pixels != null)
			makeWave(e.getX(), e.getY(), -400*ZM);
	}
	
	public void mouseReleased(MouseEvent e)
	{
		m_drag = false;
	}
	
	public void mousePressed(MouseEvent e)
	{
		int y = e.getY();
		if (e.getModifiers() == e.BUTTON1_MASK)	//左クリック
		{
			if (m_url == null)
				makeWave(e.getX(), y, -600*ZM);
			else
			{
				try
				{
					getAppletContext().showDocument(new URL(m_url), m_target);
				}
				catch (Exception ex)
				{
					showStatus("Link Miss!!");
				}
			}
		}else
		{	//右クリック
			int w = m_w, h = m_h;
			int hh = y * w;
			int[] px = m_pixels;
			if (y < h - 3)
			{
				//縦波生成
				for (int i = 0; i < w - 1; i++)
					px[hh + i] = px[hh + w + i] = px[hh + w + w + i] = -128 * ZM;
			}
		}
	}
	
	public void mouseEntered(MouseEvent e)
	{
		if (m_url != null)
			showStatus(m_url);
	}
	
	public void mouseExited(MouseEvent e) {}
	public void mouseClicked(MouseEvent e){}

	private int[] getPixels(Image img, Dimension size)
	{//画像から配列作成
		int w, h;
		MediaTracker mt = new MediaTracker(this);
		mt.addImage(img, 0);
		try
		{
			mt.waitForAll();
			mt.removeImage(img);
			mt = null;
			w = img.getWidth(this);
			h = img.getHeight(this);
		}catch(InterruptedException e)
		{
			return null;
		}
		int[] pixels=new int[w * h];
		PixelGrabber pg=new PixelGrabber(img, 0, 0, w, h, pixels, 0, w);
		try
		{
			pg.grabPixels();
		}catch (InterruptedException e)
		{
			return null;
		}
		if((pg.getStatus() & ImageObserver.ABORT)!=0)
			return null;
		size.width = w;
		size.height = h;
		return pixels;
	}

	private static Color stringToColor(String paramValue)
	{ //"rrggbb" 形式の文字列をColorオブジェクトに変換
		int red;
		int green;
		int blue;

		try
		{
			red = (Integer.decode("0x" + paramValue.substring(0, 2))).intValue();
			green = (Integer.decode("0x" + paramValue.substring(2, 4))).intValue();
			blue = (Integer.decode("0x" + paramValue.substring(4, 6))).intValue();
			return new Color(red, green, blue);
		}
		catch (Exception e)
		{
			return null;
		}
	}

	private void makeWave(int mx, int my, int sz)
	{ //波紋生成
		final int[] px = m_pixels;
		final int[][] pow = POW;
		int r;
		for (int x = -HSIZE; x < HSIZE; x++)
			for (int y = -HSIZE; y < HSIZE; y++)
			{
				r = pow[x + HSIZE][y + HSIZE];
				if (r <= HSIZE)
					px[getOff(x + mx, y + my)] = sz >> r; //距離に応じて波の強さを減少
			}
	}
	
	private Drop[] makeDrops()
	{
		Drop[] drops = new Drop[m_max];
		Random rand = new Random();
		int[] pal;
		int c;
		int w = m_w, h = m_h;
		for(int i = 0; i < drops.length; i++)
		{
			pal = new int[(int)(rand.nextDouble() * MAX_LENGTH - MIN_LENGTH) + MIN_LENGTH];
			c = (int)(rand.nextDouble() * 140) + 50;
			for(int j = 0; j < pal.length; j++)
				pal[j] = c * (j + 1) / pal.length;
			drops[i] = new Drop((int)(rand.nextDouble() * (w - 2)) + 1,
								-(int)(rand.nextDouble() * (h - 1)),
								h * c / 190 - pal.length,
								pal);
		}
		return drops;
	}
}

class Drop
{
	int x;
	int y;
	int speed;
	int length;
	int[] pal;
	int end;
	
	Drop(int x, int y, int end, int[] pal)
	{
		this.x = x;
		this.y = y;
		this.pal = pal;
		this.length = pal.length;
		this.speed = pal[length - 1] / 46 + 1;
		this.end = end;
	}
}

