/***************************************************************************
    copyright            : (C) 2007 by Sebastian Held
    email                : sebastian.held@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

// uses code from xawtv: webcam.c   (c) 1998-2002 Gerd Knorr

//#include <stdio.h>
//#include <stdlib.h>
#include <fcntl.h>              /* low-level i/o */
#include <unistd.h>
#include <errno.h>
//#include <malloc.h>
//#include <sys/stat.h>
//#include <sys/types.h>
//#include <sys/time.h>
//#include <sys/mman.h>
#include <sys/ioctl.h>

#include "barcode_v4l.h"
#include "../tellico_debug.h"

using barcodeRecognition::ng_vid_driver;
using barcodeRecognition::ng_vid_driver_v4l;
using barcodeRecognition::barcode_v4l;
using barcodeRecognition::video_converter;

TQPtrList<barcodeRecognition::video_converter> barcodeRecognition::ng_vid_driver::m_converter;
const char *barcodeRecognition::device_cap[] = { "capture", "tuner", "teletext", "overlay", "chromakey", "clipping", "frameram", "scales", "monochrome", 0 };
const unsigned int barcodeRecognition::ng_vfmt_to_depth[] = {
    0,               /* unused   */
    8,               /* RGB8     */
    8,               /* GRAY8    */
    16,              /* RGB15 LE */
    16,              /* RGB16 LE */
    16,              /* RGB15 BE */
    16,              /* RGB16 BE */
    24,              /* BGR24    */
    32,              /* BGR32    */
    24,              /* RGB24    */
    32,              /* RGB32    */
    16,              /* LUT2     */
    32,              /* LUT4     */
    16,		     /* YUYV     */
    16,		     /* YUV422P  */
    12,		     /* YUV420P  */
    0,		     /* MJPEG    */
    0,		     /* JPEG     */
    16,		     /* UYVY     */
};

const char* barcodeRecognition::ng_vfmt_to_desc[] = {
    "none",
    "8 bit PseudoColor (dithering)",
    "8 bit StaticGray",
    "15 bit TrueColor (LE)",
    "16 bit TrueColor (LE)",
    "15 bit TrueColor (BE)",
    "16 bit TrueColor (BE)",
    "24 bit TrueColor (LE: bgr)",
    "32 bit TrueColor (LE: bgr-)",
    "24 bit TrueColor (BE: rgb)",
    "32 bit TrueColor (BE: -rgb)",
    "16 bit TrueColor (lut)",
    "32 bit TrueColor (lut)",
    "16 bit YUV 4:2:2 (packed, YUYV)",
    "16 bit YUV 4:2:2 (planar)",
    "12 bit YUV 4:2:0 (planar)",
    "MJPEG (AVI)",
    "JPEG (JFIF)",
    "16 bit YUV 4:2:2 (packed, UYVY)",
};

unsigned int  barcodeRecognition::ng_yuv_gray[256];
unsigned int  barcodeRecognition::ng_yuv_red[256];
unsigned int  barcodeRecognition::ng_yuv_blue[256];
unsigned int  barcodeRecognition::ng_yuv_g1[256];
unsigned int  barcodeRecognition::ng_yuv_g2[256];
unsigned int  barcodeRecognition::ng_clip[256 + 2 * CLIP];

void barcodeRecognition::ng_color_yuv2rgb_init()
{
    int i;
# define RED_NULL    128
# define BLUE_NULL   128
# define LUN_MUL     256
# define RED_MUL     512
# define BLUE_MUL    512
#define GREEN1_MUL  (-RED_MUL/2)
#define GREEN2_MUL  (-BLUE_MUL/6)
#define RED_ADD     (-RED_NULL  * RED_MUL)
#define BLUE_ADD    (-BLUE_NULL * BLUE_MUL)
#define GREEN1_ADD  (-RED_ADD/2)
#define GREEN2_ADD  (-BLUE_ADD/6)

    /* init Lookup tables */
    for (i = 0; i < 256; i++) {
        barcodeRecognition::ng_yuv_gray[i] = i * LUN_MUL >> 8;
        barcodeRecognition::ng_yuv_red[i]  = (RED_ADD    + i * RED_MUL)    >> 8;
        barcodeRecognition::ng_yuv_blue[i] = (BLUE_ADD   + i * BLUE_MUL)   >> 8;
        barcodeRecognition::ng_yuv_g1[i]   = (GREEN1_ADD + i * GREEN1_MUL) >> 8;
        barcodeRecognition::ng_yuv_g2[i]   = (GREEN2_ADD + i * GREEN2_MUL) >> 8;
    }
    for (i = 0; i < CLIP; i++)
        barcodeRecognition::ng_clip[i] = 0;
    for (; i < CLIP + 256; i++)
        barcodeRecognition::ng_clip[i] = i - CLIP;
    for (; i < 2 * CLIP + 256; i++)
        barcodeRecognition::ng_clip[i] = 255;
}











barcode_v4l::barcode_v4l()
{
  m_devname = "/dev/video0";
  m_grab_width = 640;
  m_grab_height = 480;
  m_drv = 0;
  m_conv = 0;

  barcodeRecognition::ng_color_yuv2rgb_init();
  //ng_vid_driver::register_video_converter( new barcodeRecognition::yuv422p_to_rgb24() );
  //ng_vid_driver::register_video_converter( new barcodeRecognition::yuv422_to_rgb24() );
  ng_vid_driver::register_video_converter( new barcodeRecognition::yuv420p_to_rgb24() );

  grab_init();
}

barcode_v4l::~barcode_v4l()
{
  if (m_drv) {
    m_drv->close();
    delete m_drv;
  }
}

bool barcode_v4l::isOpen()
{
  return m_drv;
}

TQImage barcode_v4l::grab_one2()
{
  if (!m_drv) {
    myDebug() << "no driver/device available" << endl;
    return TQImage();
  }

  TQByteArray *cap;
  if (!(cap = m_drv->getimage2())) {
      myDebug() << "capturing image failed" << endl;
      return TQImage();
  }

  TQByteArray *buf;
  if (m_conv) {
    buf = new TQByteArray( 3*m_fmt.width*m_fmt.height );
    m_conv->frame( buf, cap, m_fmt_drv );
  } else {
    buf = new TQByteArray(*cap);
  }
  delete cap;

  int depth = 32;
  TQByteArray *buf2 = new TQByteArray( depth/8 *m_fmt.width*m_fmt.height );
  for (uint i=0; i<m_fmt.width*m_fmt.height; i++) {
    (*buf2)[i*4+0] = buf->at(i*3+2);
    (*buf2)[i*4+1] = buf->at(i*3+1);
    (*buf2)[i*4+2] = buf->at(i*3+0);
    (*buf2)[i*4+3] = 0;
  }
  delete buf;
  TQImage *temp = new TQImage( (uchar*)buf2->data(), m_fmt.width, m_fmt.height, depth, 0, 0, TQImage::LittleEndian );
  TQImage temp2 = temp->copy();
  delete temp;
  delete buf2;

  return temp2;
}







bool barcode_v4l::grab_init()
{
  m_drv = ng_vid_driver::createDriver( m_devname );
  if (!m_drv) {
      myDebug() << "no grabber device available" << endl;
      return false;
  }
  if (!(m_drv->capabilities() & CAN_CAPTURE)) {
      myDebug() << "device doesn't support capture" << endl;
      m_drv->close();
      delete m_drv;
      m_drv = 0;
      return false;
  }

  /* try native */
  m_fmt.fmtid  = VIDEO_RGB24;
  m_fmt.width  = m_grab_width;
  m_fmt.height = m_grab_height;
  if (m_drv->setformat( &m_fmt )) {
    m_fmt_drv = m_fmt;
    return true;
  }

  /* check all available conversion functions */
  m_fmt.bytesperline = m_fmt.width * ng_vfmt_to_depth[m_fmt.fmtid] / 8;
  for (int i = 0; i<VIDEO_FMT_COUNT; i++) {
    video_converter *conv = ng_vid_driver::find_video_converter( VIDEO_RGB24, i );
    if (!conv)
      continue;

    m_fmt_drv = m_fmt;
    m_fmt_drv.fmtid = conv->fmtid_in();
    m_fmt_drv.bytesperline = 0;
    if (m_drv->setformat( &m_fmt_drv )) {
      m_fmt.width  = m_fmt_drv.width;
      m_fmt.height = m_fmt_drv.height;
      m_conv = conv;
      //m_conv->init( &m_fmt );
      return true;
    }
  }

  myDebug() << "can't get rgb24 data" << endl;
  m_drv->close();
  delete m_drv;
  m_drv = 0;
  return false;
}


ng_vid_driver* ng_vid_driver::createDriver( TQString device )
{
    /* check all grabber drivers */
    ng_vid_driver *drv = new ng_vid_driver_v4l();
    if (!drv->open2( device )) {
      myDebug() << "no v4l device found" << endl;
      delete drv;
      drv = 0;
    }

    return drv;
}

int ng_vid_driver::xioctl( int fd, int cmd, void *arg )
{
  int rc;

  rc = ioctl(fd,cmd,arg);
  if (rc == 0)
    return 0;
  //print_ioctl(stderr,ioctls_v4l1,PREFIX,cmd,arg);
  tqDebug( ": %s\n",(rc == 0) ? "ok" : strerror(errno) );
  return rc;
}



void ng_vid_driver::register_video_converter( video_converter *conv )
{
  if (!conv)
    return;

  m_converter.append( conv );
}

video_converter *ng_vid_driver::find_video_converter( int out, int in )
{
  video_converter *conv;
  for (conv = m_converter.first(); conv; conv = m_converter.next() )
      if ((conv->fmtid_in() == in) && (conv->fmtid_out() == out))
        return conv;
  return 0;
}


















ng_vid_driver_v4l::ng_vid_driver_v4l()
{
  m_name = "v4l";
  m_drv = 0;
  m_fd = -1;

  m_use_read = false;

  for (int i=0; i<VIDEO_FMT_COUNT; i++)
    format2palette[i] = 0;
  format2palette[VIDEO_RGB08]    = VIDEO_PALETTE_HI240;
  format2palette[VIDEO_GRAY]     = VIDEO_PALETTE_GREY;
  format2palette[VIDEO_RGB15_LE] = VIDEO_PALETTE_RGB555;
  format2palette[VIDEO_RGB16_LE] = VIDEO_PALETTE_RGB565;
  format2palette[VIDEO_BGR24]    = VIDEO_PALETTE_RGB24;
  format2palette[VIDEO_BGR32]    = VIDEO_PALETTE_RGB32;
  format2palette[VIDEO_YUYV]     = VIDEO_PALETTE_YUV422;
  format2palette[VIDEO_UYVY]     = VIDEO_PALETTE_UYVY;
  format2palette[VIDEO_YUV422P]  = VIDEO_PALETTE_YUV422P;
  format2palette[VIDEO_YUV420P]  = VIDEO_PALETTE_YUV420P;
}

bool ng_vid_driver_v4l::open2( TQString device )
{
  /* open device */
  if ((m_fd = ::open( device.latin1(), O_RDWR )) == -1) {
    tqDebug( "v4l: open %s: %s\n", device.latin1(), strerror(errno) );
    return false;
  }
  if (ioctl( m_fd, VIDIOCGCAP, &m_capability ) == -1) {
    ::close( m_fd );
    return false;
  }

#ifdef Barcode_DEBUG
  tqDebug( "v4l: open: %s (%s)\n", device.latin1(), m_capability.name );
#endif

  fcntl( m_fd, F_SETFD, FD_CLOEXEC ); // close on exit

#ifdef Barcode_DEBUG
  myDebug() << "  capabilities: " << endl;
  for (int i = 0; device_cap[i] != NULL; i++)
    if (m_capability.type & (1 << i))
      tqDebug( " %s", device_cap[i] );
  tqDebug( "  size    : %dx%d => %dx%d", m_capability.minwidth, m_capability.minheight, m_capability.maxwidth, m_capability.maxheight );
#endif

#ifdef Barcode_DEBUG
  fprintf(stderr,"  v4l: using read() for capture\n");
#endif
  m_use_read = true;

  return true;
}

void ng_vid_driver_v4l::close()
{
#ifdef Barcode_DEBUG
  fprintf(stderr, "v4l: close\n");
#endif

  if (m_fd != -1)
    ::close(m_fd);
  m_fd = -1;
  return;
}

int ng_vid_driver_v4l::capabilities()
{
    int ret = 0;

    if (m_capability.type & VID_TYPE_OVERLAY)
	ret |= CAN_OVERLAY;
    if (m_capability.type & VID_TYPE_CAPTURE)
	ret |= CAN_CAPTURE;
    if (m_capability.type & VID_TYPE_TUNER)
	ret |= CAN_TUNE;
    if (m_capability.type & VID_TYPE_CHROMAKEY)
	ret |= NEEDS_CHROMAKEY;
    return ret;
}

bool ng_vid_driver_v4l::setformat( ng_video_fmt *fmt )
{
  bool rc = false;

#ifdef Barcode_DEBUG
  fprintf(stderr,"v4l: setformat\n");
#endif
  if (m_use_read) {
    rc = read_setformat( fmt );
  } else {
    Q_ASSERT( false );
  }

  m_fmt = *fmt;

  return rc;
}





bool ng_vid_driver_v4l::read_setformat( ng_video_fmt *fmt )
{
    xioctl( m_fd, VIDIOCGCAP, &m_capability );
    if (fmt->width > static_cast<uint>(m_capability.maxwidth))
      fmt->width = m_capability.maxwidth;
    if (fmt->height > static_cast<uint>(m_capability.maxheight))
      fmt->height = m_capability.maxheight;
    fmt->bytesperline = fmt->width * ng_vfmt_to_depth[fmt->fmtid] / 8;

    xioctl( m_fd, VIDIOCGPICT, &m_pict );
    m_pict.depth   = ng_vfmt_to_depth[fmt->fmtid];
    m_pict.palette = GETELEM(format2palette,fmt->fmtid,0);
    m_pict.brightness = 20000;
    if (-1 == xioctl( m_fd, VIDIOCSPICT, &m_pict ))
      return false;

    xioctl( m_fd, VIDIOCGWIN, &m_win);
    m_win.width = m_fmt.width;
    m_win.height = m_fmt.height;
    m_win.clipcount = 0;
    if (-1 == xioctl( m_fd, VIDIOCSWIN, &m_win ))
      return false;

    return true;
}

TQByteArray* ng_vid_driver_v4l::getimage2()
{
#ifdef Barcode_DEBUG
  myDebug() << "v4l: getimage2" << endl;
#endif

  return read_getframe2();
}


TQByteArray* ng_vid_driver_v4l::read_getframe2()
{
    TQByteArray* buf;
    int size;

    size = m_fmt.bytesperline * m_fmt.height;
    buf = new TQByteArray( size );
    if (size != read( m_fd, buf->data(), size )) {
      delete( buf );
      return 0;
    }
    return buf;
}




























#define GRAY(val)		barcodeRecognition::ng_yuv_gray[val]
#define RED(gray,red)		ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_red[red] ]
#define GREEN(gray,red,blue)	ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_g1[red] +	\
						       barcodeRecognition::ng_yuv_g2[blue] ]
#define BLUE(gray,blue)		ng_clip[ CLIP + gray + barcodeRecognition::ng_yuv_blue[blue] ]

void barcodeRecognition::yuv420p_to_rgb24::frame( TQByteArray *out, const TQByteArray *in, const ng_video_fmt fmt )
{
    unsigned char *y, *u, *v, *d;
    unsigned char *us,*vs;
    unsigned char *dp;
    unsigned int i,j;
    int gray;

    dp = (unsigned char*)out->data();
    y  = (unsigned char*)in->data();
    u  = y + fmt.width * fmt.height;
    v  = u + fmt.width * fmt.height / 4;

    for (i = 0; i < fmt.height; i++) {
	d = dp;
	us = u; vs = v;
	for (j = 0; j < fmt.width; j+= 2) {
	    gray   = GRAY(*y);
	    *(d++) = RED(gray,*v);
	    *(d++) = GREEN(gray,*v,*u);
	    *(d++) = BLUE(gray,*u);
	    y++;
	    gray   = GRAY(*y);
	    *(d++) = RED(gray,*v);
	    *(d++) = GREEN(gray,*v,*u);
	    *(d++) = BLUE(gray,*u);
	    y++; u++; v++;
	}
	if (0 == (i % 2)) {
	    u = us; v = vs;
	}
	dp += 3*fmt.width;
    }
}
