/***************************************************************************************
 *
 * videodev.cc is part of SimpleSkyCam.
 *
 *****************************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <errno.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <jpeglib.h>

#include <list>
#include <string>
#include <setjmp.h>

#include "simpleskycam.h"
#include "gui.h"
#include "video.h"
#include "videoframe.h"
#include "videodev.h"
#include "convert.h"



VideoDev::VideoDev() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	conf_device = "";
	conf_devicename = "";
	conf_format = "";
	conf_parameter = "";
	conf_height = 800;
	conf_width = 600;
	running = 0;
	callback = NULL;
	pixelformat = 0x0;
	g_mutex_init (&mutex);
};


#define THREAD_WAITWARNING_TO 10000000
VideoDev::~VideoDev() {
	int to = 0;

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

	if (running) Stop();
	while (running > 0) {
		if (--to <= 0) {
			to = THREAD_WAITWARNING_TO;
			printf ("%s:%d %s still waiting for complete\n", __FILE__, __LINE__, __FUNCTION__);
		}
	}
}



/*
 * fill the list with all found controls.
 */
int VideoDev::GetCtrlList(std::list<std::string> *list) {
	std::list<VideoDevCtrl>::iterator iter;
	if (list == NULL) return VDEV_STATUS_ERROR;

	list->clear();
	LockMutex();

	for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++) {
		list->push_back ((*iter).name);
	}

	UnLockMutex();

	return VDEV_STATUS_OK;
};


/*
 * if the control is found, fill out all the fields and return with VDEV_STATUS_OK,
 * on error VDEV_STATUS_UNKNOWN. In case he control have not been found.
 */
int VideoDev::GetCtrlMinMaxValue(std::string name, int *min, int *max, int *value) {
	std::list<VideoDevCtrl>::iterator iter;
	LockMutex();
	for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++)
		if (iter->name.compare(name) == 0) {
			GetDevCtrl(iter->id, &(iter->value));
			if (value != NULL)	*value = iter->value;
			if (min != NULL)	*min = iter->min;
			if (max != NULL)	*max = iter->max;
			break;
		}
	UnLockMutex();
	if (iter == vidctrls.end()) return VDEV_STATUS_ERROR;
	return VDEV_STATUS_OK;
};


//
// set the value for an control, on error VDEV_STATUS_UNKNOWN
//
int VideoDev::SetCtrlValue(std::string name, int value) {
	std::list<VideoDevCtrl>::iterator iter;
	LockMutex();
	for (iter = vidctrls.begin(); iter != vidctrls.end(); iter++)
		if (iter->name.compare(name) == 0) {
			SetDevCtrl(iter->id, value);
			break;
		}
	UnLockMutex();
	if (iter == vidctrls.end()) return VDEV_STATUS_ERROR;
	return VDEV_STATUS_OK;
};


/*
 * before is it possible to Start the VideoGrabbing it is mandatory to set up some values
 */
void VideoDev::SetConfig(std::string dev, int w, int h, std::string format, std::string parameter, gboolean (*callback_func)(gpointer data)) {
	conf_device = dev;
	conf_format = format;
	conf_parameter = parameter;
	conf_width = w;
	conf_height = h;
	callback = callback_func;
};


/*
 * start the video, start capturing, start thread
 * after return of this function we can call the Ctrl thread
 * this function should onyl be called after everything is setup with SetConfig(..)
 */
#define CYCLETIME 0.050
void VideoDev::ThreadProcess() {
	struct timeval cycle_timestamp;
	int numframes = 0;
	int lastsec = 0;
	float cycle_time = 0.0;
	float cycle_wait = 0.0;
	int i;

	printf ("%s:%d %s Enter\n", __FILE__, __LINE__, __FUNCTION__);
	cycle_time = get_cycletime(&cycle_timestamp);	// just start counting

	running = 1;

	//
	// open and init device buffers device
	if (Open() != VDEV_STATUS_OK) {
		Close();
		running = 0;
	}

	//
	// start capturing
	if (CaptureStart() != VDEV_STATUS_OK) {
		Close();
		running = 0;
	}

	if (running) {
		ConvertStart(&cdata, pixelformat);
	}

	threaddata.running = 1;	// prevent reading all controlls every time.
							// will be set to 2 in callback function, after the first frame
							// is read and all controls a loaded.
	if (callback) gdk_threads_add_idle(callback, &threaddata);

	while (running) {
		i = Grab(&threaddata.vfr);
		Convert(&cdata, &threaddata.vf, threaddata.vfr.data, threaddata.vfr.size, threaddata.vfr.pixfmt, threaddata.vfr.w, threaddata.vfr.h);
		switch (i) {
		case VDEV_STATUS_OK:
			numframes++;
			if (callback) gdk_threads_add_idle(callback, &threaddata);
			break;

		case VDEV_STATUS_AGAIN:
			break;

		default:
			running = 0;
			break;
		}

		//
		// keep 25fps, write every second a message
		cycle_time = get_cycletime(&cycle_timestamp);
		cycle_wait = (CYCLETIME - cycle_time) + cycle_wait;
		if (lastsec != cycle_timestamp.tv_sec) {
			printf ("%s:%d %s   Loop: cycle_time:%f      Freq:%f Hz      Frames:%d                  \n", __FILE__, __LINE__, __FUNCTION__, cycle_time, (1.0/cycle_time), numframes);
			lastsec = cycle_timestamp.tv_sec;
			numframes = 0;
		}
		if (cycle_wait > 0.0 && cycle_wait < 1.0 ) usleep ((int)(cycle_wait * 1000000.0));
	}

	//
	// stop capturing
	if (callback) gdk_threads_add_idle(callback, NULL);
	ConvertStop(&cdata, pixelformat);
	CaptureStop();
	Close();

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

	return;
};


void VideoDev::Stop() {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	if (running == 1) running = 0;
	return;
};


void VideoDev::GetVideoInfo(int *w, int *h, std::string *format) {
	if (format != NULL) *format = conf_format;
	if (w != NULL) 		*w = conf_width;
	if (h != NULL) 		*h = conf_height;
}


list<VideoDevCtrl> VideoDev::GetCtrlsMinMaxValue() {
	return vidctrls;
};


int VideoDev::get_bytesperpixel (uint32_t pixfmt) {
	int bytesperpixel = 4;

	switch (pixfmt) {
	case (V4L2_PIX_FMT_SGRBG8):
		bytesperpixel = 1;
		break;
	case (V4L2_PIX_FMT_SGRBG16):
		bytesperpixel = 2;
		break;
	case (V4L2_PIX_FMT_BGR32):
	case (V4L2_PIX_FMT_RGB32):
		bytesperpixel = 4;
		break;
	case (V4L2_PIX_FMT_BGR24):
	case (V4L2_PIX_FMT_RGB24):
		bytesperpixel = 3;
		break;
	default:
		errorexit((char*)"%s:%d unsupported pixelformat %s\n", convert_from_pixelformat(pixfmt).c_str());
		break;
	}
	return bytesperpixel;
}


