// Include atomic std library
#include <atomic>

// Include gstreamer library
#include <gst/gst.h>
#include <gst/app/app.h>
#include <gst/app/gstappsink.h>


#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

using namespace std;
using namespace cv;


// Include OpenCV library
//#include <opencv.hpp>
#include "opencv2/opencv.hpp"
// Share frame between main loop and gstreamer callback
std::atomic<cv::Mat*> atomicFrame;


//#define FACE_DETECTION
//#define MULTI_THREAD

#ifdef MULTI_THREAD
#define NUM_CAMERA	4


#define CAM_IP_ADDRESS	192.168.1.2

#define CAMERA(cam_no) "rtspsrc location=\"rtsp://888888:888888@192.168.1.11:554/cam/realmonitor?channel="#cam_no"&subtype=1 --live --fps 25\" latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264decvpe ! video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! appsink name=sink sync=false"
//#define CAMERA(cam_no)	"rtspsrc location=\"rtsp://888888:888888@192.168.1.11:554/cam/realmonitor?channel="#cam_no"&subtype=1 --live --fps 25\" latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264dec ! appsink name=sink sync=false"


/* create thread argument struct for thr_func() */
typedef struct _thread_data_t {
	int tid;
	double stuff;
} thread_data_t;



/* thread function */
void *thr_func(void *arg) {
	thread_data_t *data = (thread_data_t *)arg;

	printf("hello from thr_func, thread id: %d\n", data->tid);

	pthread_exit(NULL);
}


#endif


#define DAHUA_HD_CAM_SOFT_DECODE	"rtspsrc location=rtsp://admin:jenex#2018@192.168.1.108:554 latency=150 sync=false ! rtph264depay ! h264parse ! avdec_h264 ! appsink name=sink sync=false"

#define DAHUA_HD_CAM_HARD_DECODE	"rtspsrc location=rtsp://admin:jenex#2018@192.168.1.108:554 latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264dec ! appsink name=sink sync=false"

#define NVR_CAM_VID_CVT_BGR		"rtspsrc location=rtsp://888888:888888@192.168.1.5:554 latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264dec ! videoconvert ! video/x-raw,format=(string)BGR ! appsink name=sink sync=false"

#define NVR_CAM_NO_VID_CVT		"rtspsrc location=rtsp://888888:888888@192.168.1.5:554 latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264dec ! appsink name=sink sync=false"

#define NVR_CAM_VPE_CVT_BGR		"rtspsrc location=rtsp://888888:888888@192.168.1.5:554 latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264decvpe ! video/x-raw,format=(string)BGR ! appsink name=sink sync=false"

#define NVR_CAM_VPE_CVT_RGB		"rtspsrc location=rtsp://888888:888888@192.168.1.10:554 latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264decvpe ! video/x-raw,format=(string)RGB ! appsink name=sink sync=false"

#define NVR_CAM_VPE_CVT_NV12		"rtspsrc location=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" latency=150 sync=false ! rtpjitterbuffer ! rtph264depay ! h264parse ! queue ! ducatih264decvpe ! queue !video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! queue ! appsink name=sink sync=false"

//#define NVR_CAM_VPE_CVT_NV12		"rtspsrc location=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" latency=150 sync=false ! rtpjitterbuffer ! rtph264depay ! h264parse ! queue ! ducatih264decvpe ! queue !video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! queue ! fakesink silent=true"

//#define NVR_CAM_VPE_CVT_NV12		"rtspsrc location=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" latency=150 sync=false ! rtpjitterbuffer ! rtph264depay ! h264parse ! queue ! ducatih264decvpe ! queue !video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! queue ! appsink sync=false max_buffers=2 drop=true name=sink emit-signals=true"

//#define NVR_CAM_VPE_CVT_NV12		"rtspsrc location=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264decvpe ! video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! appsink name=sink sync=false"


//#define NVR_CAM_VPE_CVT_NV12		"rtspsrc location=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" latency=150 sync=false ! rtph264depay ! h264parse ! ducatih264decvpe ! video/x-raw, format=(string)NV12, width=(int)800, height=(int)600 ! waylandsink sync=false"


#define DAHUA_HD_CAM_SOFT_DECODE_VID_CVT_BGR	"rtspsrc location=rtsp://admin:jenex#2018@192.168.1.108:554 latency=150 sync=false ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! video/x-raw,format=(string)BGR ! appsink name=sink sync=false"



#ifdef FACE_DETECTION

/** Function Headers */
void detectAndDisplay( Mat frame );

/** Global variables */
//-- Note, either copy these two files from opencv/data/haarscascades to your current folder, or change these locations
String face_cascade_name = "/usr/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml";
CascadeClassifier face_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);

/**
 * @function detectAndDisplay
 */
void detectAndDisplay( Mat frame )
{
	std::vector<Rect> faces;
	Mat frame_gray;

	cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
	equalizeHist( frame_gray, frame_gray );
	//-- Detect faces
	face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );

	printf("\nNo. Of Face-detected= %d\n",faces.size());

	for( size_t i = 0; i < faces.size(); i++ )
	{
		Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
		ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2), 0, 0, 360, Scalar( 255, 0, 255 ), 2, 8, 0 );
	}
	//-- Show what you got
	imshow( window_name, frame );
}

#endif


/**
 * @brief Check preroll to get a new frame using callback
 *  https://gstreamer.freedesktop.org/documentation/design/preroll.html
 * @return GstFlowReturn
 */
GstFlowReturn new_preroll(GstAppSink* /*appsink*/, gpointer /*data*/)
{
	return GST_FLOW_OK;
}

/**
 * @brief This is a callback that get a new frame when a preroll exist
 *
 * @param appsink
 * @return GstFlowReturn
 */
GstFlowReturn new_sample(GstAppSink *appsink, gpointer /*data*/)
{
	static int framecount = 0;

	// Get caps and frame
	GstSample *sample = gst_app_sink_pull_sample(appsink);
	GstCaps *caps = gst_sample_get_caps(sample);
	GstBuffer *buffer = gst_sample_get_buffer(sample);
	GstStructure *structure = gst_caps_get_structure(caps, 0);
	const int width = g_value_get_int(gst_structure_get_value(structure, "width"));
	const int height = g_value_get_int(gst_structure_get_value(structure, "height"));

	// Print dot every 30 frames
	if(!(framecount%30)) {
		g_print(".");
	}

	// Show caps on first frame
	if(!framecount) {
		g_print("caps: %s\n", gst_caps_to_string(caps));
	}
	framecount++;

	// Get frame data
	#ifdef FACE_DETECTION
	GstMapInfo map;
	gst_buffer_map(buffer, &map, GST_MAP_READ);

	// Convert gstreamer data to OpenCV Mat


	cv::Mat* prevFrame;
	prevFrame = atomicFrame.exchange(new cv::Mat(cv::Size(width, height), CV_8UC3, (char*)map.data, cv::Mat::AUTO_STEP));
	if(prevFrame) 
	{
		cv::Mat picBGR;
		//cv::cvtColor(prevFrame, prevFrame, COLOR_YUV2BGR_YV12);
		delete prevFrame;
	}

	gst_buffer_unmap(buffer, &map);
	#endif
	gst_sample_unref(sample);

	return GST_FLOW_OK;
}

/**
 * @brief Bus callback
 *  Print important messages
 *
 * @param bus
 * @param message
 * @param data
 * @return gboolean
 */
static gboolean my_bus_callback(GstBus *bus, GstMessage *message, gpointer data)
{
	// Debug message
	//g_print("Got %s message\n", GST_MESSAGE_TYPE_NAME(message));
	switch(GST_MESSAGE_TYPE(message)) {
		case GST_MESSAGE_ERROR: {
						GError *err;
						gchar *debug;

						gst_message_parse_error(message, &err, &debug);
						g_print("Error: %s\n", err->message);
						g_error_free(err);
						g_free(debug);
						break;
					}
		case GST_MESSAGE_EOS:
					/* end-of-stream */
					break;
		default:
					/* unhandled message */
					break;
	}
	/* we want to be notified again the next time there is a message
	 * on the bus, so returning TRUE (FALSE means we want to stop watching
	 * for messages on the bus and our callback should not be called again)
	 */
	return true;
}

int main(int argc, char *argv[]) 
{
#ifdef FACE_DETECTION
	//-- 1. Load the cascades
	if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
#endif

#ifdef MULTI_THREAD

	int camera_number, rc[NUM_CAMERA];

	gchar *descr_camera[NUM_CAMERA];
	GstMessage *msg_camera[NUM_CAMERA];
	GError *error_camera[NUM_CAMERA];
	GstElement *pipeline_camera[NUM_CAMERA];

	// Get sink
	GstElement *sink_camera[NUM_CAMERA];

	// Declare bus
	GstBus *bus_camera[NUM_CAMERA];

	gst_init(&argc, &argv);

	pthread_t thr[NUM_CAMERA];

	/* create a thread_data_t argument array */
	thread_data_t thr_data[NUM_CAMERA];

	/* create threads */
	for (camera_number = 0; camera_number < NUM_CAMERA; ++camera_number) 
	{
		thr_data[camera_number].tid = camera_number;

		//char sink_camera_name[15]={0};

		//sprintf(sink_camera_name,"sink_camera_%d",camera_number);

		if ((rc[camera_number] = pthread_create(&thr[camera_number], NULL, thr_func, &thr_data[camera_number]))) 
		{
			fprintf(stderr, "error: pthread_create, rc_camera[%d]: %d\n",camera_number, rc[camera_number]);
			return EXIT_FAILURE;
		}

		
		switch(camera_number+1)
		{
			case 1:
				descr_camera[camera_number] = g_strdup(
									CAMERA(1)
								);	
				break;
			case 2:
				descr_camera[camera_number] = g_strdup(
									CAMERA(2)
								);	
				break;
			case 3:
				descr_camera[camera_number] = g_strdup(
									CAMERA(3)
								);	
				break;
			case 4:
				descr_camera[camera_number] = g_strdup(
									CAMERA(4)
								);	
				break;
			default:	
				break;
		}


		// Check pipeline
		error_camera[camera_number] = nullptr;
		pipeline_camera[camera_number] = gst_parse_launch(descr_camera[camera_number], &(error_camera[camera_number]));

		if(error_camera[camera_number]) 
		{
			g_print("could not construct pipeline: %s\n%s\n", error_camera[camera_number]->message,descr_camera[camera_number]);
			g_error_free(error_camera[camera_number]);
			exit(-1);
		}
		else
		{
			g_print("construct pipeline: %s\n", descr_camera[camera_number]);
		}

		sink_camera[camera_number] = gst_bin_get_by_name(GST_BIN(pipeline_camera[camera_number]), "sink");

		/**
		 * @brief Get sink signals and check for a preroll
		 *  If preroll exists, we do have a new frame
		 */
		gst_app_sink_set_emit_signals((GstAppSink*)sink_camera[camera_number], true);
		gst_app_sink_set_drop((GstAppSink*)sink_camera[camera_number], true);
		gst_app_sink_set_max_buffers((GstAppSink*)sink_camera[camera_number], 1);
		GstAppSinkCallbacks callbacks = { nullptr, new_preroll, new_sample };
		gst_app_sink_set_callbacks(GST_APP_SINK(sink_camera[camera_number]), &callbacks, nullptr, nullptr);

		bus_camera[camera_number] = gst_pipeline_get_bus(GST_PIPELINE(pipeline_camera[camera_number]));
		gst_bus_add_watch(bus_camera[camera_number], my_bus_callback, nullptr);
		gst_object_unref(bus_camera[camera_number]);


		gst_element_set_state(GST_ELEMENT(pipeline_camera[camera_number]), GST_STATE_PLAYING);
	}


	/* block until all threads complete */
	for (camera_number = 0; camera_number < NUM_CAMERA; ++camera_number) 
	{
		pthread_join(thr[camera_number], NULL);
	}

	// Main loop
	while(1) 
	{
		g_main_iteration(false);

		cv::Mat* frame = atomicFrame.load();
		//	printf("\nIn While\n");
		if(frame) 
		{
			//printf("\nIn If\n");

			//	cv::imshow("Frame", atomicFrame.load()[0]);

#ifdef FACE_DETECTION
			detectAndDisplay( atomicFrame.load()[0]);
#endif
			cv::waitKey(30);
		}
	}

	for (camera_number = 0; camera_number < NUM_CAMERA; ++camera_number)
	{	
		gst_element_set_state(GST_ELEMENT(pipeline_camera[camera_number]), GST_STATE_NULL);
		gst_object_unref(GST_OBJECT(pipeline_camera[camera_number]));
	}

#else

	GstMessage *msg;

	gst_init(&argc, &argv);


/*	
gchar *descr = g_strdup(
    "videotestsrc  pattern=ball ! "
    "video/x-raw,format=RGB ! "
    "videoconvert ! "
    "appsink name=sink sync=true"
  );
*/
	gchar *descr = g_strdup(
			NVR_CAM_VPE_CVT_NV12		
			//NVR_CAM_VPE_CVT_RGB		
			//NVR_CAM_NO_VID_CVT		
			);


	// Check pipeline
	GError *error = nullptr;
	//GstElement *pipeline = gst_parse_launch(descr, &error);
	//GstElement *pipeline = gst_parse_launch("playbin flags=0x57 uri=\"rtsp://888888:888888@192.168.1.2:554/cam/realmonitor?channel=1&subtype=0 --live --fps 25\" video-sink=waylandsink", &error);
	//GstElement *pipeline = gst_parse_launch("playbin uri=\"rtsp://888888:888888@192.168.1.12:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" video-sink=waylandsink",NULL);

	//GstElement *pipeline = gst_parse_launch("rtspsrc location=\"rtsp://888888:888888@192.168.1.4:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" ! rtph264depay ! h264parse ! ducatih264decvpe ! 'video/x-raw, format=(string)NV12, width=(int)800, height=(int)600' ! video-sink=waylandsink", &error);
	
	//GstElement *pipeline = gst_parse_launch("rtspsrc location=\"rtsp://888888:888888@192.168.1.4:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\" ! rtph264depay ! h264parse ! ducatih264decvpe ! 'video/x-raw, format=(string)NV12, width=(int)800, height=(int)600' ! appsink name=sink", &error);


	GstElement *pipeline = gst_parse_launch ("rtspsrc location=\"rtsp://888888:888888@192.168.1.4:554/cam/realmonitor?channel=1&subtype=1 --live --fps 25\"! queue ! rtph264depay ! h264parse ! ducatih264dec ! vpe ! capsfilter caps=video/x-raw,format=NV12,width=720,height=420 ! queue ! waylandsink" , NULL);


	if(error) {
		g_print("could not construct pipeline: %s\n", error->message);
		g_error_free(error);
		exit(-1);
	}

	// Get sink
	GstElement *sink = gst_bin_get_by_name(GST_BIN(pipeline), "sink");


	/**
	 * @brief Get sink signals and check for a preroll
	 *  If preroll exists, we do have a new frame
	 */
	gst_app_sink_set_emit_signals((GstAppSink*)sink, true);
	gst_app_sink_set_drop((GstAppSink*)sink, true);
	gst_app_sink_set_max_buffers((GstAppSink*)sink, 1);
	//GstAppSinkCallbacks callbacks = { nullptr, new_preroll, new_sample };
	//gst_app_sink_set_callbacks(GST_APP_SINK(sink), &callbacks, nullptr, nullptr);

	// Declare bus
	GstBus *bus;
	bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
	gst_bus_add_watch(bus, my_bus_callback, nullptr);
	gst_object_unref(bus);


	gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING);

	// Main loop
	while(1) 
	{
		g_main_iteration(false);

		cv::Mat* frame = atomicFrame.load();
		//	printf("\nIn While\n");
		if(frame) 
		{
			//printf("\nIn If\n");

			//	cv::imshow("Frame", atomicFrame.load()[0]);

#ifdef FACE_DETECTION
			detectAndDisplay( atomicFrame.load()[0]);
#endif
			cv::waitKey(30);
		}
	}

	gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_NULL);
	gst_object_unref(GST_OBJECT(pipeline));
#endif
	return 0;

}
