/**
 * 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.nio.*;
import org.gstreamer.*;
import org.gstreamer.elements.*;
import org.gstreamer.Buffer;

/**
 * This class makes movies from a running program. NOT WORKING YET.
 */
public class GSMovieMaker 
{
	boolean endStream;
	int pushedFrames;
	
	int movieWidth;
    int movieHeight;
  
    AppSrc buffersrc;
    AppSink buffersink;
    
    
    byte[] moviePixels;
  
    Pipeline gpipe;
  
    private PApplet parent;
    int width, height;
  
    public static final int THEORA = 0;  
    public static final int MPEG4 = 1;
/*
  public static final int WORST = 0;
  public static final int LOW = 1;
  public static final int MEDIUM = 2;
  public static final int HIGH = 3;
  public static final int BEST = 4;
  public static final int LOSSLESS = 5;
*/  
  
    int frameRate;
    int waitTime;

    public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight, String filename) 
    {
        this(parent, requestWidth, requestHeight, filename, MPEG4, 30);
    }
  
    public GSMovieMaker(PApplet parent, int requestWidth, int requestHeight, String filename, int codecType, int ifps) 
    {
        this.parent = parent;
        //parent.super.init(parent.width, parent.height, PConstants.RGB);
	   	   
        GSVideo.init();	   
	  
        // register methods
        parent.registerDispose(this);
	
        frameRate = ifps;
        Double t = 1000.0 / ifps;
        waitTime = t.intValue();
	
        width = parent.width;
        height = parent.height;
	
        //synchQueue = new SynchronousQueue<ByteBuffer>();

        
        movieWidth = requestWidth;
        movieHeight = requestHeight;
  
        gpipe = new Pipeline("GSMovieMaker");	  
	
        //final GSBufferSrc buffersrc = new GSBufferSrc(parent, synchQueue, waitTime, movieWidth, movieHeight, "pixelbuffer source");
      
        endStream = false;
        pushedFrames = 0;
        
        buffersrc = (AppSrc)ElementFactory.make("appsrc", "buffersrc");
        //buffersrc.setMaxBytes(movieWidth * movieHeight * 4 * 30);
        //buffersrc.setSize(movieWidth * movieHeight * 4 * 30);
        Caps srccaps = new Caps("video/x-raw-rgb, width=" + movieWidth + ", height=" + movieHeight + ", bpp=32, depth=24, framerate=" + frameRate + "/1");
        buffersrc.setCaps(srccaps);
        buffersrc.set("format", 3);
        
        /*
        final Element identity = ElementFactory.make("identity", "srcElement");
        buffersink = (AppSink)ElementFactory.make("appsink", "sinkElement");
        
        gpipe.addMany(buffersrc, identity, buffersink);
        Element.linkMany(buffersrc, identity, buffersink);       
        gpipe.setState(State.PLAYING);
        */
        
        
        
        final Element colorConv = ElementFactory.make("ffmpegcolorspace", "colorconverter");        
        final Element videofilter = ElementFactory.make("capsfilter", "srcfilter");
        Caps videoCaps = new Caps("video/x-raw-yuv, width=" + movieWidth + ", height=" + movieHeight + ", bpp=32, depth=24");
        videofilter.setCaps(videoCaps);

        final Element queue0 = ElementFactory.make("queue", "queue0");
        final Element videorate = ElementFactory.make("videorate", "videorate0");
        
        final Element fpsFilter = ElementFactory.make("capsfilter", "videoratefilter");
        Caps fpsCaps= new Caps("video/x-raw-yuv,framerate=" + frameRate + "/1");
        fpsFilter.setCaps(fpsCaps);
        
        final Element queue1 = ElementFactory.make("queue", "queue1");
        
	
        final Element videoenc;
        final Element muxer;
        if (codecType == THEORA) 
        {
            videoenc = ElementFactory.make("theoraenc",  "theoraenc");
            muxer = ElementFactory.make("oggmux", "oggmux");
        }
        else 
        {
            videoenc = ElementFactory.make("xvidenc",  "xvidenc");
            muxer = ElementFactory.make("avimux", "avimux");	
        }
	
        final Element queue2 = ElementFactory.make("queue", "queue2");
        
        Element videosink = ElementFactory.make("filesink", "sink");
        videosink.set("location", parent.savePath(filename));
	       
        gpipe.addMany(buffersrc, colorConv, videofilter, queue0, videorate, fpsFilter, videoenc, queue1, muxer, queue2, videosink);
        Element.linkMany(buffersrc, colorConv, videofilter, queue0, videorate, fpsFilter, videoenc, queue1, muxer, queue2, videosink);
        
        //gpipe.addMany(buffersrc, colorConv, videofilter, videoenc, muxer, videosink);
        //Element.linkMany(buffersrc, colorConv, videofilter, videoenc, muxer, videosink);


        
        //buffersrc.setState(State.PLAYING);
        gpipe.setState(State.PLAYING);
        
        /*
        // Creating bus to handle end-of-stream event.
        // Creating bus to handle end-of-stream event.
        buffersrc.getBus().connect(
            new Bus.MESSAGE() 
            {
                public void busMessage(Bus bus, Message message)
                { 
                	
                    PApplet.println("MESSAGE from " + message.getSource());
                }
            }
        );        
        */
        
    }
  
    public void addFrame() 
    {
    	if (!endStream)
    	{
        parent.loadPixels();
	
        // Converting int[] pixels array into byte array.
        //tmpBuffer.rewind();
        //tmpBuffer.asIntBuffer().put(parent.pixels);	
        
        Buffer srcBuffer = new Buffer(width * height * 4);
        Caps buffcaps = new Caps("video/x-raw-rgb, width=" + movieWidth + ", height=" + movieHeight + ", bpp=32, depth=24, framerate=" + frameRate + "/1");


        ByteBuffer tmpBuffer = srcBuffer.getByteBuffer();
        tmpBuffer.clear();
        tmpBuffer.asIntBuffer().put(parent.pixels);
        
        srcBuffer.setCaps(buffcaps);        
        srcBuffer.setOffset(0);
        long d = 1000 * NANOS_PER_MILISECOND / frameRate;
        srcBuffer.setDuration(ClockTime.fromNanos(d));
        long f = parent.millis() * NANOS_PER_MILISECOND;
        PApplet.println("DURATION: " + d);
        PApplet.println("TIME: " + f);
        srcBuffer.setTimestamp(ClockTime.fromNanos(f));
        
        
        buffersrc.pushBuffer(srcBuffer);
        srcBuffer.dispose();

        System.out.println("maxSize: " + buffersrc.getSize());        
        System.out.println("maxBytes: " + buffersrc.getMaxBytes());
        System.out.println("Done.");
        pushedFrames++;
        
    	}
        
        /*
        try
        {	
            parent.print("Offering buffer...");
            synchQueue.offer(tmpBuffer, waitTime, TimeUnit.MILLISECONDS);
            parent.println("Sent buffer.");
        }
        catch (InterruptedException ie)
        {
            ie.printStackTrace();
        }*/
        
    }

    public void getFrame()
    {
    	int n = 0;
   	    while (!buffersink.isEOS()) 
   	    {
   		    Buffer buf;
            buf = buffersink.pullBuffer();
            PApplet.println("retrieved buffer " + n + "(" + pushedFrames + ") " + buf.getByteBuffer().get(0));
            buf.dispose();
            n++;
       }
   	   PApplet.println("Number of pushed frames: " + pushedFrames);
   	   PApplet.println("Numer of retrieved frames:" + n);
    	
    	
    }
    
    public void restart() 
    { 
        gpipe.setState(State.PLAYING);
    }
  
    public void finish() 
    {
        gpipe.setState(State.PAUSED);
        //buffersrc.flushQueue();
        buffersrc.endOfStream();
        endStream = true;
        System.out.println("END OF STREAM");
    }
   
    public void dispose() 
    {
    } 
  
    static long NANOS_PER_MILISECOND = 100000;
}
