#ifndef MASLAB_CAMERA_HEADER
#define MASLAB_CAMERA_HEADER

// include video4linux
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/videodev.h>
#include <string.h>

// Include usual stuff
#include <stdio.h>
#include <stdlib.h>

// Color conversion
#include "ccvt.h"

#include "pwc-ioctl.h"

#include "camera.h"

#include <assert.h>

static int camera_configure_buffer(camera_t *cam);
static int camera_checkopen(camera_t *cam);

/** Actually open the devname, returning 0 if no error. **/
int camera_checkopen(camera_t *cam)
{
	struct video_capability vcap;

	if (cam->fd!=0)
		return 0;
	
	cam->fd = open(cam->devname,O_RDONLY);
	if (cam->fd == -1)	{
		perror(cam->devname);
		return -1;
	}
	
	if (ioctl(cam->fd, VIDIOCGCAP, &vcap)<0) {
		perror(cam->devname);		
		return -1;
	}
	
	//	printf("%s connected on %s\n", vcap.name, cam->devname);
	camera_configure_buffer(cam);
	
	return 0;
}

camera_t *camera_open(const char *devname)
{
	camera_t *cam=NULL;
	
	cam=(camera_t*) calloc(sizeof(camera_t),1);

	cam->devname = strdup(devname);

	if (camera_checkopen(cam))
	  goto error;

	return cam;

 error:
	if (cam!=NULL)
	  {
	    if (cam->devname!=NULL)
	      free(cam->devname);

	    free(cam);
	  }

	return NULL;
}

void camera_close(camera_t *cam)
{
	if (cam==NULL)
		return;

	close(cam->fd);
	free(cam->devname);
	free(cam);
}

int camera_configure_buffer(camera_t *cam)
{
	struct video_window vwin;
 
	if (cam->buffer!=NULL)
		free(cam->buffer);
	cam->buffer=NULL;

	ioctl(cam->fd,VIDIOCGWIN,&vwin);      
	cam->width = vwin.width;
	cam->height = vwin.height;
	cam->buflen = (int) (cam->width*cam->height*1.5);
	cam->buffer = (unsigned char*) malloc(cam->buflen);
	cam->fps = (vwin.flags>>PWC_FPS_SHIFT)&0x1f;

	//	printf("Reallocating buffer, %ix%i\n", cam->width, cam->height);

	return 1;
}

int camera_width(camera_t *cam)
{
	return cam->width;
}

int camera_height(camera_t *cam)
{
	return cam->height;
}

int camera_fps(camera_t *cam)
{
	return cam->fps;
}

void camera_capture(camera_t *cam, unsigned char *img)
{
	if (camera_checkopen(cam))
		return;

	int res = read(cam->fd, cam->buffer, cam->buflen);
	if (res <= 0)
	  {
	    close(cam->fd);
	    cam->fd = 0;
	    usleep(200000);
	    return;
	  }

	// On intel hosts, our byte order is the opposite of
	// java. On big-endian hosts, we would use the more 
	// natural seeming rgb32 specification.
	ccvt_420p_bgr32(cam->width, cam->height, cam->buffer, img);

	return;
}

int camera_set_capture_settings(camera_t *cam, int w, int h, int fps)
{
	struct video_window vwin;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCGWIN, &vwin);
	vwin.width = w;
	vwin.height = h;
	vwin.flags &= ~PWC_FPS_FRMASK;
	vwin.flags |= (fps<<PWC_FPS_SHIFT);

	if (ioctl(cam->fd,VIDIOCSWIN,&vwin) < 0)
		return 0;

	camera_configure_buffer(cam);

	return 1;
}

int camera_set_white_balance(camera_t *cam, int mode, int red, int blue)
{
	struct pwc_whitebalance wb;

	if (camera_checkopen(cam))
		return 0;

	if (mode > 4 || mode < 0)
		return 0;

	if (ioctl(cam->fd, VIDIOCPWCGAWB, &wb)<0)
		return 0;
  
	wb.mode = mode;
	wb.manual_red = red;
	wb.manual_blue = blue;

	if (ioctl(cam->fd,VIDIOCPWCSAWB, &wb)<0)
		return 0;

	return 1;
}

int camera_get_white_balance(camera_t *cam, int field)
{
	struct pwc_whitebalance wb;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGAWB, &wb);

	if (field==0)
		return wb.mode;
	else if (field==1)
		return wb.manual_red;
	else
		return wb.manual_blue;
}

void camera_set_led(camera_t *cam, int on_time, int off_time)
{
	struct pwc_leds l;

	if (camera_checkopen(cam))
		return;

	l.led_on = on_time;
	l.led_off = off_time;
	ioctl(cam->fd,VIDIOCPWCSLED,&l);
}

int camera_get_led_on(camera_t *cam)
{
	struct pwc_leds l;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd,VIDIOCPWCSLED,&l);

	return l.led_on;
}

int camera_get_led_off(camera_t *cam)
{
	struct pwc_leds l;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd,VIDIOCPWCSLED,&l);

	return l.led_off;
}

void camera_set_quality(camera_t *cam, int value)
{
	assert(value>=0 && value<=3);

	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSCQUAL, &value);
}

int camera_get_quality(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGCQUAL, &value);	

	return value;
}

void camera_set_contour(camera_t *cam, int value)
{
	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSCONTOUR, &value);
}

int camera_get_contour(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGCONTOUR, &value);	

	return value;
}

/* value is on/offf */
void camera_set_backlight(camera_t *cam, int value)
{
	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSBACKLIGHT, &value);
}

int camera_get_backlight(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGBACKLIGHT, &value);	

	return value;
}

/* value is on/offf */
void camera_set_flicker(camera_t *cam, int value)
{
	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSFLICKER, &value);
}

int camera_get_flicker(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGFLICKER, &value);	

	return value;
}

void camera_set_noisereduction(camera_t *cam, int value)
{	
	assert(value>=0 && value<=3);

	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSDYNNOISE, &value);
}

int camera_get_noisereduction(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGDYNNOISE, &value);	

	return value;
}

void camera_set_user(camera_t *cam)
{
  if (camera_checkopen(cam))
    return;
  
  ioctl(cam->fd, VIDIOCPWCSUSER, NULL);
}

void camera_get_user(camera_t *cam)
{
  if (camera_checkopen(cam))
    return;
  
  ioctl(cam->fd, VIDIOCPWCRUSER, NULL);
}

void camera_get_factory(camera_t *cam)
{
  if (camera_checkopen(cam))
    return;
  
  ioctl(cam->fd, VIDIOCPWCFACTORY, NULL);
}

/* set to negative for auto.*/
void camera_set_gain(camera_t *cam, int value)
{	
	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSAGC, &value);
}

int camera_get_gain(camera_t *cam)
{
	int value;

	if (camera_checkopen(cam))
		return 0;

	ioctl(cam->fd, VIDIOCPWCGAGC, &value);	

	return value;
}

/* set to negative for auto.*/
void camera_set_shutter(camera_t *cam, int value)
{	
	if (camera_checkopen(cam))
		return;

	ioctl(cam->fd, VIDIOCPWCSSHUTTER, &value);
}

void rgb_to_rgv(unsigned char *buf, int width, int height)
{
	int x, y;
	int r, g, b;
	int sum;

	for (y=0;y<height;y++)
	{
		for (x=0;x<width;x++)
		{
			b=buf[0];
			g=buf[1];
			r=buf[2];

			sum=r+g+b;

			if (sum==0)
			  sum=1;

			buf[2]=255*r/sum;
			buf[1]=255*g/sum;
			buf[0]=0;

			buf+=4;
		}
	}
}

void rgb_to_hsv(unsigned char *buf, int width, int height)
{
	int x, y;
	int r, g, b, h, s, v;
	int minrg, min, maxrg, max, delta;

	for (y=0;y<height;y++)
	{
		for (x=0;x<width;x++)
		{
			b=buf[0];
			g=buf[1];
			r=buf[2];

			minrg=r<g ? r : g;
			min=b<minrg ? b : minrg;

			maxrg=r>g ? r : g;
			max=b>maxrg ? b : maxrg;

			delta=max-min;
			if (delta==0)
				delta=1;
			v=max;

			if (max==0)
			{
				s=0;
				h=0;
			}
			else
			{
				s=255*delta/max;

				if (r==max)
				{
					h=0+43*(g-b)/delta;
					if (h<0)
						h+=256;
				}
				else if (g==max)
				{
					h=85+43*(b-r)/delta;
				}
				else 
				{
					h=170+43*(r-g)/delta;
				}
			}

			buf[0]=v;
			buf[1]=s;
			buf[2]=h;

			buf+=4;
		}
	}

}

// channel, offset
// 0 -> 2
// 1 -> 1
// 2 -> 0
// 3 -> 3
void channel_select(unsigned char *buf, int width, int height, int channel)
{
	int x, y;
	int v;

	if (channel<=2)
	    channel=2-channel;

	for (y=0;y<height;y++)
	{
		for (x=0;x<width;x++)
		{
			v=buf[channel];
			buf[0]=v;
			buf[1]=v;
			buf[2]=v;
			buf[3]=v;

			buf+=4;
		}
	}
}
#endif //MASLAB_CAMERA_HEADER
