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


import java.io.IOException;
import java.io.InputStream;

import java.util.zip.GZIPInputStream;

import java.net.Socket;

import static com.cwasa.javacv.ServiceApp.DO_LOGGING;
import static com.cwasa.javacv.ServiceApp.SUFF;


public class VideoJobHandler {

	public static final String			VJ_TAG = "Video Job Handler";

/** 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(VJ_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 Logging				LOG;

	private final Socket				CC_SOCKET;
	private final InputStream			IN_FROM_CLIENT;
	private final byte[]				READ_BUF;

	private final int                               N_FRAMES;
	private final int				W;
	private final int				H;
	private final int				FPS;
	private String				DOT_MOV_PATH;

	private final FrameQueue			FRAME_QUEUE;

	private VideoMaker				videoGen;

	private boolean					videoGenDone;

	private final int				FRAME_BUF_SIZE;

/** Input byte count. */
	private int					nRead;

	private final Thread				VIDEO_GEN_THREAD = new Thread() {

		public void run() {

			VideoJobHandler THIS_VJH = VideoJobHandler.this;
			THIS_VJH.videoGen =
				new VideoMaker(
					THIS_VJH.W, THIS_VJH.H, THIS_VJH.FPS,
					THIS_VJH.DOT_MOV_PATH, THIS_VJH.FRAME_QUEUE,
					THIS_VJH.LOG);
			THIS_VJH.notifyVideoGenDone();
		}
	};


	public VideoJobHandler(Socket ccsckt, Logging thelog)
	throws IOException, InterruptedException {

		this.LOG = thelog;
		this.logt(" starts.");

		long ta = System.nanoTime();

		this.CC_SOCKET = ccsckt;  // Client connection socket.
		//this.IN_FROM_CLIENT = this.CC_SOCKET.getInputStream();
		InputStream ccins = this.CC_SOCKET.getInputStream();
		this.IN_FROM_CLIENT = new GZIPInputStream(ccins);

		this.READ_BUF = new byte[4];

		this.N_FRAMES = this.readInt();
		this.W = this.readInt();
		this.H = this.readInt();
		this.FPS = this.readInt();
		this.DOT_MOV_PATH = this.readString();
                if (!this.DOT_MOV_PATH.endsWith(SUFF)) { this.DOT_MOV_PATH += SUFF; }

                this.LOG.log(String.format(
                        "VideoJobHandler: N=%d (W,H)=(%d,%d) FPS=%d  Video file: %s",
                        this.N_FRAMES, this.W, this.H, this.FPS, this.DOT_MOV_PATH));

		this.FRAME_BUF_SIZE = this.W * this.H * 3;
		this.FRAME_QUEUE = new FrameQueue(this.FRAME_BUF_SIZE);
		//this.FRAME_QUEUE = new FrameQueue(this.FRAME_BUF_SIZE, -1);

		this.nRead = 0;

		this.VIDEO_GEN_THREAD.start();

		// In this constructor thread we read frames from the client
		// and pass them on to the video generation thread.
		this.readFramesFromClient();

		this.logt(" receiver done: nRead="+this.nRead);

		this.awaitVideoGenDone();

		long tb = System.nanoTime();
		int tjob = (int)((tb-ta) / 1000);
		this.logt(String.format(" generator done:  t="+tjob));
	}

	private synchronized void notifyVideoGenDone() {

		this.videoGenDone = true;  this.notify();
	}

	private synchronized void awaitVideoGenDone()
	throws InterruptedException {

		while (!this.videoGenDone) { this.wait(); }
	}

	protected void readFramesFromClient()
	throws IOException, InterruptedException {

		for (int f=0; f!=this.N_FRAMES; ++f) {

			final byte[] F_PIXELS = this.FRAME_QUEUE.allocItem();

			//this.logc(" reading frame from client: "+f);

			long t0 = System.nanoTime();
			this.readFrameFromClient(F_PIXELS);
			long t1 = System.nanoTime();

			//this.logc(" queuing frame "+f);

			this.FRAME_QUEUE.addFrame(F_PIXELS);
			long t2 = System.nanoTime();

			if (DO_LOGGING) {
				int tget = (int)((t1-t0)/(1000));
				int tput = (int)((t2-t1)/(1000));
				this.logt(String.format(
					" frame %d:  tGet=%d  tPut=%d", f, tget, tput));
			}
		}

		this.FRAME_QUEUE.closeQueue();
	}

	protected void readFrameFromClient(byte[] pixels) throws IOException {

		int i = 0, ii = this.FRAME_BUF_SIZE;
		while (i != ii) {
			int n = this.IN_FROM_CLIENT.read(pixels, i, ii-i);
			if (0 < n) { this.nRead += n; i += n; }
			else { throw new IOException("Client input ends early"); }
		}
	}

	protected String readString() throws IOException {

		int n = this.readInt();
		char[] sbuf = new char[n];
		for (int i=0; i!=n; ++i) {
			int ch = this.readInt();
			if ((ch & 0xFFFF) != ch) {
				throw new IOException("Invalid string character.");
			}
			sbuf[i] = (char)ch;
		}

		return new String(sbuf);
	}

	protected int readInt() throws IOException {
		int ival = -1;
		try {
			final byte[] BUF = this.READ_BUF;
			final int ii = 4;
			int i = 0;
			while (i != ii) {
				int n = this.IN_FROM_CLIENT.read(BUF, i, ii-i);
				if (0 < n) { this.nRead += n; i += n; }
				else { throw new IOException("Client input ends early"); }
			}
			ival = BUF[3] << 8;
			ival |= (BUF[2] & 0xFF);  ival <<= 8;
			ival |= (BUF[1] & 0xFF);  ival <<= 8;
			ival |= (BUF[0] & 0xFF);
		}
		catch (IOException iox) {
			//System.out.println("####  Client IOException nRead="+this.nRead);
			this.log("####  Client IOException nRead="+this.nRead);
			throw iox;
		}
		return ival;
	}
}
