/*
 * Copyright (c) Jim Lowe, 1995, 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 Jim Lowe
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/*
 * 1.0	11/28/95
 *	Initial Release.
 *	Spigot library available from:
 *			ftp.cs.uwm.edu:/pub/FreeBSD/spigot/spigot.tar.gz
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "grabber.h"
#include "Tcl.h"
#include "device-input.h"
#include "module.h"

extern "C" {
#	include <machine/spigot.h>
#	include <spigot_saa7191.h>
} ;

/*XXX*/
#define NTSC_WIDTH 320
#define NTSC_HEIGHT 240
#define PAL_WIDTH 384
#define PAL_HEIGHT 288
#define CIF_WIDTH 352
#define CIF_HEIGHT 288


class SpigotGrabber : public Grabber {
 public:
	SpigotGrabber(const char* name, const char* format);
	virtual ~SpigotGrabber();
	virtual void start();
 protected:
	virtual int command(int argc, const char*const* argv);
	virtual int capture();
	virtual int grab();
	void format();
	void setsize();

	int video_format_;	/* video input format: NTSC or PAL */
	int port_;		/* video input port */
	int coder_format_;	/* 411, 422, or cif */
	int vtof_;		/* Vertical top of frame (lines to skip) */
	int secam_;		/* secam mode ? */
	u_int basewidth_;	/* Height of frame to be captured */
	u_int baseheight_;	/* Width of frame to be captured */
	u_int decimate_;	/* division of base sizes */
	volatile u_short* pyuv_;/* pointer to yuv data */
	int 	loff_;		/* offset from start of frame to scan */
	int	coff_;		/* offset from start of frame to scan */
	int	hwrap_;		/* amount to skip on each output line */
	int	hskip_;		/* amount of input to toss on each line */
};

static const int	f_411 = 0;	/* coder_format_s */
static const int	f_422 = 1;
static const int	f_cif = 2;

static const int	vf_pal = 0;	/* video_format_s */
static const int	vf_ntsc = 1;
static const int	vf_automode = 2;

static const int	port_rca = 0;	/* port_s */
static const int	port_svideo = 1;

static const int	vtof = 10;	/* XXX */

class SpigotDevice : public InputDevice {
 public:
	SpigotDevice(const char* nickname, const char* devname);
	virtual int command(int argc, const char*const* argv);
 protected:
	const char* name_;
};


static SpigotDevice spigot_device("Video Spigot", "/dev/spigot");

SpigotDevice::SpigotDevice(const char* nickname, const char *devname):
					InputDevice(nickname), name_(devname)
{
	if(access(devname, R_OK) == 0)
		attributes_ = "\
format {422 411} \
size {normal small cif} \
port {RCA S-Video}";
	else
		attributes_ = "disabled";
}

int SpigotDevice::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if ((argc == 3) && (strcmp(argv[1], "open") == 0)) {
		TclObject* o = 0;
		o = new SpigotGrabber(name_, argv[2]);
		if (o != 0)
			tcl.result(o->name());
		return (TCL_OK);
	}
	return (InputDevice::command(argc, argv));
}

SpigotGrabber::SpigotGrabber(const char* name, const char* format)
{
	coder_format_ = -1;
	if(!strcmp(format, "411")) coder_format_ = f_411;
	if(!strcmp(format, "422")) coder_format_ = f_422;
	if(!strcmp(format, "cif")) coder_format_ = f_cif;
	if(coder_format_ == -1) {
		fprintf(stderr,
			"vic: SpigotGrabber: unsupported format: %s\n",
			format);
		abort();
	}

	pyuv_ = spigot_open((char *)name);
	if (pyuv_ == (u_short *)-1) {
		pyuv_ = 0;
		status_ = -1;
		return;
	}
	(void)saa7191_init(NULL, 0);	/* initialize phillips saa 7191 chip */
	(void)saa7191_setv("OFTS", 1);	/* yuv 4:2:2 output mode */
	port_ = saa7191_getv("GPSW1");
	video_format_ = saa7191_getv("FSEL");
	secam_ = saa7191_getv("SECS");	/* when should this be set ? */
	vtof_ = vtof;
	if(saa7191_getv("AUFD"))
		video_format_ = vf_automode;
	decimate_ = 2;
	basewidth_ = NTSC_WIDTH * 2;
	baseheight_ = NTSC_HEIGHT * 2;
}

SpigotGrabber::~SpigotGrabber()
{
	if (pyuv_ != 0) {
		pyuv_ = 0;
		spigot_close();
	}
}

void SpigotGrabber::setsize()
{

	int	w = basewidth_ / decimate_;
	int	h = baseheight_ / decimate_;

	spigot_set_capture_size(w, vtof_);

	switch(coder_format_) {
	case f_422:
		set_size_422(w, h);
		hwrap_ = 0;
		loff_ = 0;
		coff_ = 0;
		hskip_ = 0;
		break;
	case f_cif:
		set_size_cif(w, h);
		/* XXX */
		vstart_ = 0;
		vstop_ = blkh_;
		hstart_ = 1;
		hstop_ = blkw_ - 1;

		int	voff = (outh_ - inh_) / 2;
		hwrap_ = outw_ - inw_ ;
		int	hoff = hwrap_ / 2;
		loff_ = outw_ * voff + hoff;
		coff_ = (outw_ >> 1) * (voff >> 1) + (hoff >> 1);
		hskip_ = 0;
		break;
	case f_411:
		set_size_411(w, h);
		hwrap_ = 0;
		loff_ = 0;
		coff_ = 0;
		hskip_ = 0;
		break;
	}
	allocref();	/* allocate reference frame */

}

void SpigotGrabber::format()
{
	u_char status = spigot_status();
	if(pyuv_ == 0) {
		fprintf(stderr, "vic: spigot not active\n");
		return;
	}

	int w, h;
	switch (status & (SPIGOT_COLOR|SPIGOT_60HZ|SPIGOT_NO_HORIZONTAL_LOCK)) {
	default:
		fprintf(stderr, "vic: spigot sees no signal - using ntsc.\n");
		/* fall through */
	case 2:	/* NTSC no color */
	case 3: /* NTSC color */
		w = NTSC_WIDTH * 2;
		h = NTSC_HEIGHT * 2;
		break;

	case 0:	/* PAL no color */
	case 1: /* PAL color */
		w = PAL_WIDTH * 2;
		h = PAL_HEIGHT * 2;
		break;
	}
	basewidth_ = w;
	baseheight_ = h;
	setsize();
}


void SpigotGrabber::start()
{
	format();
	Grabber::start();
}

int SpigotGrabber::command(int argc, const char*const* argv)
{
	if (argc == 3) {
		if (strcmp(argv[1], "decimate") == 0) {
			int dec = atoi(argv[2]);
			Tcl& tcl = Tcl::instance();
			if (dec <= 0) {
				tcl.resultf("%s: divide by zero", argv[0]);
				return (TCL_ERROR);
			}
			if (dec != decimate_) {
				decimate_ = dec;
				if(running_) {
					setsize();
				}
			}
			return (TCL_OK);	
		} else if (strcmp(argv[1], "port") == 0) {
			int p = port_;
                        if(!strcmp(argv[2], "RCA")) p = port_rca;
			if(!strcmp(argv[2], "S-Video")) p = port_svideo;
			if (p != port_) {
				port_ = p;
				(void) saa7191_setv("GPSW1", p);
			}
			return (TCL_OK);	
		} else if (strcmp(argv[1], "format") == 0) {
			if (strcmp(argv[2], "auto") == 0) {
				video_format_ = vf_automode;
				(void)saa7191_setv("AUFD", 1);
			} else if (strcmp(argv[2], "pal") == 0) {
				video_format_ = vf_pal;
				(void)saa7191_setv("FSEL", 0);
				(void)saa7191_setv("AUFD", 0);
			} else {
				video_format_ = vf_ntsc;
				(void)saa7191_setv("FSEL", 1);
				(void)saa7191_setv("AUFD", 0);
			}
			if (running_)
				format();
			return (TCL_OK);	
		} else if (strcmp(argv[1], "contrast") == 0) {
			contrast(atof(argv[2]));
			return (TCL_OK);	
		}
	} else if (argc == 2) {
		if (strcmp(argv[1], "normalize") == 0) {
#ifdef notdef
			normalize();
#endif
			return (TCL_OK);	
		} else if (strcmp(argv[1], "format") == 0) {
			Tcl& tcl = Tcl::instance();
			switch (video_format_) {

			case vf_automode:
				tcl.result("auto");
				break;

			case vf_ntsc: 
				tcl.result("ntsc");
				break;

			case vf_pal:
				tcl.result("pal");
				break;

			default:
				tcl.result("");
				break;
			}
			return (TCL_OK);
			
		}
	}
	return (Grabber::command(argc, argv));
}

int SpigotGrabber::capture()
{
	if(pyuv_ == 0) return 0;

	volatile u_short*	pyuv 	 = pyuv_;
	u_char* 		lum  	 = frame_ + loff_;
	u_char*			u    	 = frame_ + framesize_ + coff_;
	int			f422 	 = (coder_format_ == f_422);
	u_char*			v    	 = u + (framesize_ >> (f422 ? 1:2));
	int 			numc 	 = ((basewidth_/decimate_) & ~0xf) >> 2;
	int			numr 	 = (baseheight_/decimate_) & ~0xf;
	int			chm_wrap = hwrap_ >> 1;

	(void)spigot_start_xfer(1);
	for(int y=0; y < numr ; y++) {
		for(int x=0; x < numc; x++) {
			/*
			 * We read these as unsigned shorts and set y data
			 * right after the read because of some weird timing
			 * problems with the spigot.  Originally, I tried
			 * reading the data as 2 unsigned ints and it didn't
			 * work on my machine.
			 *
			 * There also seems to be some word swapping going
			 * on between the 7191 and the fifo's.  Anyways,
			 * I think this is the correct order for data.
			 */
			u_short d0 = *pyuv;
			*lum++ = d0 & 0xff;
			u_short d1 = *pyuv;
			*lum++ = d1 & 0xff;
			u_short d2 = *pyuv;
			*lum++ = d2 & 0xff;
			u_short d3 = *pyuv;
			*lum++ = d3 & 0xff;
                        if(f422) { 
                       		/*
				 * Data comes from the saa7191
				 * in the format:
				 * Y0 V01
				 * Y1 U01
				 * Y2 V23
				 * Y3 U23.
				 */
				*v++ = (d0 >> 8) & 0xff;
				*u++ = (d1 >> 8) & 0xff;
				*v++ = (d2 >> 8) & 0xff;
				*u++ = (d3 >> 8) & 0xff;
			} else {        /* YUV 4:1:1, skip odd chm data */
				if((y & 1) == 0) {
					*v++ = (d0 >> 8) & 0xff;
					*u++ = (d1 >> 8) & 0xff;
					*v++ = (d2 >> 8) & 0xff;
					*u++ = (d3 >> 8) & 0xff;
				}
			}
		}
		lum += hwrap_;
		if((y & 1) == 0) {
			u += chm_wrap;
			v += chm_wrap;
		}
	}

	/* remove trailing garbage */
	for(y=0; y < ((baseheight_/decimate_)&0xf)* (basewidth_/decimate_); y++)
		u_short d0 = *pyuv;

	(void)spigot_stop_xfer();

	return 1;
}

int SpigotGrabber::grab()
{
	if (capture() == 0)
		return (0);
	suppress(frame_);
	saveblks(frame_);
	YuvFrame f(media_ts(), frame_, crvec_, outw_, outh_);
	return (target_->consume(&f));
}
