/* WCEURLImage.java
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package gnu.java.awt.peer.wce;

import java.awt.AWTError;
import java.awt.AWTException;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class WCEURLImage extends WCEImage {
	/**
	 * 非同期にロードするスレッド数
	 */
	private static final int LOADER_THREAD_COUNT = 3;
	
	/**
	 * ロード対象となるイメージのリスト。
	 * 非同期ロードスレッドがこのリストから順番にロードしていく
	 */
	static List imageList = new ArrayList();
	
	static {
		initIDs();

		// 非同期ロードスレッドを生成する
		for (int i = 0; i < LOADER_THREAD_COUNT; ++i) {
			Thread t = new Thread(new Loader());
			t.setDaemon(true);
			t.start();
		}
	}
	
	/**
	 * アニメーション用Timer
	 */
	private static final Timer animationTimer = new Timer();
	
	/**
	 * リモートからロード中であることを示すフラグ
	 */
	private boolean loading;
	
	/**
	 * リモートからのイメージロードが完了したかどうかを示すフラグ
	 */
	private boolean loaded;
	
	/**
	 * ローカルにテンポラリファイルを作成したかどうか
	 */
	private boolean tempFileCreated;
	
	/**
	 * ロード対象となるURL
	 */
	private URL url;
	
	/**
	 * ロード対象となるファイル名
	 */
	private String filename;
	
	/**
	 * 推奨Blt関数
	 * native関数が値を設定する
	 */
	int preferredBltAPI;
	
	/**
	 * 透明色
	 * native関数が値を設定する
	 */
	int transparentColor;
	
	/**
	 * ネイティブアニメーションデータ
	 */
	int animationData;
	
	/**
	 * 指定されたURLに対応したImageのインスタンスを作成する。
	 */
	public WCEURLImage(URL url) {
		this.url = url;
		if ("file".equals(url.getProtocol())) {
			// ローカルファイルの場合は、ファイル名を取得する
			this.filename = url.getPath();
			
			// イメージの内容は、すでにファイルにロード済み
			this.loaded = true;
		}
	}
	
	static native void initIDs();
	
	/**
	 * ネイティブアニメーションデータを削除する
	 */
	native void deleteNativeAnimationData(int data);
	
	/**
	 * ビットマップを作成する。
	 */
	protected int createBitmapHandle(int windowHandle) {
		int hBitmap = 0;
		if (this.loaded && this.filename != null) {
			// ローカルファイルまたはファイルがすでにロード済みの場合
			hBitmap = createNativeBitmapHandle(windowHandle,
											   this.filename);
			if (hBitmap == 0) {
				// 作成できなかった場合
				setFlags(ImageObserver.ERROR | ImageObserver.ABORT);
				notifyImageObservers(ImageObserver.ERROR | ImageObserver.ABORT, -1, -1, -1, -1);
			}
			
			synchronized (this) {
				if (this.tempFileCreated && this.filename != null) {
					// テンポラリファイルを削除する
					File file = new File(this.filename);
					if (file.delete()) {
						filename = null;
						this.tempFileCreated = false;
					} else {
						System.err.println("WARNING:WCEURLImage:failed to delete temp file:" + file.getAbsolutePath());
					}
				}
			}

		} else {
			// ロードを開始する
			startLoading();
		}
		return hBitmap;
	}
	
	public int getBitmapHandle() {
		boolean firstTime;
		if (this.bitmapHandle == 0) {
			firstTime = true;
		} else {
			firstTime = false;
		}
		
		int hBitmap = super.getBitmapHandle();
		
		if (firstTime && hBitmap != 0) {
			// ALLBITSフラグを追加する
			setFlags(getFlags() | ImageObserver.ALLBITS);
			if (this.animationData == 0) {
				notifyImageObservers(getFlags(), 0, 0, getWidth(), getHeight());
			} else {
				// FRAMEBITSフラグを追加する
				setFlags(getFlags() | ImageObserver.FRAMEBITS);

				// FRAMEBITSはnextAnimationFrame()内で通知される
				nextAnimationFrame();
			}
		}
		return hBitmap;
	}
	
	/**
	 * ロード処理を開始する
	 */
	private void startLoading() {
		if (! this.loading && ! this.loaded) {
			this.loading = true;
			
			// 別スレッドでロードを開始する
			synchronized (imageList) {
				// リストに追加した後、待機中のローダスレッドに通知
				imageList.add(this);
				imageList.notifyAll();
			}
		}
	}
	
	/**
	 * イメージファイルをロードする
	 */
	void load() {
		try {
			// URLからデータを読み込み、ファイルに出力する
			URLConnection conn = this.url.openConnection();
			InputStream ins = conn.getInputStream();
			BufferedInputStream bis = new BufferedInputStream(ins);
			
			// テンポラリファイルを作成する
			synchronized (this) {
				this.tempFileCreated = true;
				File tempFile = File.createTempFile("AWT", "img");
				FileOutputStream fos = new FileOutputStream(tempFile);
				BufferedOutputStream bos = new BufferedOutputStream(fos);
				
				byte[] buff = new byte[16 * 1024];
				int len;
				
				// SOMEBITS フラグを設定しておく
				setFlags(ImageObserver.SOMEBITS);
				
				// ファイルにデータを書き出す
				while ((len = bis.read(buff, 0, buff.length)) != -1) {
					bos.write(buff, 0, len);
				}
				bos.close();
				bis.close();
				
				// ファイル名を設定し、ロード完了フラグを立てる
				this.filename = tempFile.getAbsolutePath();
				this.loaded = true;
				
				// VM終了時に確実に削除されるようにする
				tempFile.deleteOnExit();
			}
			
			// 各ImageObserverにSOMEBITSを通知する
			notifyImageObservers(ImageObserver.SOMEBITS, -1, -1, -1, -1);
			
		} catch (IOException ioe) {
			ioe.printStackTrace();
			// エラーフラグを設定する
			setFlags(ImageObserver.ERROR | ImageObserver.ABORT);

			// ImageObserverにエラーを通知
			notifyImageObservers(ImageObserver.ERROR | ImageObserver.ABORT,-1,-1,-1,-1);

		} finally {
			// ロード中フラグをクリアする
			this.loading = false;
		}
	}
	
	/**
	 * 指定されたファイルから、ビットマップを作成する
	 */
	private native int createNativeBitmapHandle(int windowHandle,
												 String filename);
	
	public void flush() {
		super.flush();
		// テンポラリファイルを削除する
		if (this.filename != null && this.tempFileCreated) {
			File file = new File(filename);
			if (file.delete()) {
				this.filename = null;
				this.tempFileCreated = false;
			} else {
				System.err.println("WARNING:WCEURLImage:failed to delete temp file:" + file.getAbsolutePath());
			}
		}
		
		// アニメーションを破棄する
		if (this.animationData != 0) {
			deleteNativeAnimationData(this.animationData);
		}
	}

	/**
	 * 推奨Blt関数を返す
	 */
	public int getPreferredBltAPI() {
		return this.preferredBltAPI;
	}
	
	/**
	 * 透明色を返す
	 */
	public int getTransparentColor() {
		return this.transparentColor;
	}
	
	/**
	 * イメージロードを非同期に行う
	 */
	private static class Loader implements Runnable {
		public void run() {
			while (true) {
				try {
					WCEURLImage img = null;
					synchronized (WCEURLImage.imageList) {
						while (imageList.size() <= 0) {
							// ロード対象イメージが追加されるまで待機する
							WCEURLImage.imageList.wait();
						}
						img = (WCEURLImage) WCEURLImage.imageList.get(0);
						WCEURLImage.imageList.remove(0);
					}
					if (img != null) {
						img.load();
					}
				} catch (Throwable t) {
					// ThreadDeath以外ではこのスレッドは停止しない
					if (t instanceof ThreadDeath) {
						throw (ThreadDeath) t;
					}
					t.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * アニメーションを1コマ進める
	 */
	void nextAnimationFrame() {
		int duration = nextNativeAnimationFrame(this.animationData);
		if (duration != 0) {
			// タイマーを設定する
			WCEURLImage.animationTimer.schedule(new AnimationTask(), duration * 10);
		}
		// ImageObserverに通知する
		notifyImageObservers((duration == 0) ? ImageObserver.ALLBITS
											 : ImageObserver.FRAMEBITS,
							 0,
							 0,
							 0,
							 0);
	}

	/**
	 * ネイティブアニメーションを1コマ進める
	 *
	 * @return	次のフレームの表示時間（10ミリ秒単位）
	 */
	native int nextNativeAnimationFrame(int data);
	
	/**
	 * アニメーションタイマタスク
	 */
	class AnimationTask extends TimerTask {
		public void run() {
			nextAnimationFrame();
		}
	}
}
