/************************************************************************************
 * videodev-dumpfile:
 * raw videodump input file support. It will scan the directory givin by the command
 * line argument -rd DIRECTORY and for each it will create an own device. This file
 * will be read in a loop.
 *
 * This is needed only for debugging.
 ************************************************************************************/

/* enable files > 2GB on 32 bit systems */
#define _LARGEFILE64_SOURCE 1
#define _FILE_OFFSET_BITS 64

#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>

#ifdef BUILD_WINDOWS
#include <winsock2.h>
#include <io.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#endif
#include <sys/stat.h>

#include "configuration.h"
#include "videodev-dumpfile.h"

VideoDev_Dumpfile::VideoDev_Dumpfile() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	filesize = 0;
	filepos = 0;
	fd = -1;
	w = 0;
	h = 0;
	pixformat = 0;
	inframe = NULL;
	inframe_size = 0;
	inframe_maxsize = 0;
	inframe_nexttime = 0;
	fixedframesize = 0;
};


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



/*
 * searchs in the given path for any videodump files and present them as
 * video device.
 */
int VideoDev_Dumpfile::GetDeviceList(std::list<std::string> *list) {
	std::string device;
	DIR *dir;
	struct dirent *de = NULL;
	char fname[256];
	int i;

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	if (list == NULL || config.readdumppath == NULL) return 0;

	if ((dir = opendir (config.readdumppath)) == NULL) {
		printf ("%s:%d could not read directory '%s'\n", __FILE__, __LINE__, config.readdumppath);
		return 0;
	}

	while ((de = readdir (dir))) {
#ifndef BUILD_WINDOWS
		if (de->d_type & DT_REG) {
#endif
			for (i = 0; i < 255 && de->d_name[i] != 0; i++) fname[i] = toupper(de->d_name[i]);
			fname[i] = 0;
			if (strstr (fname, ".VIDEODUMP") != NULL) {
				device = (std::string) "VIDEODUMP " + (std::string) de->d_name;
				list->push_back(device);
			}
#ifndef BUILD_WINDOWS
		}
#endif
	}
	closedir(dir);

	return 1;
}



/*
 * Open Device
 * prepare the buffer, InitMMAP and read all controls
 */
int VideoDev_Dumpfile::Open() {
	if (config.readdumppath == NULL) return VDEV_STATUS_ERROR;

	VideoDevCtrl vctl;
	uint32_t inbuf[3];
	int i;
	struct stat s;

	std::string fname = config.readdumppath;
	fname = fname + "/" + conf_device;

	printf ("%s:%d %s file %s\n", __FILE__, __LINE__, __FUNCTION__, fname.c_str());

	if (fd >= 0) close (fd);
	fd = -1;


	//
	// read filesize
	if (stat (fname.c_str(), &s) != 0) {
		printf ("%s:%d %s could not read stat of file '%s'. Error:%s\n", __FILE__, __LINE__, __FUNCTION__,
				fname.c_str(), strerror(errno));
		Close();
		return VDEV_STATUS_ERROR;
	}
	filesize = s.st_size;

#ifdef BUILD_WINDOWS
	if ((fd = open(fname.c_str(), O_RDONLY | O_BINARY)) == -1) {
#else
	if ((fd = open(fname.c_str(), O_RDONLY)) == -1) {
#endif
		printf ("%s:%d could not open file '%s' error:%s\n", __FILE__, __LINE__, fname.c_str(), strerror(errno));
		return VDEV_STATUS_ERROR;
	}

	//
	// read header
	if (read (fd, inbuf, 12) != 12) {
		printf ("%s:%d could not read all header data.\n", __FILE__, __LINE__);
		close (fd);
		fd = -1;
		return VDEV_STATUS_ERROR;
	}
	i = 0;
	filepos = 12;
	conf_width = w = ntohl(inbuf[i++]);
	conf_height = h = ntohl(inbuf[i++]);
	pixformat = ntohl(inbuf[i++]);
	conf_format = convert_from_pixelformat (pixformat);

	vidctrls.clear();
	vctl.name = "FilePosition";
	vctl.id = 1;
	vctl.min = 0;
	vctl.max = 255;
	vctl.value = 0;
	vidctrls.push_back(vctl);

	return VDEV_STATUS_OK;
};


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

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

	return VDEV_STATUS_OK;
};



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


/*
 * send the start capture signal to the cam
 */
int VideoDev_Dumpfile::CaptureStart() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	Close();
	if (Open() != VDEV_STATUS_OK) return VDEV_STATUS_ERROR;

	// read timestamp and first header and frame
	gettimeofday(&starttv, NULL);
	pixelformat = pixformat;
	ReadFrame();

	return VDEV_STATUS_OK;
};


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

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

	if (inframe != NULL) {
		free (inframe);
		inframe_size = 0;
		inframe = NULL;
	}
	return VDEV_STATUS_OK;
};


/*
 * try to grab one frame and convert it into RGB32.
 * 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.
 */
#define SIZE_FRAMEHEADER 8
#define SIZE_DUMPHEADER  12
int VideoDev_Dumpfile::Grab(VideoFrameRaw *vf) {
	struct timeval curtv;
	unsigned int diff = 0;
	std::list<VideoDevCtrl>::iterator ctrl;

	if (fd == -1) return VDEV_STATUS_ERROR;

	//
	// is it time?
	do {
		gettimeofday(&curtv, NULL);
		diff = 1000 * (curtv.tv_sec  - starttv.tv_sec) +
			    	  (curtv.tv_usec - starttv.tv_usec) / 1000;

		if (diff < inframe_nexttime)
			usleep ((inframe_nexttime-diff)*1000);
	} while (diff < inframe_nexttime);

	// if (diff - inframe_nexttime > 1000)
	//	printf ("%s:%d time difference to big. (%dms) Maybe to slow hard drive?\n",
	//			__FILE__, __LINE__, (diff - inframe_nexttime));

	LockMutex();
	vf->CopyFrom(pixformat, w, h, inframe_size, inframe);
	UnLockMutex();

	ctrl = vidctrls.begin();
	if (ctrl->value == 0) {
		// fixed framesize -> calculate frames
		switch (pixformat) {
		case(V4L2_PIX_FMT_RGB24):
		case(V4L2_PIX_FMT_BGR24):
			fixedframesize = 3 * w * h;
			break;
		case(V4L2_PIX_FMT_RGB32):
		case(V4L2_PIX_FMT_BGR32):
			fixedframesize = 4 * w * h;
			break;
		case(V4L2_PIX_FMT_SGRBG8):
			fixedframesize = 1 * w * h;
			break;
		case(V4L2_PIX_FMT_SGRBG16):
			fixedframesize = 2 * w * h;
			break;
		default:
			ctrl->max = 0;
			ctrl->value = -1;
			fixedframesize = 0;
			break;
		}
		if (fixedframesize > 0)
			ctrl->max = (filesize-SIZE_DUMPHEADER) / (fixedframesize + SIZE_FRAMEHEADER);
	}

	if (ctrl->value != -1) ctrl->value++;

	//
	// read next frame
	LockMutex();
	ReadFrame();
	UnLockMutex();

	return VDEV_STATUS_OK;
}


/*****************************************************************************************************
 * Read Frame
 */
int VideoDev_Dumpfile::ReadFrame() {
	uint32_t inbuf[2];

	if (fd < 0)	return VDEV_STATUS_ERROR;

	//
	// check position, if end of file restart from beginning
	if (filepos == filesize) {
		printf ("%s:%d end of file start with first frame\n", __FILE__, __LINE__);
		std::list<VideoDevCtrl>::iterator ctrl;
		ctrl = vidctrls.begin();
		ctrl->value = 0;
		if (lseek(fd, 12, SEEK_SET) != 12) {
			printf ("%s:%d %s lseek returned: %s\n", __FILE__, __LINE__, __FUNCTION__, strerror(errno));
			Close();
		}
		else {	// reset filepos and starttime
			filepos = 12;
			gettimeofday(&starttv, NULL);
		}
	}

	//
	// read frame
	if (read (fd, inbuf, 4*2) != 4*2) {
        printf ("%s:%d could not read frame header: %s\n", __FILE__, __LINE__, strerror(errno));
		Close();
	}
	filepos += (4*2);
	inframe_size = ntohl(inbuf[0]);
	inframe_nexttime = ntohl(inbuf[1]);

	// read header
	if (inframe == NULL) {
		inframe = (unsigned char*) malloc (inframe_size);
		inframe_maxsize = inframe_size;
	}
	else if (inframe_maxsize < inframe_size) {
		inframe = (unsigned char*) realloc (inframe, inframe_size);
		inframe_maxsize = inframe_size;
	}

	// allocate memory and read frame
	if (inframe == NULL) {
		Close();
		printf ("%s:%d could not allocate enough memory\n", __FILE__, __LINE__);
		return VDEV_CBSTATUS_ERROR;
	}
	if (read (fd, inframe, inframe_size) != inframe_size) {
        printf ("%s:%d could not read frame: %s\n", __FILE__, __LINE__, strerror(errno));
        Close();
	}
	filepos += inframe_size;

	return VDEV_STATUS_OK;
}



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

/*
 * set video control identified by id
 */
int VideoDev_Dumpfile::SetDevCtrl(unsigned int id, int value) {
	std::list<VideoDevCtrl>::iterator ctrl;
	int framerest;
	off_t newfilepos = 0;
	printf ("%s:%d VideoDev_Dumpfile::SetDevCtrl Set Offset to %d   id:%d  fixedframesize:%d\n", __FILE__, __LINE__, value, id, fixedframesize);
	struct timeval curtv;

	if (id == 1) {
		ctrl = vidctrls.begin();
		if (value != ctrl->value && value < ctrl->max) {
			filepos = SIZE_DUMPHEADER + ((off_t)value * (SIZE_FRAMEHEADER + (off_t)fixedframesize));
			printf ("%s:%d filepos:%ld\n", __FILE__, __LINE__, filepos);

			if ((newfilepos = lseek (fd, filepos, SEEK_SET)) < 0) {
				printf ("%s:%d ******* lseek error:%s\n", __FILE__, __LINE__, strerror(errno));
			}
			else {
				framerest = (newfilepos - SIZE_DUMPHEADER)%(SIZE_FRAMEHEADER + fixedframesize);
				ctrl->value = (newfilepos - SIZE_DUMPHEADER)/(SIZE_FRAMEHEADER + fixedframesize);
				if (ctrl->value != value || framerest != 0) {
					printf ("%s:%d could not set file to correct position. ctrl->value:%d value:%d  Framerest:%d\n", __FILE__, __LINE__, ctrl->value, value, framerest);
				}
				else {
					//
					// read first frame
					ReadFrame();
					gettimeofday(&curtv, NULL);
					starttv.tv_sec = curtv.tv_sec - (inframe_nexttime/1000);
				}
			}
		}

		// else printf ("%s:%d could not set video position (ctrl->value:%d value:%d max:%d)\n",
		//		__FILE__, __LINE__, ctrl->value, value, ctrl->max);
	}

	return VDEV_STATUS_OK;
};


/*
 * get video control identified by id
 */
int VideoDev_Dumpfile::GetDevCtrl(unsigned int id, int *value) {
	return VDEV_STATUS_OK;
};


