/* 2010-03-15
 */
package com.cwasa.javacv;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

import java.util.concurrent.TimeUnit;

//import com.xuggle.mediatool.IMediaWriter;
//import com.xuggle.mediatool.ToolFactory;
//import com.xuggle.xuggler.IRational;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;

import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Properties;
import javax.imageio.ImageIO;

import static com.cwasa.javacv.ServiceApp.DO_LOGGING;
import static com.cwasa.javacv.ServiceApp.DO_VIDEO;
import static com.cwasa.javacv.ServiceApp.DO_FRAMES;
import static com.cwasa.javacv.ServiceApp.IMG_TYPE;
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_PANIC;
import static org.bytedeco.ffmpeg.global.avutil.av_log_set_level;


public class VideoMaker {

	public static final String			VM_TAG = "VideoMaker/Consumer";

/** Writes the given message to the log. */
	//private void log(String msg)	{ System.out.println(msg); }
	private void log(String msg)	{ this.LOG.log(msg); }
/** Writes the standard prefix tag and then the given message to the log. */
	private void logt(String msg)	{ this.log(VM_TAG+msg); }
/** Conditionally writes the given message to the log. */
	private void logc(String msg)	{ if (DO_LOGGING) { this.log(msg); } }
/** Conditionally writes the standard prefix and the given message to the log. */
	private void logct(String msg)	{ if (DO_LOGGING) { this.logt(msg); } }

	private final int					W;
	private final int					H;
	private final int					FPS;

	private final Logging				LOG;

	private final String				DOT_MOV_PATH;
	private final FrameQueue			FRAME_QUEUE;

	public VideoMaker(
		int w, int h, int fps, String mpath, FrameQueue fq, Logging log) {

		this.LOG = log;

		this.W = w;  this.H = h;  this.FPS = fps;
		this.DOT_MOV_PATH = mpath;
		this.FRAME_QUEUE = fq;

		this.makeVideoFile();
	}
        
   
        private static FFmpegFrameRecorder recorder = null;
        private static final Java2DFrameConverter converter = new Java2DFrameConverter();

	protected void makeVideoFile() {

		try {
			// final IMediaWriter moviewrtr = (DO_VIDEO ? ToolFactory.makeWriter(DOT_MOV_PATH) : null);
			// final IRational FPS_IR = IRational.make(FPS, 1);
			// if (DO_VIDEO) {
			//	moviewrtr.addVideoStream(0, 0, FPS_IR, this.W, this.H);
			// }

			this.logt(" starts.");
			int f = 0;
			byte[] fpixels = this.FRAME_QUEUE.getNextFrame();
			while (fpixels != null) {
                            long t0 = System.nanoTime();
                            final BufferedImage FRAME = this.makeImage(fpixels);
                            final long T_MS = (long)(1000 * f / (double) FPS);
                            if (DO_VIDEO) {
                                // Add frame to video
                                int width = FRAME.getWidth();
                                int height = FRAME.getHeight();
                                // May need to crop for some JavaCV versions
                                int okWidth = width / 2 * 2;
                                int okHeight = height / 2 * 2;
                                // moviewrtr.encodeVideo(0, FRAME, T_MS, TimeUnit.MILLISECONDS);
                                if (recorder == null) {
                                    // Minimise logging
                                    av_log_set_level(AV_LOG_PANIC);
                                    recorder = new FFmpegFrameRecorder(DOT_MOV_PATH, okWidth, okHeight);
                                    // set codec
                                    recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
                                    //set frame rate
                                    recorder.setFrameRate(FPS);
                                    recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
                                    recorder.setFormat("mp4");
                                    recorder.start();
                                }
                                BufferedImage cropped = FRAME;
                                if (width != okWidth || height != okHeight) {
                                    cropped = FRAME.getSubimage(0, 0, okWidth, okHeight);
                                }
                                recorder.record(converter.getFrame(cropped));
                            }
                            if (DO_FRAMES) {
                                // Save frames to PNG files
                                putImage(DOT_MOV_PATH+".frames", f, FRAME);
                            }
                            this.FRAME_QUEUE.freeItem(fpixels);
                            long t1 = System.nanoTime();
                            int tgen = (int)((t1-t0)/(1000));
                            this.logct(String.format(" frame %d:  t-gen=%d", f, tgen));
                            //                 Consumer/MG frame 58nnn:  t-gen=42nnn
                            fpixels = this.FRAME_QUEUE.getNextFrame();
                            ++ f;
			}
			if (DO_VIDEO && recorder != null) {
                            // moviewrtr.close();
                            recorder.stop();
                            recorder.release();
                            // Ready to restart
                            recorder = null;
			}
			if (DO_FRAMES) {
                            Properties props = new Properties();
                            props.setProperty("Path", DOT_MOV_PATH);
                            props.setProperty("FrameCount", ""+f);
                            props.setProperty("Width", ""+this.W);
                            props.setProperty("Height", ""+this.H);
                            props.setProperty("FPS", ""+this.FPS);
                            File pFil = new File(DOT_MOV_PATH+".frames/Attributes.properties");
                            OutputStream oFil = new FileOutputStream(pFil);
                            props.store(oFil, "Video Generator Settings");
                            pFil = new File(DOT_MOV_PATH+".frames/Attributes.xml");
                            oFil = new FileOutputStream(pFil);
                            props.storeToXML(oFil, "Video Generator Settings");
			}
			this.logt(" done: N="+f);
		}
		catch (Throwable t) {
			this.logt(" failure: "+t.toString());
			for (StackTraceElement ste : t.getStackTrace()) {
				this.log(ste.toString());
			}
			//t.printStackTrace();
		}
	}
	
	private BufferedImage makeImage(byte[] pixels) throws IOException {

		BufferedImage bimg = new BufferedImage(this.W, this.H, IMG_TYPE);
		byte[] imgbytes = ((DataBufferByte) bimg.getRaster().getDataBuffer()).getData();

		// Image rows are still  vertically flipped (from the original
		// GL capture). So now we need to copy them to the buffered
		// image in reverse order.
		final int ROW_SZ = this.W * 3;
		final int TOTAL_SZ = this.H * ROW_SZ;
		final int LAST_ROW_IX = TOTAL_SZ - ROW_SZ;
		for (int y=0; y!=this.H; ++y) {
			// Increasing y goes down the image (but up the pixels).
			final int P_BASE = y * ROW_SZ;
			final int IMG_BASE = LAST_ROW_IX - P_BASE;
			System.arraycopy(pixels, P_BASE, imgbytes, IMG_BASE, ROW_SZ);
		}
		return bimg;

	}

	protected static void putImage(String DIR, int f, BufferedImage bimg) throws IOException {
		ImageIO.write(bimg, "png", imageFile(DIR, f));
	}

	protected static File imageFile(String DIR, int f) {
		File FIL = new File(imagePath(DIR, f));
		if (f == 0) {
			FIL.mkdirs();
		}
		return FIL;
	}

	protected static String imagePath(String DIR, int f) {
		final String F_TAG = String.format("%05d", f);
		return DIR+"/Frame"+F_TAG+".png";
	}

}
