/*
 * Copyright (c) 1993-1994 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and the Network Research Group at
 *      Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char rcsid[] =
    "@(#) $Header: /cs/research/mice/starship/src/local/CVS_repository/vic/grabber.cc,v 1.4 1999/01/14 12:48:43 ucackha Exp $ (LBL)";
#endif

#if defined(__osf__) || defined(__ultrix__)
/*XXX they didn't get this one right */
extern "C" {
#include <sys/types.h>
#include <sys/uio.h>
}
#else
#include <sys/types.h>
#ifndef WIN32
#include <sys/uio.h>
#endif
#endif

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "grabber.h"
#include "Tcl.h"
#include "crdef.h"

#if defined(sun) && !defined(__svr4__)
extern "C" int gettimeofday(struct timeval*, struct timezone*);
#endif

Grabber::Grabber()
	: status_(0), running_(0), delta_(0.),
	  crvec_(0), ref_(0), rover_(0),
	  vstart_(0), vstop_(0), 
	  hstart_(0), hstop_(0),
	  framebase_(0), frame_(0),
	  inw_(0), inh_(0), outw_(0), outh_(0),
	  threshold_(48),
	  target_(0), tx_(0)
{
	bps(128);
	fps(1);

	/*XXX*/
	idle_low_ = 2;
	idle_high_ = 2;

	/* CCIR 601 */
	ymin_ = 16;
	ymax_ = 235;
	contrast_ = 1.0;
	for (int i = 0; i < 256; ++i)
		ynorm_[i] = i;
}

Grabber::~Grabber()
{
	delete framebase_;
	delete crvec_;
	delete ref_;
}

int Grabber::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if (argc == 2) {
		if (strcmp(argv[1], "status") == 0) {
			sprintf(tcl.buffer(), "%d", status_);
			tcl.result(tcl.buffer());
			return (TCL_OK);
		}
		if (strcmp(argv[1], "need-capwin") == 0) {
			tcl.result("0");
			return (TCL_OK);
		}
	}
	if (argc == 3) {
		if (strcmp(argv[1], "send") == 0) {
			if (atoi(argv[2])) {
				if (!running_) {
					start();
					running_ = 1;
				}
			} else {
				if (running_) {
					stop();
					running_ = 0;
				}
			}
			return (TCL_OK);
		}
		if (strcmp(argv[1], "threshold") == 0) {
			threshold_ = 8*atoi(argv[2]);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "fps") == 0) {
			/*XXX assume value in range */
			fps(atoi(argv[2]));
			return (TCL_OK);
		}
		if (strcmp(argv[1], "bps") == 0) {
			/*XXX assume value in range */
			bps(atoi(argv[2]));
			return (TCL_OK);
		}
		if (strcmp(argv[1], "fillrate") == 0) {
			/*XXX assume value in range */
			fillrate(atoi(argv[2]));
			return (TCL_OK);
		}
		if (strcmp(argv[1], "target") == 0) {
			target_ = (Module*)TclObject::lookup(argv[2]);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "transmitter") == 0) {
			tx_ = (Transmitter*)TclObject::lookup(argv[2]);
			return (TCL_OK);
		}
		/*XXX*/
		if (strcmp(argv[1], "decimate") == 0 ||
		    strcmp(argv[1], "port") == 0 ||
		    strcmp(argv[1], "type") == 0 ||
			/* windows only */
			strcmp(argv[1], "useconfig") ==0)
			/* ignore */
			return (TCL_OK);
	}
	return (TclObject::command(argc, argv));
}

void Grabber::contrast(double c)
{
	/* map min to ccir-601 black (16) & max to ccir-601 white (235) */
	u_int min = ymin_, max = ymax_;
	double dmin = min, dmax = max;
	double dslope = 219. / (dmax - dmin) * c;
	double dy = 16.;
	int i;
	for (i = 0; i < min; ++i)
		ynorm_[i] = u_char(dy);
	for ( ; i < max; ++i) {
		ynorm_[i] = u_char(dy);
		if (dy < 235.)
			dy += dslope;
	}
	for ( ; i < 256; ++i)
		ynorm_[i] = u_char(dy);

	contrast_ = c;
}

void Grabber::fps(int v)
{
	fps_ = v;
	frametime_ = 1e6 / double(v);
}

void Grabber::bps(int kbps)
{
	bps_ = 1000 * kbps;
}

/*
 * Advance the frame clock and return the amount of time we
 * need to wait before sending the next frame.  We compute
 * this time according to the desired bit and frame rates,
 * favoring the more restrictive metric.  If we're more than
 * 200ms behind (e.g., the cpu is saturated or we've been
 * suspended), give up and reset the frame clock.
 */
double Grabber::tick(int n)
{
	double frametime = 8e6 * double(n) / double(bps_);
	if (frametime < frametime_) {
		if (frametime * 2. < frametime_)
			delta_ += (frametime - delta_) * .25;
		else
			delta_ = frametime;
		frametime = frametime_;
	} else
		delta_ = frametime;

	frameclock_ += frametime;
	double now = gettimeofday();
	double delta = frameclock_ - now;
	if (delta < -0.2e6) {
		delta = frametime;
		frameclock_ = now;
	} else if (delta < 0)
		/*
		 * We're not too far behind.
		 * Try to catch up.
		 */
		delta = 0.;

	return (delta);
}

void Grabber::start()
{
	frameclock_ = gettimeofday();
#ifndef WIN32
	timeout();
#endif
}

void Grabber::stop()
{
	cancel();
}

void Grabber::timeout()
{
	for (;;) {
		double delta = tick(grab());
		if (delta != 0.) {
			usched(delta);
			return;
		}
	}
}

int Grabber::grab()
{
	abort();
	return (0);
}

void Grabber::crinit(int w, int h)
{
	blkw_ = w >> 4;
	blkh_ = h >> 4;
	scan_ = 0;
	nblk_ = blkw_ * blkh_;
	delete crvec_;
	crvec_ = new u_char[nblk_];
	for (int i = 0; i < nblk_; ++i)
		crvec_[i] = CR_MOTION|CR_SEND;
}

/* must call after set_size_xxx */
void Grabber::allocref()
{
	delete ref_;
	ref_ = new u_char[framesize_];
	memset((char*)ref_, 0, framesize_);
}

/*
 * define these for REPLENISH macro used below
 */
#define ABS(v) if (v < 0) v = -v;

#define DIFF4(in, frm, v) \
	v += (in)[0] - (frm)[0]; \
	v += (in)[1] - (frm)[1]; \
	v += (in)[2] - (frm)[2]; \
	v += (in)[3] - (frm)[3];

#define DIFFLINE(in, frm, left, center, right) \
	DIFF4(in, frm, left); \
	DIFF4(in + 1*4, frm + 1*4, center); \
	DIFF4(in + 2*4, frm + 2*4, center); \
	DIFF4(in + 3*4, frm + 3*4, right); \
	ABS(right); \
	ABS(left); \
	ABS(center);

void Grabber::suppress(const u_char* devbuf)
{
	REPLENISH(devbuf, ref_, outw_, 1, 0, blkw_, 0, blkh_);
}

inline void save(const u_char* lum, u_char* cache, int stride)
{
	for (int i = 16; --i >= 0; ) {
		((u_int*)cache)[0] = ((u_int*)lum)[0];
		((u_int*)cache)[1] = ((u_int*)lum)[1];
		((u_int*)cache)[2] = ((u_int*)lum)[2];
		((u_int*)cache)[3] = ((u_int*)lum)[3];
		cache += stride;
		lum += stride;
	}
}

/*
 * Default save routine -- stuff new luma blocks into cache.
 */
void Grabber::saveblks(u_char* lum)
{
	u_char* crv = crvec_;
	u_char* cache = ref_;
	int stride = outw_;
	stride = (stride << 4) - stride;
	for (int y = 0; y < blkh_; y++) {
		for (int x = 0; x < blkw_; x++) {
			if ((*crv++ & CR_SEND) != 0)
				save(lum, cache, outw_);
			cache += 16;
			lum += 16;
		}
		lum += stride;
		cache += stride;
	}
}

void Grabber::age_blocks()
{
	for (int i = 0; i < nblk_; ++i) {
		int s = CR_STATE(crvec_[i]);
		/*
		 * Age this block.
		 * Once we hit the age threshold, we
		 * set CR_SEND as a hint to send a
		 * higher-quality version of the block.
		 * After this the block will stop aging,
		 * until there is motion.  In the meantime,
		 * we might send it as background fill
		 * using the highest quality.
		 */
		if (s <= CR_AGETHRESH) {
			if (s == CR_AGETHRESH)
				s = CR_IDLE;
			else {
				if (++s == CR_AGETHRESH)
					s |= CR_SEND;
			}
			crvec_[i] = s;
		} else if (s == CR_BG)
			/*
			 * reset the block to IDLE if it was sent
			 * as a BG block in the last frame.
			 */
			crvec_[i] = CR_IDLE;
	}
	/*
	 * Now go through and look for some idle blocks to send
	 * as background fill.
	 */
	int blkno = rover_;
	int n = (delta_ * 2. < frametime_)? idle_high_ : idle_low_;
	while (n > 0) {
		int s = CR_STATE(crvec_[blkno]);
		if (s == CR_IDLE) {
			crvec_[blkno] = CR_SEND|CR_BG;
			--n;
		}
		if (++blkno >= nblk_) {
			blkno = 0;
			/* one way to guarantee loop termination */
			break;
		}
	}
	rover_ = blkno;

	/*
	 * Bump the CR scan pointer.  This variable controls which
	 * scan line of a block we use to make the replenishment
	 * decision.  We skip 3 lines at a time to quickly precess
	 * over the block.  Since 3 and 8 are coprime, we will
	 * sweep out every line.
	 */
	scan_ = (scan_ + 3) & 7;
}

void Grabber::set_size_422(int w, int h)
{
	delete framebase_;

	inw_ = w;
	inh_ = h;
	w &=~ 0xf;
	h &=~ 0xf;
	outw_ = w;
	outh_ = h;

	framesize_ = w * h;
	int n = 2 * framesize_ + 2 * GRABBER_VPAD * w;
	framebase_ = new u_char[n];
	memset(framebase_, 0x80, n);
	frame_ = framebase_ + GRABBER_VPAD * w;
	crinit(w, h);

	vstart_ = 0;
	vstop_ = blkh_;
	hstart_ = 0;
	hstop_ = blkw_;
}

void Grabber::set_size_411(int w, int h)
{
	delete framebase_;

	inw_ = w;
	inh_ = h;
	w &=~ 0xf;
	h &=~ 0xf;
	outw_ = w;
	outh_ = h;

	int s = w * h;
	framesize_ = s;
	int n = s + (s >> 1) + 2 * GRABBER_VPAD * outw_;
	framebase_ = new u_char[n];
	/* initialize to gray */
	memset(framebase_, 0x80, n);
	frame_ = framebase_ + GRABBER_VPAD * outw_;
	crinit(w, h);

	vstart_ = 0;
	vstop_ = blkh_;
	hstart_ = 0;
	hstop_ = blkw_;
}

void Grabber::set_size_cif(int w, int h)
{
	delete framebase_;
	inw_ = w;
	inh_ = h;

	int ispal;
	switch (w) {
	case 320:
		/* 1/2 NTSC */
		ispal = 0;
		outw_ = 352;
		outh_ = 288;
		break;

	case 160:
		/* 1/8 NTSC */
		ispal = 0;
		outw_ = 176;
		outh_ = 144;
		break;

	case 352:
		/* 1/2 CIF */
		ispal = 1;
		outw_ = 352;
		outh_ = 288;
		break;

	case 176:
		/* 1/8 CIF */
		ispal = 1;
		outw_ = 176;
		outh_ = 144;

	case 384:
		/* 1/2 PAL */
		ispal = 1;
		outw_ = 352;
		outh_ = 288;
		break;

	case 192:
		/* 1/8 PAL */
		ispal = 1;
		outw_ = 176;
		outh_ = 144;
		break;

	default:
		/* XXX this shouldn't happen */
		fprintf(stderr, "vic: CIF grabber: bad geometry\n");
		abort();
	}
	int s = outw_ * outh_;
	framesize_ = s;
	int n = s + (s >> 1) + 2 * GRABBER_VPAD * outw_;
	framebase_ = new u_char[n];
	/* initialize to gray */
	memset(framebase_, 0x80, n);
	frame_ = framebase_ + GRABBER_VPAD * outw_;
	crinit(outw_, outh_);

	if (ispal) {
		/* PAL: field is bigger than CIF */
		vstart_ = 0;
		vstop_ = blkh_;
		hstart_ = 0;
		hstop_ = blkw_;
	} else {
		/* NTSC: field is smaller than CIF */
		vstart_ = 1;
		vstop_ = vstart_ + inh_ / 16;
		int nw = inw_ / 16;
		int margin = (blkw_ - nw + 1) / 2;
		hstart_ = margin;
		hstop_ = blkw_ - margin;
	}
}
