package org.himinbi.media.protocol.c2d;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.media.*;
import javax.media.format.*;
import javax.media.protocol.*;
import java.io.IOException;
import org.himinbi.util.*;
import org.himinbi.media.util.*;
import org.apache.log4j.*;

public abstract class CanvasImageStream extends JPanel implements PushBufferStream {
    int sequenceNumber = 0;
    float frameRate;
    BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
    Format format;
    Control[] controls = new Control[0];
    ContentDescriptor contentDescriptor = new ContentDescriptor(ContentDescriptor.RAW);
    BufferTransferHandler transferHandler;// = new FakeTransferHandler();
    Runner runner;
    static Logger log = Logger.getLogger("CIS");

    {
        log.debug("CanvasImageStream created:");
	addComponentListener(new ComponentAdapter() {
		public void componentResized(ComponentEvent e) {
		    resetFormat();
		    image = new BufferedImage(getSize().width, getSize().height, BufferedImage.TYPE_INT_RGB);
		}
	    });
	try {
	    runner = new Runner(this, getClass().getMethod("pushFrame", (Class[])null));
	} catch(NoSuchMethodException e) {
	}
    }

    public CanvasImageStream() {
	this(20);
    }

    public CanvasImageStream(MediaLocator locator) {
	if(locator != null && locator.getRemainder().length() > 0) {
	    try {
		setFrameRate(Float.parseFloat(locator.getRemainder()));
	    } catch(NumberFormatException e) {
	    }
	}
    }

    public CanvasImageStream(float frameRate) {
	setFrameRate(frameRate);
    }

    public void setFrameRate(float frameRate) {
	this.frameRate = frameRate <= 0 ? 1 : frameRate;
	log.info("Set framerate to: " + this.frameRate + " and interval to: " + (1000 / frameRate));
	runner.setInterval((long)(1000 / frameRate));
	resetFormat();
    }

    private void resetFormat() {
	format = new RGBFormat(getSize(), getSize().width * getSize().height * 3,
			       Format.intArray,
			       frameRate,
			       32,                    // Bits per pixel
			       0xFF0000,              // Red mask
			       0x00FF00,              // Green mask
			       0x0000FF,              // Blue mask
			       1,                     // Pixel stride
			       getSize().width,       // Line stride
			       Format.FALSE,          // Format flipped
			       Format.NOT_SPECIFIED); // Endian
	log.debug("Format set to:" + format);
    }

    public void paint(Graphics g) {
	synchronized(image) {
	    paintBuffer(image.createGraphics());
	    ((Graphics2D)g).drawImage(image, null, 0, 0);
	}
    }

    public abstract void paintBuffer(Graphics2D g);

    public ContentDescriptor getContentDescriptor() {
	return contentDescriptor;
    }

    public long getContentLength() {
	return LENGTH_UNKNOWN;
    }

    public boolean endOfStream() {
	return false;
    }

    public Format getFormat() {
	return format;
    }

    public void read(Buffer buffer) throws IOException {
	log.info("Reading: " + sequenceNumber);
	synchronized(image) {
	    Object data = buffer.getData();
	    int dataLength = image.getWidth() * image.getHeight() * 3;
	    if(data == null || data.getClass() != Format.intArray || ((int[])data).length <= dataLength) {
		data = new int[dataLength];
		buffer.setData(data);
	    }
	    buffer.setOffset(0);
	    buffer.setHeader(null);
	    buffer.setFormat(format);
	    buffer.setLength(dataLength);
	    buffer.setFlags(buffer.getFlags() | Buffer.FLAG_KEY_FRAME);

	    buffer.setTimeStamp((long)(sequenceNumber * (1000 / frameRate) * 1000000));
	    buffer.setSequenceNumber(sequenceNumber);

	    image.getRGB(0, 0,                                // Start Y, Y
                         image.getWidth(), image.getHeight(), // Width, Height
                         (int[])data,                         // Destination
                         buffer.getOffset(),                  // Offset
                         image.getWidth());                   // Scanline stride
	    sequenceNumber++;
	}
    }

    public void setTransferHandler(BufferTransferHandler transferHandler) {
	log.debug("Setting transfer handler to: " + transferHandler);
	this.transferHandler = transferHandler;
    }

    public void setRunning(boolean running) {
	runner.setRunning(running);
    }

    public boolean isRunning() {
	return runner.isRunning();
    }

    public void pushFrame() {
	log.debug("Pushing frame: " + sequenceNumber);
	if(transferHandler != null) {
	    transferHandler.transferData(this);
	} else {
	    log.error("Dropped frame for lack of a transfer handler");
	}
    }

    public Object[] getControls() {
	return controls;
    }

    public Object getControl(String controlType) {
	try {
	    Class cls = Class.forName(controlType);
	    Object cs[] = getControls();
	    for(int i = 0; i < cs.length; i++) {
		if(cls.isInstance(cs[i])) {
		    return cs[i];
		}
	    }
	} catch(Exception e) {
	}
	return null;
    }
}