/**
 * QuickTimeお手軽キャプチャクラス
 * (c)2008 arc@dmz, A虎＠nyatla.jp
 * arc@digitalmuseum.jp
 * http://nyatla.jp/
 */
package jp.nyatla.nyartoolkit.qt.utils;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;

import javax.swing.Timer;

import quicktime.QTException;
import quicktime.QTSession;
import quicktime.io.QTFile;
import quicktime.qd.PixMap;
import quicktime.qd.QDConstants;
import quicktime.qd.QDGraphics;
import quicktime.qd.QDRect;
import quicktime.std.StdQTConstants;
import quicktime.std.movies.Movie;
import quicktime.std.movies.media.DataRef;
import quicktime.std.sg.SGVideoChannel;
import quicktime.std.sg.SequenceGrabber;
import quicktime.util.RawEncodedImage;

import jp.nyatla.nyartoolkit.NyARException;


public class QtCameraCapture implements ActionListener
{

	private Dimension image_size;
    private QtCaptureListener capture_listener;

	protected float fps = 30;				// キャプチャ画像を取得するfps
	protected byte[] pixels;				// キャプチャ画像の実データを保持するバイト型配列

	// キャプチャに使うタイマー
	private Timer timer;

	// QTJava用のあれこれ
	private QDGraphics graphics;
	private QDRect bounds;
	private SequenceGrabber grabber;
	private SGVideoChannel channel;
	private RawEncodedImage rawEncodedImage;
	private Movie movie;

	// ピクセルフォーマット変換用の一時変数
	private int[] pixels_int;
    
    public static final int PIXEL_FORMAT_RGB = quicktime.util.EndianOrder.isNativeLittleEndian() ?
			QDConstants.k32BGRAPixelFormat : QDGraphics.kDefaultPixelFormat;
    public QtCameraCapture(int i_width,int i_height,float i_rate)
    {
        image_size = new Dimension(i_width,i_height);
        fps = i_rate;
    }
    public Dimension getSize()
    {
	return image_size;
    }
    public byte[] readBuffer() throws NyARException
    {
	if(grabber==null){
            throw new NyARException();
	}
        return pixels;
    }
    public void setCaptureListener(QtCaptureListener i_listener) throws NyARException
    {
	if(grabber!=null){
	    throw new NyARException();
	}
	capture_listener=i_listener;
	
    }

    public void prepSetInput(Object input) throws QTException {
		QTSession.open();
		bounds = new QDRect(image_size.width, image_size.height);
		graphics = new QDGraphics(
				quicktime.util.EndianOrder.isNativeLittleEndian() ?
						QDConstants.k32BGRAPixelFormat : QDGraphics.kDefaultPixelFormat,
				bounds);
		if (input != null && input.getClass().equals(File.class)) {
			movie = quicktime.std.movies.Movie.fromDataRef(
				new DataRef(new QTFile((File) input)),
				StdQTConstants.newMovieActive
			);
		} else {
			grabber = new SequenceGrabber();
			grabber.setGWorld(graphics, null);
			channel = new SGVideoChannel(grabber);
			channel.setBounds(bounds);

			// seqGrabPreview == 2, Processingでmagic numberとしてハードコートされていた…
			channel.setUsage(StdQTConstants.seqGrabPreview);

			if (input != null) {
				try {
					channel.setDevice(input.toString());
				} catch (QTException e) {
					e.printStackTrace();
					throw e;
				}
			}
		}
	}

	/**
	 * Webcamの設定ダイアログを表示する。
	 * 既定のWebcamでは駄目な場合(複数のWebcamが接続されているPCなど)ではこれを実行するとよい。
	 */
	public void prepShowDialog() throws QTException { channel.settingsDialog(); }

    public void start() throws NyARException
    {
		try {

			if (grabber == null) prepSetInput(null);

			if (movie == null) {
				grabber.prepare(true, false); // あってもなくてもよさそう
				grabber.startPreview();
			} else {
				movie.preroll(0, 1.0f);
				while (movie.maxLoadedTimeInMovie() == 0)
					movie.task(100);
				movie.setRate(1);
				movie.getPict(movie.getTime()).draw(graphics, bounds);
			}
			PixMap pixmap = graphics.getPixMap();
			rawEncodedImage = pixmap.getPixelData();

			image_size.width = rawEncodedImage.getRowBytes() / 4;
			pixels = new byte[image_size.width * image_size.height * 3];
			pixels_int = new int[image_size.width * image_size.height];
		} catch (QTException e) {
			QTSession.close();
			throw new NyARException(e);
		}

		// キャプチャイメージを定期的に更新するタイマー
		timer = new Timer((int) (1000/fps), this);
		timer.start();
    }
    public void stop()
    {
        finalize();
    }

	/** タイマー処理。キャプチャイメージの更新結果をリスナに伝える。 */
	public void actionPerformed(ActionEvent event) {

		// 画像をQTJavaのRawEncodedImageとして取得
		try {
			if (movie == null) {
				grabber.idle();
			} else {
				if (movie.isDone()) movie.goToBeginning();
				movie.getPict(movie.getTime()).draw(graphics, bounds);
			}
		} catch (QTException e) {
			QTSession.close();
			e.printStackTrace();
		}

		// RawEncodedImageをint列に落とし込む
		rawEncodedImage.copyToArray(0, pixels_int, 0, pixels_int.length);

		// バイト列を生成する
		int idx_byte = 0;
		for (int idx = 0; idx < image_size.width*image_size.height; idx ++) {
			pixels[idx_byte ++] = (byte) (pixels_int[idx] >> 16);
			pixels[idx_byte ++] = (byte) (pixels_int[idx] >> 8 & 0xff);
			pixels[idx_byte ++] = (byte) (pixels_int[idx] & 0xff);
		}

		// 各リスナに更新されたバイト列を渡す
		capture_listener.onUpdateBuffer(pixels);
	}

    protected void finalize()
    {
		try {
			if (movie == null) {
				grabber.stop();
				grabber.release();
				grabber.disposeChannel(channel);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			QTSession.close();
		}
		timer.stop();
    }

}
