
#include "convert.h"

#ifdef USE_V4L2

#include "videodev-v4l2.h"

VideoDev_V4L2::VideoDev_V4L2() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	io = IOMODE_MMAP;
	fd = -1;
	inbuffer_idx = 0;
};

VideoDev_V4L2::~VideoDev_V4L2() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	if (running > 0) 	CaptureStop();
	if (fd >= 0)		Close ();
}



/*
 * try to send ioctl command as long as EINTR is valid. But abort after 2 seconds.
 */
int VideoDev_V4L2::xioctl(int fd, int request, void *arg) {
	int r;
	int errnoioctl;
	struct timeval to1;
	struct timeval to2;
	float to;

	gettimeofday(&to1, NULL);
	do {
		r = ioctl(fd, request, arg);
		errnoioctl = errno;
		gettimeofday(&to2, NULL);
		to = (float)(to2.tv_sec - to1.tv_sec) + ((to2.tv_usec - to1.tv_usec) / 1000000.0);
	} while (r == -1 && errnoioctl == EINTR && to < 2.0);

	return r;
};



/*
 * return a list of /dev/video* devices found on the system, and read out its human friendly name
 * output will be a lit of: "V4L2 /dev/videoX [Name]"
 */
int VideoDev_V4L2::GetDeviceList(std::list<std::string> *list) {
	std::string device;
	int devnum;

	if (list == NULL) return 0;

	for (devnum = 0; devnum < 255; devnum++) {
		device = "/dev/video"+std::to_string(devnum);

		if (device.compare (conf_device) != 0) {
			int fd;
			struct v4l2_capability vcap;

			if((fd = open(device.c_str(), O_RDONLY)) == -1){
				continue;
			}

			if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1)
				strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card));
			close(fd);
			device += " [" + (std::string) ((char*)vcap.card) + "]";
		}
		else {
			device += " [" + (std::string) conf_devicename + "]";
		}

		list->push_back("V4L2 " + device);
	}

	return 1;
}


/*
 * print out the important capabilities, which are reported by the V4L2 device
 */
void VideoDev_V4L2::PrintCaps(uint32_t caps) {
	printf ("%s:%d %s Caps: %x\n", __FILE__, __LINE__, __FUNCTION__, caps);

	if (caps & V4L2_CAP_VIDEO_CAPTURE) printf ("    V4L2_CAP_VIDEO_CAPTURE\n");
	if (caps & V4L2_CAP_EXT_PIX_FORMAT) printf ("    V4L2_CAP_EXT_PIX_FORMAT\n");
#ifdef V4L2_CAP_META_CAPTURE
	if (caps & V4L2_CAP_META_CAPTURE) printf ("    V4L2_CAP_META_CAPTURE\n");
#endif
	if (caps & V4L2_CAP_STREAMING) printf ("    V4L2_CAP_STREAMING\n");
	if (caps & V4L2_CAP_DEVICE_CAPS) printf ("    V4L2_CAP_DEVICE_CAPS\n");
	if (caps & V4L2_CAP_TUNER) printf ("    V4L2_CAP_TUNER\n");
	if (caps & V4L2_CAP_MODULATOR) printf ("    V4L2_CAP_MODULATOR\n");
	if (caps & V4L2_CAP_READWRITE) printf ("    V4L2_CAP_READWRITE\n");
	if (caps & V4L2_CAP_ASYNCIO) printf ("    V4L2_CAP_ASYNCIO\n");
}


/*
 * convert the V4L2 Format data into a text humans can read.
 * the pixelformat is coded in 4 chars, maybe we need a general coding table
 * convert still need the v4l2 pixelformats.
 * as soon as we support GPhoto or something else we need our own pixelformat table.
 */
void VideoDev_V4L2::PrintFmt(struct v4l2_format *f) {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	printf ("  type : %u\n", f->type);
	printf ("  fmt.pix.width  : %d\n", f->fmt.pix.width);
	printf ("  fmt.pix.height  : %d\n", f->fmt.pix.height);
	printf ("  fmt.fmt.pix.pixelformat : %c%c%c%c\n", ((char*)&f->fmt.pix.pixelformat)[0],
			((char*)&f->fmt.pix.pixelformat)[1],
			((char*)&f->fmt.pix.pixelformat)[2],
			((char*)&f->fmt.pix.pixelformat)[3]);
	printf ("  fmt.pix.field : %d\n", f->fmt.pix.field);
	printf ("  fmt.pix.bytesperline : %d\n", f->fmt.pix.bytesperline);
	printf ("  fmt.pix.sizeimage : %d\n", f->fmt.pix.sizeimage);
}



/*
 * Open Device
 * prepare the buffer, InitMMAP and read all controls
 */
int VideoDev_V4L2::Open() {
	int i;
	struct v4l2_capability vcap;
	VideoDevCtrl vctl;
	char txt[32];

	printf ("%s:%d %s Device: '%s'\n", __FILE__, __LINE__, __FUNCTION__, conf_device.c_str());
	if (fd != -1) return VDEV_STATUS_ERROR;

	//
	// open device and get device name and capabilities      | O_NONBLOCK
	if((fd = open(conf_device.c_str(), O_RDWR)) == -1){
		return VDEV_STATUS_ERROR;
	}

	if(ioctl(fd, VIDIOC_QUERYCAP, &vcap) == -1)
		strncpy ((char*)&vcap.card, "unknown", sizeof(vcap.card));
	conf_devicename = (char*) vcap.card;

	printf ("%s:%d %s Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.capabilities);
	PrintCaps(vcap.capabilities);
	printf ("%s:%d %s Device Capabilities: %u\n", __FILE__, __LINE__, __FUNCTION__, vcap.device_caps);
	PrintCaps(vcap.device_caps);

	if (!(vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		printf ("%s:%d %s device has no video capture capabilities\n", __FILE__, __LINE__, __FUNCTION__);
		return VDEV_STATUS_ERROR;
		Close();
	}

	//
	// query controls
	struct v4l2_queryctrl queryctrl;
	uint32_t min;

	vidctrls.clear();
	memset (&queryctrl, 0, sizeof (queryctrl));
	for (i = V4L2_CID_BASE; i < V4L2_CID_DETECT_CLASS_BASE+0x1000; i++) {
		queryctrl.id  = i;
        if (0 == ioctl (fd, VIDIOC_QUERYCTRL, &queryctrl)) {
			vctl.name = (char*)queryctrl.name;
			vctl.id = queryctrl.id;
			vctl.min = queryctrl.minimum;
			vctl.max = queryctrl.maximum;
			GetDevCtrl(queryctrl.id, &vctl.value);
			vidctrls.push_back(vctl);
        }
	}

	//
	// check for cropping.. if we have it setup default
	CLEAR (cropcap);
	cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
		crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		crop.c = cropcap.defrect; // reset to default

		if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
			printf ("%s:%d %s VIDEOC_S_CROP Errorcode: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno));
		}
	}

	//
	// prepare resolution and pixelformat
	CLEAR (fmt);
	fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (conf_height != -1 && conf_width != -1) {	// resolution
		fmt.fmt.pix.width       = conf_width;
		fmt.fmt.pix.height      = conf_height;
	}
	else {
		fmt.fmt.pix.width       = 1920;
		fmt.fmt.pix.height      = 1080;
	}

	// fixme: hardcoded video format?????
	fmt.fmt.pix.pixelformat = convert_to_pixelformat(conf_format);
	fmt.fmt.pix.field       = V4L2_FIELD_NONE;
	if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt)) {
		fprintf (stderr, "%s:%d VIDIOC_S_FMT : %s\n", __FILE__, __LINE__, strerror (errno));
		close (fd);
		fd = -1;
		return VDEV_STATUS_ERROR;
	}

	// Note VIDIOC_S_FMT may change width and height.
	// Buggy driver paranoia.     - as written in the v4l2 api documentation
	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;
	conf_width = fmt.fmt.pix.width;
	conf_height = fmt.fmt.pix.height;
	snprintf (txt, 32, "%c%c%c%c", ((char*)&fmt.fmt.pix.pixelformat)[0],
			((char*)&fmt.fmt.pix.pixelformat)[1],
			((char*)&fmt.fmt.pix.pixelformat)[2],
			((char*)&fmt.fmt.pix.pixelformat)[3]);
	conf_format = txt;
	PrintFmt (&fmt);

	// init buffers
	switch (io) {
	case IOMODE_MMAP:
		if (InitMMap() == VDEV_STATUS_ERROR)
			Close();
		break;
	case IOMODE_READ:
	default:
		break;
	}

	return VDEV_STATUS_OK;
};


/*
 * prepare memory mapped buffers
 */
int VideoDev_V4L2::InitMMap() {
    struct v4l2_requestbuffers bufreq;
    struct v4l2_buffer bufinfo;
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	int i;

    CLEAR(bufreq);
    CLEAR(bufinfo);

    bufreq.count = VDEV_INBUFFERS;
    bufreq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    bufreq.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &bufreq)) {
		if (EINVAL == errno) {
			printf("%s does not support memory mapping", conf_device.c_str());
			return VDEV_STATUS_ERROR;
		} else {
			printf ("%s:%d %s Error %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno));
			return VDEV_STATUS_ERROR;
		}
    }

    if (bufreq.count < 1) {
        printf ( "Insufficient buffer memory on %s\n", conf_device.c_str());
            return VDEV_STATUS_ERROR;
    }

    for (i = 0; i < VDEV_INBUFFERS; i++) {
		CLEAR(bufinfo);
		bufinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		bufinfo.memory = V4L2_MEMORY_MMAP;
		bufinfo.index = i;

		if(ioctl(fd, VIDIOC_QUERYBUF, &bufinfo) < 0){
			perror("VIDIOC_QUERYBUF");
			exit(1);
		}

		inbuffer[i].size = bufinfo.length;
		inbuffer[i].data = (unsigned char*)mmap(NULL, bufinfo.length, PROT_READ | PROT_WRITE,
				MAP_SHARED, fd, bufinfo.m.offset);
		if (inbuffer[i].data == MAP_FAILED) {
			printf ( "%s:%d error on mmap %s\n", __FILE__, __LINE__, strerror(errno));
			exit (1);
		}
    }
    return VDEV_STATUS_OK;
}


/*
 * Close Device
 * Free videobuffer
 */
int VideoDev_V4L2::Close() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	if (fd >= 0) {
		UnInit();
		close (fd);
		fd = -1;
	}

	conf_device = "";
	conf_devicename = "";

	return VDEV_STATUS_OK;
};



int VideoDev_V4L2::UnInit() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

    switch (io) {
    case IOMODE_READ:
    	break;

    case IOMODE_MMAP:
    	for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++)
    		if (inbuffer[inbuffer_idx].data) {
				if (-1 == munmap(inbuffer[inbuffer_idx].data, inbuffer[inbuffer_idx].size)){
					fprintf(stderr, "Fatal Error @ %s:%d munmap Error:%s\n", __FILE__, __LINE__, strerror(errno));
					exit(1);
				}
				inbuffer[inbuffer_idx].data = NULL;
				inbuffer[inbuffer_idx].size = 0;
    		}
    	break;

	default:
		printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__);
		return VDEV_STATUS_ERROR;
	}


 	return VDEV_STATUS_OK;
};

/*****************************************************************************************************
 * VideoGrabbing
 */


/*
 * send the start capture signal to the cam
 */
int VideoDev_V4L2::CaptureStart() {
	enum v4l2_buf_type type;

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

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

	case IOMODE_MMAP:
		struct v4l2_buffer buf;

		for (inbuffer_idx = 0; inbuffer_idx < VDEV_INBUFFERS; inbuffer_idx++) {
			CLEAR(buf);
			buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			buf.memory = V4L2_MEMORY_MMAP;
			buf.index = inbuffer_idx;

			if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) {
			   printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno));
			   return VDEV_STATUS_ERROR;
			}
		}

		inbuffer_idx = 0;
		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) {
			printf ( "%s:%d VIDIOC_STREAMON %s\n", __FILE__, __LINE__, strerror(errno));
			return VDEV_STATUS_ERROR;
		}
		break;

	default:
		printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__);
		return VDEV_STATUS_ERROR;
	}

	pixelformat = fmt.fmt.pix.pixelformat;

	return VDEV_STATUS_OK;
};


int VideoDev_V4L2::CaptureStop() {
    enum v4l2_buf_type type;
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

    switch (io) {
    case IOMODE_READ:
            /* Nothing to do. */
            break;
    case IOMODE_MMAP:
            type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) {
    	    	fprintf(stderr, "%s:%d VIDIOC_STREAMOFF Error:%s\n", __FILE__, __LINE__, strerror(errno));
    			return VDEV_STATUS_ERROR;
            }
            break;
	default:
		printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__);
		return VDEV_STATUS_ERROR;
	}
	return VDEV_STATUS_OK;
};


/*
 * If something goes wrong return an error code.
 * Return code VDEV_STATUS_AGAIN is not an error. There was no video image ready to read.
 * To reduce the time of malloc/free reuse the destination buffer.
 */
int VideoDev_V4L2::Grab(VideoFrameRaw *vf) {
	struct v4l2_buffer buf;
	int len;

	if (vf == NULL) return VDEV_STATUS_ERROR;

	switch (io) {
	case IOMODE_READ:
		if ((len = read (fd, inbuffer[0].data, fmt.fmt.pix.sizeimage)) == -1) {
			switch (errno) {
				case EAGAIN:
					return VDEV_STATUS_AGAIN;
				case EIO:
				default:
					printf ("v4l2_grab IOM_READ: %s  dest:%p size:%d\n", strerror (errno), vf, fmt.fmt.pix.sizeimage);
					return VDEV_STATUS_ERROR;
			}
		}
		else {
			LockMutex();

			UnLockMutex();
		}
		break;

	case IOMODE_MMAP:
		CLEAR(buf);

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

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

				case EIO:
					printf ( "%s:%d error on VIDIOC_DQBUF EIO %s\n", __FILE__, __LINE__, strerror(errno));
					return VDEV_STATUS_ERROR;

				default:
		            printf ( "%s:%d error on VIDIOC_DQBUF %s\n", __FILE__, __LINE__, strerror(errno));
		            return VDEV_STATUS_ERROR;
			}
		}

		if (buf.index >= 0 && buf.index < VDEV_INBUFFERS) {
			LockMutex();
			vf->CopyFrom(fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height, buf.bytesused, inbuffer[buf.index].data);
			UnLockMutex();
		}

		if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) {
           printf ( "%s:%d error on VIDIOC_QBUF %s\n", __FILE__, __LINE__, strerror(errno));
           return VDEV_STATUS_ERROR;		}

		if (++inbuffer_idx >= VDEV_INBUFFERS) inbuffer_idx = 0;
		break;

	default:
		printf ("%s:%d %s wrong IOMODE?\n", __FILE__, __LINE__, __FUNCTION__);
		return VDEV_STATUS_ERROR;
	}

	return VDEV_STATUS_OK;
}


int VideoDev_V4L2::GetDeviceFormats(string device, std::list<string> *formats) {
	int i, fd_;
	struct v4l2_format format = {0};
	std::string result = "";

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	LockMutex();

	if (fd == -1){
		//
		// open device and get device name and capabilities      | O_NONBLOCK
		if((fd_ = open(device.c_str(), O_RDWR)) == -1) {
			printf ("%s could not open device %s: %s\n", __FUNCTION__, device.c_str(), strerror(errno));
			return VDEV_STATUS_ERROR;
		}
	}
	else fd_ = fd;

	for(i = 0; convert_pixelformats[i] != 0; i++){
	    format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    format.fmt.pix.width = -1;
	    format.fmt.pix.height = -1;
	    format.fmt.pix.pixelformat = convert_pixelformats[i];
	    if(xioctl(fd_, VIDIOC_S_FMT, &format) != -1) {
	    	if (format.fmt.pix.pixelformat == convert_pixelformats[i]) {
	    		formats->push_back(convert_from_pixelformat(format.fmt.pix.pixelformat));
	    	}
	    }
    	else {
    		printf ("error: %s\n", strerror(errno));
    	}
	}

	if (fd == -1) {
		close (fd_);
	}

	UnLockMutex();
	return VDEV_STATUS_OK;
}




/*****************************************************************************************************
 * Controls
 */

/*
 * set video control identified by id
 */
int VideoDev_V4L2::SetDevCtrl(unsigned int id, int value) {
	struct v4l2_control ctrl;

	CLEAR(ctrl);
	ctrl.id = id;
	ctrl.value = value;
	if (-1 == xioctl (fd, VIDIOC_S_CTRL, &ctrl)) {
		return VDEV_STATUS_ERROR;
	}

	return VDEV_STATUS_OK;
};


/*
 * get video control identified by id
 */
int VideoDev_V4L2::GetDevCtrl(unsigned int id, int *value) {
	struct v4l2_control ctrl;

	CLEAR(ctrl);
	ctrl.id = id;
	if (-1 == xioctl (fd, VIDIOC_G_CTRL, &ctrl)) {
		return VDEV_STATUS_ERROR;
	}
	*value = ctrl.value;

	return VDEV_STATUS_OK;
};


#endif
