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

#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
#else
  #include <unistd.h> /* close() */
#endif
#include <stdio.h>
#include <list>
#include <math.h>
#include "gui.h"
#include "config.h"
#include "video.h"
#include "output.h"
#include "detect.h"
#include "configuration.h"

extern GtkBuilder *_builder_;						// work around for threads
extern Output output;
extern Detect detect;

//
// defines what should happen on clicking a point on the input videodata
int video_enterdata = VID_ENTERDATA_NONE;			// see VID_ENTERDATA_xxxxx
position_2d video_enterdata_pos;
detect_movement detectedpos_data = {0};

GtkWidget *detect_da = NULL;
GdkPixbuf *detect_pixbuf = NULL;

GtkWidget *image_da = NULL;
GdkPixbuf *image_pixbuf = NULL;

extern GtkWidget *debug_da;
extern GdkPixbuf *debug_pixbuf;


std::string filename = "";
extern Configuration config;

//////////////////////////////////////////////////////////////////////////////////////////////////
//
// call back functions
//

gboolean cb_window_delete_event (GtkWidget *widget, GdkEvent  *event, gpointer   data) {
	config.SaveDefault();
	gtk_main_quit();
	return FALSE;
};


//
// setup default values
//
void cb_window_show (GtkWidget *widget, gpointer data) {
	GtkWidget *btnstart = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-rec"));
	GtkWidget *btnstop = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "btn-video-stop"));
	GtkWidget *w = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-w"));
	GtkWidget *h = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-h"));
	GtkWidget *rbdefdetect = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-pos-cbbright"));
	GtkWidget *rbdeffolow = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-type-indet2"));

	// changing the value, will cause the 'change' event to be triggered
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rbdefdetect), true);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rbdeffolow), true);


	gtk_widget_set_sensitive(btnstart, true);
	gtk_widget_set_sensitive(btnstop, false);

	gtk_entry_set_text(GTK_ENTRY(w),"300");
	gtk_entry_set_text(GTK_ENTRY(h),"300");

	detect.SetObjectSize(300, 300);
	cb_video_btnrefreshlist (NULL, NULL);

	//
	// load config and setup elements
	config.LoadDefault();

	//
	// show debug output window.
	if (config.show_debugwin) {
		printf ("%s:%d show debug window\n", __FILE__, __LINE__);
		GtkWidget *wnd = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "window-debug"));
		if (wnd) gtk_widget_show (wnd);
	}

	GtkWidget *cfg_rgbenc = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "menu-settings-bilinearrgb"));
	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(cfg_rgbenc), (config.debayer_mode != 0));

	//
	// update the input video controls every 2 seconds.
	g_timeout_add(2000, videoctrl_update, NULL);
};


void cb_menu_set_rgbenc (GtkCheckMenuItem *checkmenuitem,  gpointer user_data) {
	if (gtk_check_menu_item_get_active(checkmenuitem) == TRUE) config.debayer_mode = 1;
	else config.debayer_mode = 0;
}



//
// callback from filter thread. Data will point to the output VideoFrame.
// Access to this data must be Locked before use.
//
/* gboolean cb_thread_filter (gpointer data) {
	VideoFrame *vf = (VideoFrame *) data;
	int pix_h, pix_w;

	if (image_da == NULL)
		image_da = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "image-da"));

	if (vf == NULL) {
		return false;
	}

	output.LockImageMutex();

	if (image_pixbuf) {
		pix_h = gdk_pixbuf_get_height(image_pixbuf);
		pix_w = gdk_pixbuf_get_width(image_pixbuf);
	}
	else pix_h = 0;

	if (image_pixbuf == NULL || pix_h != vf->h || pix_w != vf->w) {
		if (image_pixbuf != NULL) g_object_unref (image_pixbuf);

		printf ("%s:%d %s New Pixbuf: %d x %d\n", __FILE__, __LINE__, __FUNCTION__, vf->w, vf->h);
		image_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, vf->w, vf->h);
		pix_w = vf->w;
		pix_h = vf->h;
	}
	vf->ToPixbuf(image_pixbuf);
	gdk_window_invalidate_rect(gtk_widget_get_window(image_da), NULL, true);

	output.UnLockImageMutex();
	return false;
};
*/


//
// callback from detect thread. Data will point to the temp VideoFrame.
// this is propabely the object detection
// Access to this data must be Locked before use and pointers must be looked to
gboolean cb_thread_detect (gpointer data) {
	GtkWidget *e_x = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-posx"));
	GtkWidget *e_y = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-posy"));
	DetectOutput *dout = (DetectOutput *) data;
	int pix_h, pix_w, x, y;

	if (detect_da == NULL)
		detect_da = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "temp-da"));

	if (dout == NULL) return false;
	if (dout->image == NULL) return false;

	detect.LockImageMutex();

	gtk_entry_set_text(GTK_ENTRY(e_x), std::to_string(dout->posx).c_str());
	gtk_entry_set_text(GTK_ENTRY(e_y), std::to_string(dout->posy).c_str());
	x = dout->posx;		// save value for later use (unlocked image data)
	y = dout->posy;

	if (detect_pixbuf) {
		pix_h = gdk_pixbuf_get_height(detect_pixbuf);
		pix_w = gdk_pixbuf_get_width(detect_pixbuf);
	}
	else pix_h = 0;

	if (detect_pixbuf == NULL || pix_h != dout->image->h || pix_w != dout->image->w) {
		if (detect_pixbuf != NULL) g_object_unref (detect_pixbuf);

		printf ("%s:%d %s New Pixbuf: %d x %d\n", __FILE__, __LINE__, __FUNCTION__, dout->image->w, dout->image->h);
		detect_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, false, 8, dout->image->w, dout->image->h);
		pix_w = dout->image->w;
		pix_h = dout->image->h;
	}
	dout->image->ToPixbuf(&detect_pixbuf);
	gdk_window_invalidate_rect(gtk_widget_get_window(detect_da), NULL, true);
	output.NewFrame(dout->rawimage);

	detect.UnLockImageMutex();

	//
	// detect movements, by creating a ring buffer and we comptare the current
	// position with the one DETECT_MOVEMENT_SAMPLES frames ago.
	//
	struct timeval tv = detectedpos_data.samples[0].ts;
	float ftime = get_cycletime(&tv);
	float fdist = sqrtf ( (x-detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].x) *
						  (x-detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].x)    +
						  (y-detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].y) *
						  (y-detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].y));
	if (fdist > 20.0) {
		for (int i = 0; i < DETECT_MOVEMENT_SAMPLES; i++) {
			detectedpos_data.samples[i].x = x;
			detectedpos_data.samples[i].y = y;
			detectedpos_data.samples[i].ts = tv;
		}
	}

	detectedpos_data.dx = (float)(x-detectedpos_data.samples[0].x) / ftime;
	detectedpos_data.dy = (float)(y-detectedpos_data.samples[0].y) / ftime;

	for (int i = 1; i < DETECT_MOVEMENT_SAMPLES; i++)
		detectedpos_data.samples[i-1] = detectedpos_data.samples[i];

	detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].ts = tv;
	detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].x = x;
	detectedpos_data.samples[DETECT_MOVEMENT_SAMPLES-1].y = y;

	return false;
};



void cb_imagetempda_draw(GtkWidget *area, cairo_t *cr, int w, int h, gpointer data) {
	int clientw, clienth, pixbufw, pixbufh;
	float clientar, pixbufar;

	GdkPixbuf *pixbuf = NULL;
	GdkPixbuf *src = NULL;

	if (area == image_da)  		src = image_pixbuf;
	else if (area == detect_da)	src = detect_pixbuf;
	else if (area == debug_da && debug_pixbuf != NULL)	src = debug_pixbuf;
	else return;

	clienth = gtk_widget_get_allocated_height(area);
	clientw = gtk_widget_get_allocated_width(area);
	clientar = (float)clientw/(float)clienth;
	pixbufh = gdk_pixbuf_get_height(src);
	pixbufw = gdk_pixbuf_get_width(src);
	pixbufar = (float)pixbufw/(float)pixbufh;

	if (pixbufar < clientar) {
		clientw = (pixbufar * (float) clienth);
	}
	else {
		clienth = ((float) clientw / pixbufar);
	}

	pixbuf = gdk_pixbuf_scale_simple (src, clientw, clienth, GDK_INTERP_NEAREST);

    gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
    cairo_paint(cr);
    cairo_fill (cr);

    g_object_unref (pixbuf);
};


void cb_detect_btnsetsize (GtkWidget *widget, gpointer data) {
	GtkWidget *txtw = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-w"));
	GtkWidget *txth = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-entry-h"));

	detect.SetObjectSize(	atoi(gtk_entry_get_text(GTK_ENTRY(txtw))),
							atoi(gtk_entry_get_text(GTK_ENTRY(txth))));
};



void cb_detect_bright (GtkRange *range, gpointer data) {
	double value;

	value = gtk_range_get_value(range);
	detect.SetMinBrightness(value);
};


void cb_detect_followtype (GtkWidget *widget, gpointer data) {
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)) == 1) {
		if (widget == GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-type-indet0")))
			detect.SetFollowType(AUTOFOLLOW_OFF);
		if (widget == GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-type-indet1")))
			detect.SetFollowType(AUTOFOLLOW_BRIGHT);
		if (widget == GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-type-indet2")))
			detect.SetFollowType(AUTOFOLLOW_CROSSC);
	}
};


void cb_detect_detecttype (GtkWidget *widget, gpointer data) {
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)) == 1) {
		if (widget == GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-pos-cboff")))
			detect.SetDetectType(AUTODETECT_OFF);
		if (widget == GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-pos-cbbright")))
			detect.SetDetectType(AUTODETECT_BRIGHT);
	}
};



void cb_detect_btnsetpos (GtkWidget *widget, gpointer data) {
	GtkWidget *rboff = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "detect-pos-cboff"));

	// changing the value, will cause the 'change' event to be triggered
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(rboff), true);

	video_enterdata = VID_ENTERDATA_POS;
};


void cb_detect_show_window (GtkWidget *widget, gpointer data) {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	GtkWidget *wnd = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "window-detect"));
	gtk_widget_show(wnd);
};


/*
 * setup destination path
 */
void cb_input_btnsetdestpath (GtkWidget *widget, gpointer data) {
	GtkBuilder *builder = (GtkBuilder *) data;
	GtkWindow *window = GTK_WINDOW (gtk_builder_get_object (builder, "windows-input"));
	GtkWidget *dialog;
	GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
	gint res;

	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);
	dialog = gtk_file_chooser_dialog_new ("Open Folder",
										  window,
	                                      action,
	                                      "_Cancel",
	                                      GTK_RESPONSE_CANCEL,
	                                      "_Open",
	                                      GTK_RESPONSE_ACCEPT,
	                                      NULL);

	res = gtk_dialog_run (GTK_DIALOG (dialog));
	if (res == GTK_RESPONSE_ACCEPT) {
	    char *filename;
	    GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
	    filename = gtk_file_chooser_get_filename (chooser);
	    config.SetDestPath(filename);
		GtkWidget *entry = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "input_entry_destpath"));
		gtk_entry_set_text(GTK_ENTRY(entry), filename);

	    g_free (filename);
	}
	gtk_widget_destroy (dialog);
};

void cb_input_show_window (GtkWidget *widget, gpointer data) {
	printf ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__);

	GtkWidget *wnd = GTK_WIDGET(gtk_builder_get_object (GTK_BUILDER(_builder_), "window-input"));
	gtk_widget_show(wnd);
};


void draw_text (cairo_t *cr, int x, int y, float border, std::string text) {
	cairo_pattern_t *t = NULL;
	double r, g, b, a;

	t = cairo_get_source(cr);
	cairo_pattern_get_rgba(t, &r, &g, &b, &a);

	for (int i = 0; i < 5; i++) {
		int dx, dy;

		switch (i) {
			case 0:
				cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
				dx = -1;
				dy = 0;
				break;
			case 1:
				dx = +1;
				dy = 0;
				break;
			case 2:
				dx = 0;
				dy = -1;
				break;
			case 3:
				dx = 0;
				dy = +1;
				break;
			case 4:
				cairo_set_source_rgba(cr, r, g, b, a);
				dx = 0;
				dy = 0;
				break;
		}
		cairo_move_to (cr, x+dx, y+dy);
		cairo_show_text(cr, text.c_str());
		cairo_stroke(cr);
	}
};

#define DRAW_PRINTF_LEN 512
void draw_printf(cairo_t *cr, int x, int y, float border, char *fmt,...) {
	va_list args;
	char buffer[DRAW_PRINTF_LEN];

	va_start (args, fmt);
	vsnprintf (buffer, (DRAW_PRINTF_LEN-1), fmt, args);
	va_end (args);
	buffer[DRAW_PRINTF_LEN-1] = 0;
	draw_text (cr, x, y, border, buffer);
};


