/**
 * This package provides classes to handle video in Processing. The API is compatible with the built-in video library of Processing. 
 * GSVideo uses the multimedia toolkit GStreamer (http://www.gstreamer.net/)  through the gstreamer-java bindings by Wayne Meissener:
 * http://code.google.com/p/gstreamer-java/ 
 * @author Andres Colubri
 * @version 0.4.5
 *
 * Copyright (c) 2008 Andres Colubri
 *
 * This source 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; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This code 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.
 * 
 * A copy of the GNU General Public License is available on the World
 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
 * obtain it by writing to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package codeanticode.gsvideo;
import processing.core.*;

import java.io.*;
import java.nio.*;
import java.lang.reflect.*;

import org.gstreamer.*;
import org.gstreamer.elements.*;

/**
 * Class for storing and manipulating video frames from an attached capture device such as a camera.
 */
public class GSCapture extends PImage implements PConstants 
{
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * The string and integer properties are set as empty, and framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, new String[] { }, new int[] { }, new String[] { }, new String[] { }, 25);	
    }
  
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * Integer properties are specified. The string properties are set as empty. Framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param intPropNames String[]
     * @param intPropValues int[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] intPropNames, int[] intPropValues)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, intPropNames, intPropValues, new String[] { }, new String[] { }, 25);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * String properties are specified. The integer properties are set as empty. Framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param strPropNames String[]
     * @param strPropValues String[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] strPropNames, String[] strPropValues)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, new String[] { }, new int[] { }, strPropNames, strPropValues, 25);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * String and integer properties are specified. Framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param strPropNames String[]
     * @param strPropValues String[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, intPropNames, intPropValues, strPropNames, strPropValues, 25);
    }
  
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * The string and integer properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, int frameRate)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, new String[] { }, new int[] { }, new String[] { }, new String[] { }, frameRate);
    }

    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * Integer properties are specified, as well as the framerate. The string properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] intPropNames, int[] intPropValues, int frameRate)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, intPropNames, intPropValues, new String[] { }, new String[] { }, frameRate);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     * String properties are specified, as well as the framerate. The integer properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param strPropNames String[]
     * @param strPropValues String[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, new String[] { }, new int[] { }, strPropNames, strPropValues, frameRate);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight. The default source GStreamer element for each Operating System is used: v4l2src for Linux, qtwrappersrc for OSX and dshowvideosrc for windows.
     *Integer and string properties are specified, as well as the framerate.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param strPropNames String[]
     * @param strPropValues String[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        initOS(requestWidth, requestHeight, intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
    }	

    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. The string and integer properties are set as empty. Framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName)
    {
        this(parent, requestWidth, requestHeight, sourceName, new String[] { }, new int[] { }, new String[] { }, new String[] { }, 25);
    }

    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. Integer properties are specified. The string properties are set as empty and framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param intPropNames String[]
     * @param intPropValues int[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] intPropNames, int[] intPropValues)
    {
        this(parent, requestWidth, requestHeight, sourceName, intPropNames, intPropValues, new String[] { }, new String[] { }, 25);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. String properties are specified. The integer properties are set as empty and framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param strPropNames String[]
     * @param strPropValues String[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] strPropNames, String[] strPropValues)
    {
        this(parent, requestWidth, requestHeight, sourceName, new String[] { }, new int[] { }, strPropNames, strPropValues, 25);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. Integer and string properties are specified. Framerate is set to 25.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param strPropNames String[]
     * @param strPropValues String[]
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues)
    {
        this(parent, requestWidth, requestHeight, sourceName, intPropNames, intPropValues, strPropNames, strPropValues, 25);
    }

    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. The string and integer properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, int frameRate)
    {
        this(parent, requestWidth, requestHeight, sourceName, new String[] { }, new int[] { }, new String[] { }, new String[] { }, frameRate);
    }

    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. Integer properties are specified, as well as the framerate. The string properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] intPropNames, int[] intPropValues, int frameRate)
    {
        this(parent, requestWidth, requestHeight, sourceName, intPropNames, intPropValues, new String[] { }, new String[] { }, frameRate);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. String properties are specified, as well as the framerate. The integer properties are set as empty.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param strPropNames String[]
     * @param strPropValues String[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        this(parent, requestWidth, requestHeight, sourceName, new String[] { }, new int[] { }, strPropNames, strPropValues, frameRate);
    }
	
    /**
     * Creates an instance of GSCapture with width requestWidth and height requestHeight, for an source GStreamer element sourceName. Integer and string properties are specified, as well as the framerate.
     * @param parent PApplet
     * @param requestWidth int
     * @param requestHeight int
     * @param sourceName String
     * @param intPropNames String[]
     * @param intPropValues int[]
     * @param strPropNames String[]
     * @param strPropValues String[]
     * @param frameRate int
     */
    public GSCapture(PApplet parent, int requestWidth, int requestHeight, String sourceName, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        super(requestWidth, requestHeight, RGB);
        this.parent = parent;
        init(requestWidth, requestHeight, sourceName, intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
    }

    /**
     * Reads the current video frame.
     */
    public void read() 
    {
        if (firstFrame) 
        {
            //super.init(captureWidth, captureHeight, RGB);
            loadPixels();
            firstFrame = false;
        }		
        capturePixels.get(pixels);
        updatePixels();
	
        available = false;
    }

    /**
     * Get a list of all available captures as a String array.
     * i.e. println(Capture.list()) will show you the goodies.
     * NOT IMPLEMENTED.
     * @return String[]
     */  
    static public String[] list() 
    {
        String listing[] = new String[0];
        return listing;  
    }
 
    /**
     * Set the frameRate for how quickly new frames are read
     * from the capture device.
     * NOT IMPLEMENTED.
     * @param iframeRate int
     */
    public void frameRate(int iframeRate) 
    {
    }
 
    /**
     * Returns "true" when a new video frame is available to read.
     * @return String[]
     */  
    public boolean available() 
    {
        return available;
    }

    /**
     * Resumes the capture pipeline.
     */
    public void resume()
    {
        gpipe.setState(State.PLAYING);
    }

    /**
     * Stops the capture pipeline.
     */  
    public void stop()
    {
        gpipe.setState(State.PAUSED);  
    }
   
    /**
     * @invisible
     */
    protected void invokeEvent(int w, int h, IntBuffer buffer) 
    {
        available = true;  
        captureWidth = w;
        captureHeight = h;
        capturePixels = buffer;
	  
        // Creates a movieEvent.
        if (captureEventMethod != null) 
        {
            try 
            {
                captureEventMethod.invoke(parent, new Object[] { this });
            } 
            catch (Exception e) 
            {
                System.err.println("error, disabling captureEvent() for videotestsrc");
                e.printStackTrace();
                captureEventMethod = null;
            }
        }
    }

    /**
     * @invisible
     */
    public void dispose() 
    {
    } 

    /**
     * @invisible
     */
    protected void initOS(int requestWidth, int requestHeight, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        if (gsvideo.OS() == gsvideo.LINUX) 
        {
            init(requestWidth, requestHeight, "v4l2src", intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
        }
        else if (gsvideo.OS() == gsvideo.MACOS) 
        {
            init(requestWidth, requestHeight, "osxvideosrc", intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
        }
        else if (gsvideo.OS() == gsvideo.WINDOWS) 
        {
            init(requestWidth, requestHeight, "ksvideosrc", intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
        	//init(requestWidth, requestHeight, "dshowvideosrc", intPropNames, intPropValues, strPropNames, strPropValues, frameRate);
        }
	    else 
        {
            parent.die("Error: unrecognized platform.", null);
        }
    }

    /**
     * @invisible
     */
    protected void init(int requestWidth, int requestHeight, String sourceName, String[] intPropNames, int[] intPropValues, String[] strPropNames, String[] strPropValues, int frameRate)
    {
        gpipe = null;
	
        gsvideo = new GSVideo();
        gsvideo.init();

        // register methods
        parent.registerDispose(this);

        try 
        {
            captureEventMethod = parent.getClass().getMethod("captureEvent", new Class[] { GSCapture.class });
        } 
        catch (Exception e) 
        {
            // no such method, or an error.. which is fine, just ignore
        }
	  
        gpipe = new Pipeline("GSCapturePipeline");
	  
        Element videoSource = ElementFactory.make(sourceName, "Source");
	
        if (intPropNames.length != intPropValues.length) 
        {
            parent.die("Error: number of integer property names is different from number of values.", null);
        }
	
        for (int i = 0; i < intPropNames.length; i++) 
        {
            videoSource.set(intPropNames[i], intPropValues[i]);
        }

        if (strPropNames.length != strPropValues.length) 
        {
            parent.die("Error: number of string property names is different from number of values.", null);
        }		
		
        for (int i = 0; i < strPropNames.length; i++) 
        {
            videoSource.set(strPropNames[i], strPropValues[i]);
        }
		
        // conv is needed for dshowvideosrc to work, but is incompatible with matroxcapture... FIX!
        Element conv = ElementFactory.make("ffmpegcolorspace", "ColorConverter");
	
        Element videofilter = ElementFactory.make("capsfilter", "ColorFilter");
        videofilter.setCaps(new Caps("video/x-raw-rgb, width=" + requestWidth + ", height=" + requestHeight + ", bpp=32, depth=24"));
	
        RGBDataSink videoSink = new RGBDataSink("rgb",
            new RGBDataSink.Listener() 
            {
                public void rgbFrame(int w, int h, IntBuffer buffer) 
                {
                    invokeEvent(w, h, buffer);
                }
            }	
        );
	  
        gpipe.addMany(videoSource, conv, videofilter, videoSink);
        Element.linkMany(videoSource, conv, videofilter, videoSink);  

        //gpipe.addMany(videoSource, videofilter, videoSink);
        //Element.linkMany(videoSource, videofilter, videoSink);  	
	
        gpipe.setState(State.PLAYING);  
    }

    /**
     * @invisible
     */
    protected Method captureEventMethod; 
  
    /**
     * @invisible
     */
    protected boolean available;

    /**
     * @invisible
     */
    protected boolean firstFrame = true;
  
    /**
     * @invisible
     */
    protected int captureWidth;

    /**
     * @invisible
     */
    protected int captureHeight;

    /**
     * @invisible
     */
    protected IntBuffer capturePixels;

    /**
     * @invisible
     */
    protected Pipeline gpipe;

    /**
     * @invisible
     */
    protected GSVideo gsvideo; 
}
