/*
 * Copyright (c) 1996 The 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 Network Research
 * 	Group at Lawrence Berkeley National 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.
 *
 * This module contributed by Koji OKAMURA <oka@is.aist-nara.ac.jp>:
 *  My grabber-qcam.cc is depend on "ftp://ftp.nas.com/laird/"'s APIs.
 *  It works on linux box.
 *  I put its binary on ftp://ftp.jain.ad.jp/pub/linux.
 *
 * Modified for more generic driver-versus-library detection by John Bashinski
 * <jbash@cisco.com>.  This version works with QuickCam driver on Solaris 2.x.
 * Also added QuickCam GUI controls, and removed old auto-contrast system.
 *
 * Converted for color and to use cqcam driver
 *    Toivo Pedaste (toivo@ucs.uwa.edu.au)
 */

#ifndef lint
static char rcsid[] =
    "@(#) $Header: /work/projects/tove/cvs/src/testing/vic-2.8-cqcam/grabber-cqcam.cc,v 1.1 1998/03/02 10:24:51 jturunen Exp $ (LBL)";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/fcntl.h>  
#include <sys/ioctl.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <unistd.h>
#include <errno.h>

#include "./cqcam/camera.h"
#include "./cqcam/imager.h"

#include "grabber.h"
#include "Tcl.h"
#include "device-input.h"
#include "module.h"
#include "rgb-converter.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

/*XXX*/
#define VOLATILE volatile

class CqcamGrabber : public Grabber {
public:
  CqcamGrabber();
  virtual ~CqcamGrabber();
  virtual void start();
  struct camera_t *cam;
protected:
  virtual int command(int argc, const char*const* argv);
  virtual void initsize(int bw, int bh, int bpp);
  void do_setsize();
  int capture();
  virtual int grab();
  virtual void grab_image();
  void format();
  void normalize();
  void tcl_other_sliders();
  void tcl_col_sliders();
  void tcl_brit_sliders();
  void do_auto_bright();
  virtual void setsize();
  void contrast (double c);
  RGB_Converter* converter_;

  int format_;		/* video format: NTSC or PAL */
  int rformat_;		/* requested format, above + AUTO */
  int port_;
  int fd;
  char buf[320*240];
  u_int basewidth_;
  u_int baseheight_;
  u_int decimate_;
  int upper_bound, lower_bound, bri_loops;
  int auto_bright, auto_color, de_speckle;
};

class CIFCqcamGrabber : public CqcamGrabber {
protected:
  virtual void setsize();
  virtual void initsize(int bw, int bh, int bpp);
  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 throw out on each line */
};

class CqcamDevice : public InputDevice {
 public:
	CqcamDevice(const char*);
	virtual int command(int argc, const char*const* argv);
};

static CqcamDevice cqcam_device("cqcam");

CqcamDevice::CqcamDevice(const char* name) : InputDevice(name)
{
    	attributes_ = " \
		format { 422 cif} \
		size { small cif large } \
		port { cqcam0 } ";
}

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

  static int iport = DEFAULT_PORT;
  static int idetect = DEFAULT_DETECT_MODE;
  static int ibpp = DEFAULT_BPP;

CqcamGrabber::CqcamGrabber()
{

  cam = new camera_t(iport, idetect);  // probe for and initialize the beast

  tcl_other_sliders();
  tcl_col_sliders();
  tcl_brit_sliders();
  do_auto_bright();
  auto_color = 1;

  format_=0;
  decimate_ = 2;
  setsize();
}

CqcamGrabber::~CqcamGrabber()
{

  delete cam;
}

void CqcamGrabber::tcl_other_sliders()
{
  (Tcl::instance()).evalf
    ("if [info exists cqcamwindow] {\n"
     "    eval \"$cqcamwindow(setbright) %d\"\n"
     "    eval \"$cqcamwindow(sethue) %d\"\n"
     "    eval \"$cqcamwindow(setsat) %d\"\n"
     "    eval \"$cqcamwindow(setcont) %d\"\n"
     "    eval \"$cqcamwindow(setwbal) %d\"\n"
     "    eval \"$cqcamwindow(setbbal) %d\"\n"
     "    eval \"$cqcamwindow(setbpp) %d\"\n"
     "    eval \"$cqcamwindow(setdecimate) %d\"\n"
     "    eval \"$cqcamwindow(setleft) %d\"\n"
     "    eval \"$cqcamwindow(settop) %d\"\n"
     "}\n",
     cam->get_brightness(),
     cam->get_hue(),
     cam->get_saturation(),
     cam->get_contrast(),
     cam->get_white_level(),
     cam->get_black_level(),
     cam->get_bpp(),
     cam->get_decimation(),
     cam->get_left(),
     cam->get_top()
     );
}

void CqcamGrabber::tcl_col_sliders()
{
  (Tcl::instance()).evalf
    ("if [info exists cqcamwindow] {\n"
     "    eval \"$cqcamwindow(setred) %d\"\n"
     "    eval \"$cqcamwindow(setgreen) %d\"\n"
     "    eval \"$cqcamwindow(setblue) %d\"\n"
     "}\n",
     cam->get_red(),
     cam->get_green(),
     cam->get_blue()
     );
}

void CqcamGrabber::tcl_brit_sliders()
{
  (Tcl::instance()).evalf
    ("if [info exists cqcamwindow] {\n"
     "    eval \"$cqcamwindow(setbright) %d\"\n"
     "}\n",
     cam->get_brightness()
     );
}


void CqcamGrabber::format()
{
  setsize();
}

/*XXX*/
void CqcamGrabber::normalize()
{

}

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

void CqcamGrabber::do_auto_bright()
{
	auto_bright = 1;
	upper_bound = 253;
	lower_bound = 5;
	bri_loops = 0;
}

int CqcamGrabber::command(int argc, const char*const* argv)
{
  Tcl& tcl = Tcl::instance();

  if (argc == 4) {
    if (strcmp (argv[1], "set") == 0) {
      if (strcmp (argv[2], "BPP") == 0) {
	cam->set_bpp (atoi(argv[3]));
	printf("BPP=%d %d\n",atoi(argv[3]),cam->get_bpp());
	setsize();
	return (TCL_OK);
      } else if (strcmp (argv[2], "auto_color") == 0) {
	auto_color = (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "auto_bright") == 0) {
	do_auto_bright();
	return (TCL_OK);
      } else if (strcmp (argv[2], "despeckle") == 0) {
	de_speckle = ! de_speckle;
	return (TCL_OK);
      } else if (strcmp (argv[2], "BRIGHT") == 0) {
	cam->set_brightness ( atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "DECIMATE") == 0) {
	cam->set_decimation ( atoi(argv[3]));
	setsize();
	return (TCL_OK);
      } else if (strcmp (argv[2], "HUE") == 0) {
	cam->set_hue (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "RED") == 0) {
	cam->set_red (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "GREEN") == 0) {
	cam->set_green (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "BLUE") == 0) {
	cam->set_blue (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "SATURATION") == 0) {
	cam->set_saturation (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "CONTRAST") == 0) {
	cam->set_contrast  (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "WBAL") == 0) {
	cam->set_white_level (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "BBAL") == 0) {
	cam->set_black_level (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "LEFT") == 0) {
	cam->set_left (atoi(argv[3]));
	return (TCL_OK);
      } else if (strcmp (argv[2], "TOP") == 0) {
	cam->set_top (atoi(argv[3]));
	return (TCL_OK);
      } else {
	tcl.resultf("%s: unknown set command for Quickcam: %s",
		    argv[0], argv[2]);
	return (TCL_ERROR);
      }
    }
  } else if (argc == 3) {
    if (strcmp(argv[1], "get") == 0) {
      if (strcmp (argv[2], "BPP") == 0) {
	tcl.resultf("%d", cam->get_bpp());
	return (TCL_OK);
      } else if (strcmp (argv[2], "BRIGHT") == 0) {
	tcl.resultf("%d", cam->get_brightness());
	return (TCL_OK);
      } else if (strcmp (argv[2], "DECIMATE") == 0) {
	tcl.resultf("%d", cam->get_decimation());
	return (TCL_OK);
      } else if (strcmp (argv[2], "HUE") == 0) {
	tcl.resultf("%d", cam->get_hue());
	return (TCL_OK);
      } else if (strcmp (argv[2], "RED") == 0) {
	tcl.resultf("%d", cam->get_red());
	return (TCL_OK);
      } else if (strcmp (argv[2], "GREEN") == 0) {
	tcl.resultf("%d", cam->get_green());
	return (TCL_OK);
      } else if (strcmp (argv[2], "BLUE") == 0) {
	tcl.resultf("%d", cam->get_blue());
	return (TCL_OK);
      } else if (strcmp (argv[2], "SATURATION") == 0) {
	tcl.resultf("%d", cam->get_saturation());
	return (TCL_OK);
      } else if (strcmp (argv[2], "CONTRAST") == 0) {
	tcl.resultf("%d", cam->get_contrast());
	return (TCL_OK);
      } else if (strcmp (argv[2], "WBAL") == 0) {
	tcl.resultf("%d", cam->get_white_level());
	return (TCL_OK);
      } else if (strcmp (argv[2], "BBAL") == 0) {
	tcl.resultf("%d", cam->get_black_level());
	return (TCL_OK);
      } else if (strcmp (argv[2], "LEFT") == 0) {
	tcl.resultf("%d", cam->get_left());
	return (TCL_OK);
      } else if (strcmp (argv[2], "TOP") == 0) {
	tcl.resultf("%d", cam->get_top());
	return (TCL_OK);
      }
    } else if (strcmp(argv[1], "decimate") == 0) {
      int dec = atoi(argv[2]);
      if (dec <= 0) {
	tcl.resultf("%s: divide by zero", argv[0]);
	return (TCL_ERROR);
      }
      if (dec != decimate_) {
	decimate_ = dec;
	setsize();
      }
      return (TCL_OK);	
    } else if (strcmp(argv[1], "port") == 0) {
      int p = atoi(argv[2]);
      if (p != port_) {
	port_ = p;
      }
      return (TCL_OK);	
    } else if (strcmp(argv[1], "format") == 0) {
      /*
	if (strcmp(argv[2], "auto") == 0)
	rformat_ = VFC_AUTO;
	else if (strcmp(argv[2], "pal") == 0)
	rformat_ = VFC_PAL;
	else
	rformat_ = VFC_NTSC;
	*/
      if (running_)
	format();
      return (TCL_OK);	
    }
  } else if (argc == 2) {
    if (strcmp(argv[1], "normalize") == 0) {
      normalize();
      return (TCL_OK);	
    } else if (strcmp(argv[1], "format") == 0) {
      Tcl& tcl = Tcl::instance();
      switch (format_) {

      default:
	tcl.result("");
	break;
      }
      return (TCL_OK);
			
    }
  }

  return (Grabber::command(argc, argv));
}


void CqcamGrabber::grab_image()
{

  int i,j,inw, inh;
  int rtemp, gtemp, btemp, limit;
  unsigned char *scan, *buf;
  int d;
  int britemp, done;

  scan=cam->get_frame();
  inw = cam->get_pix_width();
  inh = cam->get_pix_height();

  if (cam->get_bpp() == 32) {
    scan = raw32_to_24(scan, inw, inh);
  } else {
    if (de_speckle) {
      scan = despeckle(scan, inw, inh);
    }
  }
  limit = inh * inw;

  if (auto_color) {
    rtemp = cam->get_red();
    gtemp = cam->get_green();
    btemp = cam->get_blue();
    get_rgb_adj(scan, limit, rtemp, gtemp, btemp);
    cam->set_red(rtemp);
    cam->set_green(gtemp);
    cam->set_blue(btemp);
    tcl_col_sliders();
    auto_color = 0;
  }

  if (auto_bright) {
    done = get_brightness_adj(scan, limit, britemp);
    if (!done) {
      int cur_bri = cam->get_brightness() + britemp;
      if (cur_bri > upper_bound)
        cur_bri = upper_bound - 1;
      if (cur_bri < lower_bound)
        cur_bri = lower_bound + 1;
      if (britemp > 0)
        lower_bound = cam->get_brightness() + 1;
      else
        upper_bound = cam->get_brightness() - 1;

        fprintf(stderr, "Brightness %s to %d (%d..%d)\n",
          (britemp<0)? "decreased" : "increased", cur_bri, lower_bound,
          upper_bound);

      cam->set_brightness(cur_bri);	
      tcl_brit_sliders();
    }
    if (done || upper_bound <= lower_bound || ++bri_loops > 10)
      auto_bright = 0;
  }
  
  if (!(cam->get_red() == 128 &&
	cam->get_green() == 128 &&
	cam->get_blue() ==128))
    do_rgb_adj(scan, limit, cam->get_red(), cam->get_green(),
	       cam->get_blue());

  //  fprintf(stderr,"inh=%d inw=%d outh=%d outw=%d\n",inh,inw,outh_,outw_);

  if (inw == outw_) {
    converter_->convert((u_int8_t*) scan,
			outw_, outh_, frame_);
  } else {
    buf =  new unsigned char[outh_*outw_*3];

    if (inw > outw_) {
      printf("inw > outw_ %d > %d\n", inw, outw_);
    } else {
      //      printf("inw < outw_ %d < %d\n", inw, outw_);
      int h = inh >  outh_ ? outh_ : inh;
      for(i=0; i < h; i++) {
	memcpy(buf+i*outw_*3, scan+i*inw*3, inw*3);
	memset(buf+i*outw_*3+inw*3, 0, (outw_-inw)*3);
      }
      if (outh_ > inh) {
	for (i = inh; i < outh_; i++) {
	  memset(buf+i*outw_*3, 0, outw_*3);
	}
      }
    }	
    converter_->convert((u_int8_t*) buf,
			outw_, outh_, frame_);
    delete buf;
  }

  delete scan;
}

int CqcamGrabber::capture()
{

  int i;

    grab_image();
 
  return (1);
}

int CqcamGrabber::grab()
{

  if (capture() == 0)
    return (0);

  suppress(frame_);
  saveblks(frame_);
  YuvFrame f(media_ts(), frame_, crvec_, outw_, outh_);
  return (target_->consume(&f));
}


void CqcamGrabber::do_setsize()
{ 
  int bh; 
  int bw;
  int bpp;

  bh = baseheight_ / decimate_;
  bw = basewidth_ / decimate_;
  bpp = cam->get_bpp();

  initsize(bw, bh, bpp);
}

void CqcamGrabber::setsize()
{ 
  int bh; 
  int bw;
  int bpp;

  basewidth_ = 640;
  baseheight_ = 480;

  do_setsize();
}

void CIFCqcamGrabber::setsize()
{ 
  basewidth_ = CIF_WIDTH*2;
  baseheight_ = CIF_HEIGHT*2;

  do_setsize();
}

void CqcamGrabber::initsize(int bw,int bh,int bpp)
{
  int deci = cam->get_decimation();
  int orig_bw = bw;

  converter_ = RGB3_Converter_422::instance();
  set_size_422(bw, bh);
  allocref();

  if (bpp == 32) {
    if (bw == NTSC_WIDTH*2) {
      if (deci == 1) {
	bw = 640; bh = 480;
      } else if (deci == 2) {
	bw = 640; bh = 480;
      } else if (deci == 4) {
	bw = 640; bh = 480;
      }
    } else if (bw == NTSC_WIDTH) {
      if (deci == 1) {
	bw = 320; bh = 240;
      } else if (deci == 2) {
	bw = 640; bh = 480;
      } else if (deci == 4) {
	bw = 640; bh = 480;
      }
    } else if (bw == NTSC_WIDTH/2) {
      if (deci == 1) {
	bw = 160; bh = 120;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 640; bh = 480;
      }
    } else
      printf("Unexpected width %d\n",bw);
  } else if (bpp = 24) {
    if (bw == NTSC_WIDTH*2) {
      if (deci == 1) {
	bw = 320; bh = 240;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 320; bh = 240;
      }
    } else if (bw == NTSC_WIDTH) {
      if (deci == 1) {
	bw = 320; bh = 240;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 320; bh = 240;
      }
    } else if (bw == NTSC_WIDTH/2) {
      if (deci == 1) {
	bw=160; bh - 120;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 320; bh = 240;
      }
    } else
      printf("Unexpected width %d\n",bw);
  }  else
    printf("Unexpected BPP %d\n",bpp);
	
  cam->set_width(bw);
  cam->set_height(bh);

#ifdef DEBUG
  printf("SETSIZE BPP=%d DECI=%d origw = %d  %d,%d = %d,%d\n",
	 bpp,deci,orig_bw,
	 bw ,
	 bh ,
	 cam->get_pix_width(),cam->get_pix_height());
#endif
}

void CIFCqcamGrabber::initsize(int bw, int bh, int bpp)
{
  int deci = cam->get_decimation();
  int orig_bw = bw;

  converter_ = RGB3_Converter_411::instance();
  set_size_411(bw, bh);
  allocref();

  if (bpp == 32) {
    if (bw == CIF_WIDTH) {
      if (deci == 1) {
	bw = CIF_WIDTH; bh = CIF_HEIGHT;
      } else if (deci == 2) {
	bw = 640; bh = 480;
      } else if (deci == 4) {
	bw = 640; bh = 480;
      }
    } else if (bw == CIF_WIDTH/2) {
      if (deci == 1) {
	bw = CIF_WIDTH/2; bh = CIF_HEIGHT/2;
      } else if (deci == 2) {
	bw = CIF_WIDTH; bh = CIF_HEIGHT;
      } else if (deci == 4) {
	bw = 640; bh = 480;
      }
    } else
      printf("Unexpected width %d\n",bw);
  } else if (bpp = 24) {
    if (bw == CIF_WIDTH) {
      if (deci == 1) {
	bw = 320; bh = 240;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 320; bh = 240;
      }
    } else if (bw == CIF_WIDTH/2) {
      if (deci == 1) {
	bw = CIF_WIDTH/2; bh = CIF_HEIGHT/2;
      } else if (deci == 2) {
	bw = 320; bh = 240;
      } else if (deci == 4) {
	bw = 320; bh = 240;
      }
    } else
      printf("Unexpected width %d\n",bw);
  }  else
    printf("Unexpected BPP %d\n",bpp);
	
  cam->set_width(bw);
  cam->set_height(bh);

#ifdef DEBUG
  printf("SETSIZE BPP=%d DECI=%d origw = %d  %d,%d = %d,%d\n",
	 bpp,deci,orig_bw,
	 bw ,
	 bh ,
	 cam->get_pix_width(),cam->get_pix_height());
#endif
}
