/*
    Copyright (C) 2005 by Jasem Mutlaq

    Based on V4L 2 Example
    http://v4l2spec.bytesex.org/spec-single/v4l2.html#CAPTURE-EXAMPLE

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/ 

#include <iostream>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
#include <asm/types.h>          /* for videodev2.h */

#include "ccvt.h"
#include "v4l2_base.h"
#include "../eventloop.h"
#include "../indidevapi.h"

#define ERRMSGSIZ	1024

#define CLEAR(x) memset (&(x), 0, sizeof (x))

using namespace std;

V4L2_Base::V4L2_Base()
{
   frameRate=10;
   selectCallBackID = -1;
   dropFrame = false;
  
   xmax = xmin = 160;
   ymax = ymin = 120;

   io		= IO_METHOD_MMAP;
   fd           = -1;
   buffers      = NULL;
   n_buffers    = 0;

   YBuf         = NULL;
   UBuf         = NULL;
   VBuf         = NULL;
   colorBuffer  = NULL;
   rgb24_buffer = NULL;
   callback     = NULL;

}

V4L2_Base::~V4L2_Base()
{

  delete (YBuf);
  delete (UBuf);
  delete (VBuf);
  delete (colorBuffer);
  delete (rgb24_buffer);

}

int V4L2_Base::xioctl(int fd, int request, void *arg)
{
        int r;

        do r = ioctl (fd, request, arg);
        while (-1 == r && EINTR == errno);

        return r;
}

int V4L2_Base::errno_exit(const char *s, char *errmsg)
{
        fprintf (stderr, "%s error %d, %s\n",
                 s, errno, strerror (errno));
     
        snprintf(errmsg, ERRMSGSIZ, "%s error %d, %s\n", s, errno, strerror (errno));

        return -1;
} 

int V4L2_Base::connectCam(const char * devpath, char *errmsg , int pixelFormat , int width , int height )
{
   frameRate=10;
   selectCallBackID = -1;
   dropFrame = false;

    if (open_device (devpath, errmsg) < 0)
      return -1;

    if (init_device(errmsg, pixelFormat, width, height) < 0)
      return -1;

   cerr << "V4L 2 - All successful, returning\n";
   return fd;
}

void V4L2_Base::disconnectCam()
{
   char errmsg[ERRMSGSIZ];
   delete YBuf;
   delete UBuf;
   delete VBuf;
   YBuf = UBuf = VBuf = NULL;
   
   if (selectCallBackID != -1)
     rmCallback(selectCallBackID);
     
   stop_capturing (errmsg);

   uninit_device (errmsg);

   close_device ();
     
   fprintf(stderr, "Disconnect cam\n");
}

int V4L2_Base::read_frame(char *errmsg)
{
        struct v4l2_buffer buf;
	unsigned int i;
        //cerr << "in read Frame" << endl;

	switch (io) {
	case IO_METHOD_READ:
    		if (-1 == read (fd, buffers[0].start, buffers[0].length)) {
            		switch (errno) {
            		case EAGAIN:
                    		return 0;

			case EIO:
				/* Could ignore EIO, see spec. */

				/* fall through */

			default:
				return errno_exit ("read", errmsg);
			}
		}

    		//process_image (buffers[0].start);

		break;

	case IO_METHOD_MMAP:
		CLEAR (buf);

            	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            	buf.memory = V4L2_MEMORY_MMAP;

    		if (-1 == xioctl (fd, VIDIOC_DTQBUF, &buf)) {
            		switch (errno) {
            		case EAGAIN:
                    		return 0;

			case EIO:
				/* Could ignore EIO, see spec. */

				/* fall through */

			default:
				return errno_exit ("VIDIOC_DTQBUF", errmsg);
			}
		}

                assert (buf.index < n_buffers);

                switch (fmt.fmt.pix.pixelformat)
                {
                  case V4L2_PIX_FMT_YUV420:
                    memcpy(YBuf,((unsigned char *) buffers[buf.index].start), fmt.fmt.pix.width * fmt.fmt.pix.height);
                    memcpy(UBuf,((unsigned char *) buffers[buf.index].start) + fmt.fmt.pix.width * fmt.fmt.pix.height, (fmt.fmt.pix.width/2) * (fmt.fmt.pix.height/2));
                    memcpy(VBuf,((unsigned char *) buffers[buf.index].start) + fmt.fmt.pix.width * fmt.fmt.pix.height + (fmt.fmt.pix.width/2) * (fmt.fmt.pix.height/2), (fmt.fmt.pix.width/2) * (fmt.fmt.pix.width/2));
		    break;

                  case V4L2_PIX_FMT_YUYV:
                     ccvt_yuyv_420p( fmt.fmt.pix.width , fmt.fmt.pix.height, buffers[buf.index].start, YBuf, UBuf, VBuf);
		     break;

                 case V4L2_PIX_FMT_RGB24:
			RGB2YUV(fmt.fmt.pix.width, fmt.fmt.pix.height, buffers[buf.index].start, YBuf, UBuf, VBuf, 0);
                        break;

                 case V4L2_PIX_FMT_SBGGR8:
                         bayer2rgb24(rgb24_buffer, ((unsigned char *) buffers[buf.index].start), fmt.fmt.pix.width, fmt.fmt.pix.height);
                         break;
                }

		if (-1 == xioctl (fd, VIDIOC_TQBUF, &buf))
			return errno_exit ("VIDIOC_TQBUF", errmsg);
                  
                if (dropFrame)
                {
                  dropFrame = false;
                  return 0;
                } 

                /* Call provided callback function if any */
                 if (callback)
                	(*callback)(uptr);

		break;

	case IO_METHOD_USERPTR:
		CLEAR (buf);

    		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    		buf.memory = V4L2_MEMORY_USERPTR;

		if (-1 == xioctl (fd, VIDIOC_DTQBUF, &buf)) {
			switch (errno) {
			case EAGAIN:
				return 0;

			case EIO:
				/* Could ignore EIO, see spec. */

				/* fall through */

			default:
				errno_exit ("VIDIOC_DTQBUF", errmsg);
			}
		}

		for (i = 0; i < n_buffers; ++i)
			if (buf.m.userptr == (unsigned long) buffers[i].start
			    && buf.length == buffers[i].length)
				break;

		assert (i < n_buffers);

    		//process_image ((void *) buf.m.userptr);

		if (-1 == xioctl (fd, VIDIOC_TQBUF, &buf))
			errno_exit ("VIDIOC_TQBUF", errmsg);

		break;
	}

	return 0;
}

int V4L2_Base::stop_capturing(char *errmsg)
{
        enum v4l2_buf_type type;

	switch (io) {
	case IO_METHOD_READ:
		/* Nothing to do. */
		break;

	case IO_METHOD_MMAP:
	case IO_METHOD_USERPTR:
		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

                IERmCallback(selectCallBackID);
                selectCallBackID = -1;

                dropFrame = true;

		if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
			return errno_exit ("VIDIOC_STREAMOFF", errmsg);

                

		break;
	}

   return 0;
}

int V4L2_Base::start_capturing(char * errmsg)
{
        unsigned int i;
        enum v4l2_buf_type type;

	switch (io) {
	case IO_METHOD_READ:
		/* Nothing to do. */
		break;

	case IO_METHOD_MMAP:
		for (i = 0; i < n_buffers; ++i) {
            		struct v4l2_buffer buf;

        		CLEAR (buf);

        		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        		buf.memory      = V4L2_MEMORY_MMAP;
        		buf.index       = i;

        		if (-1 == xioctl (fd, VIDIOC_TQBUF, &buf))
                    		return errno_exit ("VIDIOC_TQBUF", errmsg);

		}
		
		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

		if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
			return errno_exit ("VIDIOC_STREAMON", errmsg);

                

                selectCallBackID = IEAddCallback(fd, newFrame, this);

		break;

	case IO_METHOD_USERPTR:
		for (i = 0; i < n_buffers; ++i) {
            		struct v4l2_buffer buf;

        		CLEAR (buf);

        		buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        		buf.memory      = V4L2_MEMORY_USERPTR;
        		buf.m.userptr	= (unsigned long) buffers[i].start;
			buf.length      = buffers[i].length;

        		if (-1 == xioctl (fd, VIDIOC_TQBUF, &buf))
                    		return errno_exit ("VIDIOC_TQBUF", errmsg);
		}


		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

		if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
			return errno_exit ("VIDIOC_STREAMON", errmsg);

		break;
	}

  return 0;

}

void V4L2_Base::newFrame(int /*fd*/, void *p)
{
  char errmsg[ERRMSGSIZ];

  ( (V4L2_Base *) (p))->read_frame(errmsg);

}

int V4L2_Base::uninit_device(char *errmsg)
{

	switch (io) {
	case IO_METHOD_READ:
		free (buffers[0].start);
		break;

	case IO_METHOD_MMAP:
		for (unsigned int i = 0; i < n_buffers; ++i)
			if (-1 == munmap (buffers[i].start, buffers[i].length))
				return errno_exit ("munmap", errmsg);
		break;

	case IO_METHOD_USERPTR:
		for (unsigned int i = 0; i < n_buffers; ++i)
			free (buffers[i].start);
		break;
	}

	free (buffers);

   return 0;
}

void V4L2_Base::init_read(unsigned int buffer_size)
{
        buffers = (buffer *) calloc (1, sizeof (*buffers));

        if (!buffers) {
                fprintf (stderr, "Out of memory\n");
                exit (EXIT_FAILURE);
        }

	buffers[0].length = buffer_size;
	buffers[0].start = malloc (buffer_size);

	if (!buffers[0].start) {
    		fprintf (stderr, "Out of memory\n");
            	exit (EXIT_FAILURE);
	}
}

int V4L2_Base::init_mmap(char *errmsg)
{
	struct v4l2_requestbuffers req;

        CLEAR (req);

        req.count               = 4;
        req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory              = V4L2_MEMORY_MMAP;

	if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
                if (EINVAL == errno) {
                        fprintf (stderr, "%s does not support "
                                 "memory mapping\n", dev_name);
                        snprintf(errmsg, ERRMSGSIZ, "%s does not support "
                                 "memory mapping\n", dev_name);
                        return -1;
                } else {
                        return errno_exit ("VIDIOC_REQBUFS", errmsg);
                }
        }

        if (req.count < 2) {
                fprintf (stderr, "Insufficient buffer memory on %s\n",
                         dev_name);
                snprintf(errmsg, ERRMSGSIZ, "Insufficient buffer memory on %s\n",
                         dev_name);
		return -1;
        }

        buffers = (buffer *) calloc (req.count, sizeof (*buffers));

        if (!buffers)
        {
                fprintf (stderr, "buffers. Out of memory\n");
                strncpy(errmsg, "buffers. Out of memory\n", ERRMSGSIZ);
                return -1;
        }

        for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
        {
                struct v4l2_buffer buf;

                CLEAR (buf);

                buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf.memory      = V4L2_MEMORY_MMAP;
                buf.index       = n_buffers;

                if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
                        return errno_exit ("VIDIOC_QUERYBUF", errmsg);

                buffers[n_buffers].length = buf.length;
                buffers[n_buffers].start =
                        mmap (NULL /* start anywhere */,
                              buf.length,
                              PROT_READ | PROT_WRITE /* required */,
                              MAP_SHARED /* recommended */,
                              fd, buf.m.offset);

                if (MAP_FAILED == buffers[n_buffers].start)
                        return errno_exit ("mmap", errmsg);
        }

   return 0;
}

void V4L2_Base::init_userp(unsigned int buffer_size)
{
	struct v4l2_requestbuffers req;
	char errmsg[ERRMSGSIZ];

        CLEAR (req);

        req.count               = 4;
        req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory              = V4L2_MEMORY_USERPTR;

        if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
                if (EINVAL == errno) {
                        fprintf (stderr, "%s does not support "
                                 "user pointer i/o\n", dev_name);
                        exit (EXIT_FAILURE);
                } else {
                        errno_exit ("VIDIOC_REQBUFS", errmsg);
                }
        }

        buffers = (buffer *) calloc (4, sizeof (*buffers));

        if (!buffers) {
                fprintf (stderr, "Out of memory\n");
                exit (EXIT_FAILURE);
        }

        for (n_buffers = 0; n_buffers < 4; ++n_buffers) {
                buffers[n_buffers].length = buffer_size;
                buffers[n_buffers].start = malloc (buffer_size);

                if (!buffers[n_buffers].start) {
    			fprintf (stderr, "Out of memory\n");
            		exit (EXIT_FAILURE);
		}
        }
}

int V4L2_Base::init_device(char *errmsg, int pixelFormat , int width, int height)
{
	unsigned int min;

        if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap))
        {
                if (EINVAL == errno) {
                        fprintf (stderr, "%s is no V4L2 device\n",
                                 dev_name);
                        snprintf(errmsg, ERRMSGSIZ, "%s is no V4L2 device\n", dev_name);
                        return -1;
                } else {
                        return errno_exit ("VIDIOC_QUERYCAP", errmsg);
                }
        }

        if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
        {
                fprintf (stderr, "%s is no video capture device\n",
                         dev_name);
                snprintf(errmsg, ERRMSGSIZ, "%s is no video capture device\n", dev_name);
                return -1;
        }

	switch (io) 
        {
	case IO_METHOD_READ:
		if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
			fprintf (stderr, "%s does not support read i/o\n",
				 dev_name);
                        snprintf(errmsg, ERRMSGSIZ, "%s does not support read i/o\n",
				 dev_name);
			return -1;
		}

		break;

	case IO_METHOD_MMAP:
	case IO_METHOD_USERPTR:
		if (!(cap.capabilities & V4L2_CAP_STREAMING))
                {
			fprintf (stderr, "%s does not support streaming i/o\n",
				 dev_name);
                        snprintf(errmsg, ERRMSGSIZ, "%s does not support streaming i/o\n",
				 dev_name);
			return -1;
		}

		break;
	}

        /* Select video input, video standard and tune here. */

        cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        if (-1 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
                /* Errors ignored. */
        }

        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect; /* reset to default */

        if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
                switch (errno) {
                case EINVAL:
                        /* Cropping not supported. */
                        break;
                default:
                        /* Errors ignored. */
                        break;
                }
        }

        CLEAR (fmt);

        fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt.fmt.pix.width       = width; 
        fmt.fmt.pix.height      = height;
        fmt.fmt.pix.pixelformat = pixelFormat;
        //fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

        if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
                return errno_exit ("VIDIOC_S_FMT", errmsg);

        /* Note VIDIOC_S_FMT may change width and height. */

	/* Buggy driver paranoia. */
	min = fmt.fmt.pix.width * 2;
	if (fmt.fmt.pix.bytesperline < min)
		fmt.fmt.pix.bytesperline = min;
	min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
	if (fmt.fmt.pix.sizeimage < min)
		fmt.fmt.pix.sizeimage = min;

        /* Let's get the actual size */
        CLEAR(fmt);

        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	if (-1 == xioctl (fd, VIDIOC_G_FMT, &fmt))
                return errno_exit ("VIDIOC_G_FMT", errmsg);

        cerr << "width: " << fmt.fmt.pix.width << " - height: " << fmt.fmt.pix.height << endl;

       switch (pixelFormat)
       {
        case V4L2_PIX_FMT_YUV420:
         cerr << "pixel format: V4L2_PIX_FMT_YUV420" << endl;
        break;

        case V4L2_PIX_FMT_YUYV:
          cerr << "pixel format: V4L2_PIX_FMT_YUYV" << endl;
         break;

        case V4L2_PIX_FMT_RGB24:
          cerr << "pixel format: V4L2_PIX_FMT_RGB24" << endl;
         break;

        case V4L2_PIX_FMT_SBGGR8:
         cerr << "pixel format: V4L2_PIX_FMT_SBGGR8" << endl;
         break;

       }

        findMinMax();

        allocBuffers();

	switch (io)
        {
	case IO_METHOD_READ:
		init_read (fmt.fmt.pix.sizeimage);
		break;

	case IO_METHOD_MMAP:
		return init_mmap(errmsg);
		break;

	case IO_METHOD_USERPTR:
		init_userp (fmt.fmt.pix.sizeimage);
		break;
	}

  return 0;
}

void V4L2_Base::close_device(void)
{
        char errmsg[ERRMSGSIZ];

        if (-1 == close (fd))
	        errno_exit ("close", errmsg);

        fd = -1;
}

int V4L2_Base::open_device(const char *devpath, char *errmsg)
{
        struct stat st; 

        strncpy(dev_name, devpath, 64);

        if (-1 == stat (dev_name, &st)) {
                fprintf (stderr, "Cannot identify '%s': %d, %s\n",
                         dev_name, errno, strerror (errno));
                snprintf(errmsg, ERRMSGSIZ, "Cannot identify '%s': %d, %s\n",
                         dev_name, errno, strerror (errno));
                return -1;
        }

        if (!S_ISCHR (st.st_mode))
        {
                fprintf (stderr, "%s is no device\n", dev_name);
                snprintf(errmsg, ERRMSGSIZ, "%s is no device\n", dev_name);
                return -1;
        }

        fd = open (dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

        if (-1 == fd)
        {
                fprintf (stderr, "Cannot open '%s': %d, %s\n",
                         dev_name, errno, strerror (errno));
                snprintf(errmsg, ERRMSGSIZ, "Cannot open '%s': %d, %s\n",
                         dev_name, errno, strerror (errno));
                return -1;
        }

  return 0;
}



int V4L2_Base::getWidth()
{
  return fmt.fmt.pix.width;
}

int V4L2_Base::getHeight()
{
 return fmt.fmt.pix.height;
}

void V4L2_Base::setFPS(int fps)
{
  frameRate = 15;//fps;
}

int V4L2_Base::getFPS()
{
  return 15;
}

char * V4L2_Base::getDeviceName()
{
  return ((char *) cap.card);
}

void V4L2_Base::allocBuffers()
{
   delete (YBuf); YBuf = NULL;
   delete (UBuf); UBuf = NULL;
   delete (VBuf); VBuf = NULL;
   delete (colorBuffer); colorBuffer = NULL;
   delete (rgb24_buffer); rgb24_buffer = NULL;
   
   YBuf= new unsigned char[ fmt.fmt.pix.width * fmt.fmt.pix.height];
   UBuf= new unsigned char[ fmt.fmt.pix.width * fmt.fmt.pix.height];
   VBuf= new unsigned char[ fmt.fmt.pix.width * fmt.fmt.pix.height];
   colorBuffer = new unsigned char[fmt.fmt.pix.width * fmt.fmt.pix.height * 4];

   if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_SBGGR8)
     rgb24_buffer = new unsigned char[fmt.fmt.pix.width * fmt.fmt.pix.height * 3];
}

void V4L2_Base::getMaxMinSize(int & x_max, int & y_max, int & x_min, int & y_min)
{
  x_max = xmax; y_max = ymax; x_min = xmin; y_min = ymin;
}

int V4L2_Base::setSize(int x, int y)
{
   char errmsg[ERRMSGSIZ];
   int oldW, oldH;
 
   oldW = fmt.fmt.pix.width;
   oldH = fmt.fmt.pix.height;

   fmt.fmt.pix.width  = x;
   fmt.fmt.pix.height = y;

   if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
   {
        fmt.fmt.pix.width  = oldW;
        fmt.fmt.pix.height = oldH;
        return errno_exit ("VIDIOC_S_FMT", errmsg);
   }

   /* PWC bug? It seems that setting the "wrong" width and height will mess something in the driver.
      Only 160x120, 320x280, and 640x480 are accepted. If I try to set it for example to 300x200, it wii
      get set to 320x280, which is fine, but then the video information is messed up for some reason. */
      xioctl (fd, VIDIOC_S_FMT, &fmt);
 
  
   allocBuffers();

  return 0;
}

void V4L2_Base::setContrast(int val)
{
   /*picture_.contrast=val;
   updatePictureSettings();*/
}

int V4L2_Base::getContrast()
{
   return 255;//picture_.contrast;
}

void V4L2_Base::setBrightness(int val)
{
   /*picture_.brightness=val;
   updatePictureSettings();*/
}

int V4L2_Base::getBrightness()
{
   return 255;//picture_.brightness;
}

void V4L2_Base::setColor(int val)
{
   /*picture_.colour=val;
   updatePictureSettings();*/
}

int V4L2_Base::getColor()
{
   return 255; //picture_.colour;
}

void V4L2_Base::setHue(int val)
{
   /*picture_.hue=val;
   updatePictureSettings();*/
}

int V4L2_Base::getHue()
{
   return 255;//picture_.hue;
}

void V4L2_Base::setWhiteness(int val)
{
   /*picture_.whiteness=val;
   updatePictureSettings();*/
}

int V4L2_Base::getWhiteness() 
{
   return 255;//picture_.whiteness;
}

void V4L2_Base::setPictureSettings()
{
   /*if (ioctl(device_, VIDIOCSPICT, &picture_) ) {
      cerr << "updatePictureSettings" << endl;
   }
   ioctl(device_, VIDIOCGPICT, &picture_);*/
}

void V4L2_Base::getPictureSettings()
{
   /*if (ioctl(device_, VIDIOCGPICT, &picture_) )
   {
      cerr << "refreshPictureSettings" << endl;
   }*/
}

unsigned char * V4L2_Base::getY()
{
  if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_SBGGR8)
      RGB2YUV(fmt.fmt.pix.width, fmt.fmt.pix.height, rgb24_buffer, YBuf, UBuf, VBuf, 0);

  return YBuf;
}

unsigned char * V4L2_Base::getU()
{
 return UBuf;
}

unsigned char * V4L2_Base::getV()
{
 return VBuf;
}

unsigned char * V4L2_Base::getColorBuffer()
{
  //cerr << "in get color buffer " << endl;

  switch (fmt.fmt.pix.pixelformat) 
  {
      case V4L2_PIX_FMT_YUV420:
        ccvt_420p_bgr32(fmt.fmt.pix.width, fmt.fmt.pix.height,
                      buffers[0].start, (void*)colorBuffer);
      break;

    case V4L2_PIX_FMT_YUYV:
         ccvt_yuyv_bgr32(fmt.fmt.pix.width, fmt.fmt.pix.height,
                      buffers[0].start, (void*)colorBuffer);
         break;

    case V4L2_PIX_FMT_RGB24:
         ccvt_rgb24_bgr32(fmt.fmt.pix.width, fmt.fmt.pix.height,
                      buffers[0].start, (void*)colorBuffer);
         break;

    case V4L2_PIX_FMT_SBGGR8:
         ccvt_rgb24_bgr32(fmt.fmt.pix.width, fmt.fmt.pix.height,
                      rgb24_buffer, (void*)colorBuffer);
         break;

   default:
    break;
  }

  return colorBuffer;
}

void V4L2_Base::registerCallback(WPF *fp, void *ud)
{
  callback = fp;
  uptr = ud;
}

void V4L2_Base::findMinMax()
{
    char errmsg[ERRMSGSIZ];
    struct v4l2_format tryfmt;
    CLEAR(tryfmt);

    xmin = xmax = fmt.fmt.pix.width;
    ymin = ymax = fmt.fmt.pix.height;

    tryfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    tryfmt.fmt.pix.width       = 10;
    tryfmt.fmt.pix.height      = 10;
    tryfmt.fmt.pix.pixelformat = fmt.fmt.pix.pixelformat;
    tryfmt.fmt.pix.field       = fmt.fmt.pix.field;

    if (-1 == xioctl (fd, VIDIOC_TRY_FMT, &tryfmt))
    {
        errno_exit ("VIDIOC_TRY_FMT 1", errmsg);
        return;
    }
   
    xmin = tryfmt.fmt.pix.width;
    ymin = tryfmt.fmt.pix.height;

    tryfmt.fmt.pix.width       = 1600;
    tryfmt.fmt.pix.height      = 1200;

    if (-1 == xioctl (fd, VIDIOC_TRY_FMT, &tryfmt))
    {
                errno_exit ("VIDIOC_TRY_FMT 2", errmsg);
                return;
    }

    xmax = tryfmt.fmt.pix.width;
    ymax = tryfmt.fmt.pix.height;

    cerr << "Min X: " << xmin << " - Max X: " << xmax << " - Min Y: " << ymin << " - Max Y: " << ymax << endl;
}

void V4L2_Base::enumerate_ctrl (void)
{
  char errmsg[ERRMSGSIZ];
  CLEAR(queryctrl);

  for (queryctrl.id = V4L2_CID_BASE; queryctrl.id < V4L2_CID_LASTP1; queryctrl.id++)
  {
        if (0 == xioctl (fd, VIDIOC_QUERYCTRL, &queryctrl))
        {
               cerr << "Control " << queryctrl.name << endl;

                if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
                        continue;

                cerr << "Control " << queryctrl.name << endl;

                if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
                        enumerate_menu ();
        } else
        {
                if (errno == EINVAL)
                        continue;

                errno_exit("VIDIOC_QUERYCTRL", errmsg);
                return;
        }
  }

  for (queryctrl.id = V4L2_CID_PRIVATE_BASE;  ;  queryctrl.id++)
  {
        if (0 == xioctl (fd, VIDIOC_QUERYCTRL, &queryctrl))
        {
              cerr << "Private Control " << queryctrl.name << endl;

                if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
                        continue;

                if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
                        enumerate_menu ();
        } else {
                if (errno == EINVAL)
                        break;

                errno_exit ("VIDIOC_QUERYCTRL", errmsg);
                return;
        }

  }

}

void V4L2_Base::enumerate_menu (void)
{
  char errmsg[ERRMSGSIZ];
  cerr << "  Menu items:" << endl;

        CLEAR(querymenu);
        querymenu.id = queryctrl.id;

        for (querymenu.index = queryctrl.minimum;  querymenu.index <= queryctrl.maximum; querymenu.index++)
         {
                if (0 == xioctl (fd, VIDIOC_QUERYMENU, &querymenu))
                {
                        cerr << "  " <<  querymenu.name << endl;
                } else
                {
                        errno_exit("VIDIOC_QUERYMENU", errmsg);
                        return;
                }
         }
}

int  V4L2_Base::query_ctrl(unsigned int ctrl_id, double & ctrl_min, double & ctrl_max, double & ctrl_step, double & ctrl_value, char *errmsg)
{ 

    struct v4l2_control control;

    CLEAR(queryctrl);

    queryctrl.id = ctrl_id;

   if (-1 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl))
   {
        if (errno != EINVAL) 
                return errno_exit ("VIDIOC_QUERYCTRL", errmsg);
                
        else 
        {
                cerr << "#" << ctrl_id << " is not supported" << endl;
                snprintf(errmsg, ERRMSGSIZ, "# %d is not supported", ctrl_id);
		return -1;
        }
   } else if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
   {
        cerr << "#" << ctrl_id << " is disabled" << endl;
        snprintf(errmsg, ERRMSGSIZ, "# %d is disabled", ctrl_id);
        return -1;
   }

   ctrl_min   = queryctrl.minimum;
   ctrl_max   = queryctrl.maximum;
   ctrl_step  = queryctrl.step;
   ctrl_value = queryctrl.default_value;

   /* Get current value */
   CLEAR(control);
   control.id = ctrl_id;

   if (0 == xioctl(fd, VIDIOC_G_CTRL, &control))
      ctrl_value = control.value;

    cerr << queryctrl.name << " -- min: " << ctrl_min << " max: " << ctrl_max << " step: " << ctrl_step << " value: " << ctrl_value << endl;

    return 0;

}

int  V4L2_Base::queryINTControls(INumberVectorProperty *nvp)
{
   struct v4l2_control control;
   char errmsg[ERRMSGSIZ];
   CLEAR(queryctrl);
   INumber *numbers = NULL;
   unsigned int *num_ctrls = NULL;
   int nnum=0;

  for (queryctrl.id = V4L2_CID_BASE; queryctrl.id < V4L2_CID_LASTP1; queryctrl.id++)
  {
        if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl))
        {
                if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
                {
                    cerr << queryctrl.name << " is disabled." << endl;
                    continue;
                }

                if (queryctrl.type == V4L2_CTRL_TYPE_INTEGER)
                {
                   numbers = (numbers == NULL) ? (INumber *) malloc (sizeof(INumber)) :
                                                 (INumber *) realloc (numbers, (nnum+1) * sizeof (INumber));

                   num_ctrls = (num_ctrls == NULL) ? (unsigned int *) malloc  (sizeof (unsigned int)) :
                                                     (unsigned int *) realloc (num_ctrls, (nnum+1) * sizeof (unsigned int));

                   strncpy(numbers[nnum].name, ((char *) queryctrl.name) , MAXINDINAME);
                   strncpy(numbers[nnum].label, ((char *) queryctrl.name), MAXINDILABEL);
                   strncpy(numbers[nnum].format, "%0.f", MAXINDIFORMAT);
                   numbers[nnum].min    = queryctrl.minimum;
                   numbers[nnum].max    = queryctrl.maximum;
                   numbers[nnum].step   = queryctrl.step;
                   numbers[nnum].value  = queryctrl.default_value;
     
                   /* Get current value if possible */
                   CLEAR(control);
                   control.id = queryctrl.id;
                   if (0 == xioctl(fd, VIDIOC_G_CTRL, &control))
                            numbers[nnum].value = control.value;

                   /* Store ID info in INumber. This is the first time ever I make use of aux0!! */
                   num_ctrls[nnum] = queryctrl.id;

                   cerr << queryctrl.name << " -- min: " << queryctrl.minimum << " max: " << queryctrl.maximum << " step: " << queryctrl.step << " value: " << numbers[nnum].value << endl;

                   nnum++;
                  
                }
        } else if (errno != EINVAL) 
                return errno_exit ("VIDIOC_QUERYCTRL", errmsg);

   }

  for (queryctrl.id = V4L2_CID_PRIVATE_BASE;  ;  queryctrl.id++)
  {
     if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl))
        {
                if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
                {
                    cerr << queryctrl.name << " is disabled." << endl;
                    continue;
                }

                if (queryctrl.type == V4L2_CTRL_TYPE_INTEGER)
                {
                   numbers = (numbers == NULL) ? (INumber *) malloc (sizeof(INumber)) :
                                                 (INumber *) realloc (numbers, (nnum+1) * sizeof (INumber));

                   num_ctrls = (num_ctrls == NULL) ? (unsigned int *) malloc  (sizeof (unsigned int)) :
                                                     (unsigned int *) realloc (num_ctrls, (nnum+1) * sizeof (unsigned int));

                   strncpy(numbers[nnum].name, ((char *) queryctrl.name) , MAXINDINAME);
                   strncpy(numbers[nnum].label, ((char *) queryctrl.name), MAXINDILABEL);
                   strncpy(numbers[nnum].format, "%0.f", MAXINDIFORMAT);
                   numbers[nnum].min    = queryctrl.minimum;
                   numbers[nnum].max    = queryctrl.maximum;
                   numbers[nnum].step   = queryctrl.step;
                   numbers[nnum].value  = queryctrl.default_value;
     
                   /* Get current value if possible */
                   CLEAR(control);
                   control.id = queryctrl.id;
                   if (0 == xioctl(fd, VIDIOC_G_CTRL, &control))
                            numbers[nnum].value = control.value;

                   /* Store ID info in INumber. This is the first time ever I make use of aux0!! */
                   num_ctrls[nnum] = queryctrl.id;
     
                   nnum++;
                  
                }
         }
         else break;
   } 

  /* Store numbers in aux0 */
  for (int i=0; i < nnum; i++)
      numbers[i].aux0 = &num_ctrls[i];

  nvp->np  = numbers;
  nvp->nnp = nnum;

  return nnum;

}

int  V4L2_Base::setINTControl(unsigned int ctrl_id, double new_value, char *errmsg)
{
   struct v4l2_control control;

   CLEAR(control);

   cerr << "The id is " << ctrl_id << " new value is " << new_value << endl;

   control.id = ctrl_id;
   control.value = (int) new_value;

   if (-1 == xioctl(fd, VIDIOC_S_CTRL, &control))
         return errno_exit ("VIDIOC_S_CTRL", errmsg);

   return 0;
}

