/*
 * Copyright (c) 2013-2014 Chun-Ying Huang
 *
 * This file is part of GamingAnywhere (GA).
 *
 * GA is free software; you can redistribute it and/or modify it
 * under the terms of the 3-clause BSD License as published by the
 * Free Software Foundation: http://directory.fsf.org/wiki/License:BSD_3Clause
 *
 * GA is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the 3-clause BSD License along with GA;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <stdio.h>
#include <pthread.h>
#include <map>

#include "vsource.h"
#include "vconverter.h"
#include "rtspconf.h"
#include "encoder-common.h"

#include "ga-common.h"
#include "ga-conf.h"
#include "ga-avcodec.h"

#include "dpipe.h"
#include "dpipe.h"
#include "filter-rgb2yuv.h"

#define	POOLSIZE		8
#define	ENABLE_EMBED_COLORCODE	1

using namespace std;

static int filter_initialized = 0;
static int filter_started = 0;
static pthread_t filter_tid[VIDEO_SOURCE_CHANNEL_MAX];
static FILE *savefp = NULL;

/* filter_RGB2YUV_init: arg is two pointers to pipeline format string */
/*	1st ptr: source pipeline */
/*	2nd ptr: destination pipeline */
/* the number of pipeline(s) is equivalut to the number of video source(s) */

static int
filter_RGB2YUV_init(void *arg) {
	// arg is image source id
	int iid;
	const char **filterpipe = (const char **) arg;
	dpipe_t *srcpipe[VIDEO_SOURCE_CHANNEL_MAX];
	dpipe_t *dstpipe[VIDEO_SOURCE_CHANNEL_MAX];
	char savefile[128];
	//
	if(filter_initialized != 0)
		return 0;
	//
	if(ga_conf_readv("save-yuv-image", savefile, sizeof(savefile)) != NULL) {
		savefp = ga_save_init(savefile);
	}
#ifdef ENABLE_EMBED_COLORCODE
	vsource_embed_colorcode_init(0/*RGBmode*/);
#endif
	//
	bzero(dstpipe, sizeof(dstpipe));
	//
	for(iid = 0; iid < video_source_channels(); iid++) {
		char pixelfmt[64];
		char srcpipename[64], dstpipename[64];
		int inputW, inputH, outputW, outputH;
		struct SwsContext *swsctx = NULL;
		dpipe_buffer_t *data = NULL;
		//
		snprintf(srcpipename, sizeof(srcpipename), filterpipe[0], iid);
		snprintf(dstpipename, sizeof(dstpipename), filterpipe[1], iid);
		srcpipe[iid] = dpipe_lookup(srcpipename);
		if(srcpipe[iid] == NULL) {
			ga_error("RGB2YUV filter: cannot find pipe %s\n", srcpipename);
			goto init_failed;
		}
		inputW = video_source_curr_width(iid);
		inputH = video_source_curr_height(iid);
		outputW = video_source_out_width(iid);
		outputH = video_source_out_height(iid);
		// create default converters
		if(ga_conf_readv("filter-source-pixelformat", pixelfmt, sizeof(pixelfmt)) != NULL) {
			if(strcasecmp("rgba", pixelfmt) == 0) {
				swsctx = create_frame_converter(
						inputW, inputH, PIX_FMT_RGBA,
						outputW, outputH, PIX_FMT_YUV420P);
				ga_error("RGB2YUV filter: RGBA source specified.\n");
			} else if(strcasecmp("bgra", pixelfmt) == 0) {
				swsctx = create_frame_converter(
						inputW, inputH, PIX_FMT_BGRA,
						outputW, outputH, PIX_FMT_YUV420P);
				ga_error("RGB2YUV filter: BGRA source specified.\n");
			} else if(strcasecmp("yuv420p", pixelfmt) == 0) {
				swsctx = create_frame_converter(
						inputW, inputH, PIX_FMT_YUV420P,
						outputW, outputH, PIX_FMT_YUV420P);
				ga_error("RGB2YUV filter: YUV source specified.\n");
			}
		}
		if(swsctx == NULL) {
#ifdef __APPLE__
			swsctx = create_frame_converter(
					inputW, inputH, PIX_FMT_RGBA,
					outputW, outputH, PIX_FMT_YUV420P);
#else
			swsctx = create_frame_converter(
					inputW, inputH, PIX_FMT_BGRA,
					outputW, outputH, PIX_FMT_YUV420P);
#endif
		}
		if(swsctx == NULL) {
			ga_error("RGB2YUV filter: cannot initialize converters.\n");
			goto init_failed;
		}
		//
		dstpipe[iid] = dpipe_create(iid, dstpipename, POOLSIZE,
				sizeof(vsource_frame_t) + video_source_mem_size(iid));
		if(dstpipe[iid] == NULL) {
			ga_error("RGB2YUV filter: create dst-pipeline failed (%s).\n", dstpipename);
			goto init_failed;
		}
		for(data = dstpipe[iid]->in; data != NULL; data = data->next) {
			if(vsource_frame_init(iid, (vsource_frame_t*) data->pointer) == NULL) {
				ga_error("RGB2YUV filter: init frame failed for %s.\n", dstpipename);
				goto init_failed;
			}
		}
		video_source_add_pipename(iid, dstpipename);
	}
	//
	filter_initialized = 1;
	//
	return 0;
init_failed:
	for(iid = 0; iid < video_source_channels(); iid++) {
		if(dstpipe[iid] != NULL)
			dpipe_destroy(dstpipe[iid]);
		dstpipe[iid] = NULL;
	}
#if 0
	if(pipe) {
		delete pipe;
	}
#endif
	return -1;
}

static int
filter_RGB2YUV_deinit(void *arg) {
	if(savefp != NULL) {
		ga_save_close(savefp);
		savefp = NULL;
	}
	return 0;
}

/* filter_RGB2YUV_threadproc: arg is two pointers to pipeline name */
/*	1st ptr: source pipeline */
/*	2nd ptr: destination pipeline */

static void *
filter_RGB2YUV_threadproc(void *arg) {
	// arg is pointer to source pipe
	//char pipename[64];
	const char **filterpipe = (const char **) arg;
	dpipe_t *srcpipe = dpipe_lookup(filterpipe[0]);
	dpipe_t *dstpipe = dpipe_lookup(filterpipe[1]);
	dpipe_buffer_t *srcdata = NULL;
	dpipe_buffer_t *dstdata = NULL;
	vsource_frame_t *srcframe = NULL;
	vsource_frame_t *dstframe = NULL;
	// image info
	//int istride = video_source_maxstride();
	//
	unsigned char *src[] = { NULL, NULL, NULL, NULL };
	unsigned char *dst[] = { NULL, NULL, NULL, NULL };
	int srcstride[] = { 0, 0, 0, 0 };
	int dststride[] = { 0, 0, 0, 0 };
	int iid;
	int outputW, outputH;
	//
	struct SwsContext *swsctx = NULL;
	//
	pthread_mutex_t condMutex = PTHREAD_MUTEX_INITIALIZER;
	pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
	//
	if(srcpipe == NULL || dstpipe == NULL) {
		ga_error("RGB2YUV filter: bad pipeline (src=%p; dst=%p).\n", srcpipe, dstpipe);
		goto filter_quit;
	}
#ifdef ENABLE_EMBED_COLORCODE
	vsource_embed_colorcode_reset();
#endif
	//
	iid = dstpipe->channel_id;
	outputW = video_source_out_width(iid);
	outputH = video_source_out_height(iid);
	//
	ga_error("RGB2YUV filter[%ld]: pipe#%d from '%s' to '%s' (output-resolution=%dx%d)\n",
		ga_gettid(), iid,
		srcpipe->name, dstpipe->name,
		outputW/*iwidth*/, outputH/*iheight*/);
	// start filtering
	while(filter_started != 0) {
		// wait for notification
		srcdata = dpipe_load(srcpipe, NULL);
		if(srcdata == NULL) {
			ga_error("RGB2YUV filter: unexpected NULL frame received (from '%s', data=%d, buf=%d).\n",
				srcpipe->name, srcpipe->out_count, srcpipe->in_count);
			exit(-1);
			// should never be here
			goto filter_quit;
		}
		srcframe = (vsource_frame_t*) srcdata->pointer;
		//
		dstdata = dpipe_get(dstpipe);
		dstframe = (vsource_frame_t*) dstdata->pointer;
		// basic info
		dstframe->imgpts = srcframe->imgpts;
		dstframe->timestamp = srcframe->timestamp;
		dstframe->pixelformat = PIX_FMT_YUV420P;	//yuv420p;
		dstframe->realwidth = outputW;
		dstframe->realheight = outputH;
		dstframe->realstride = outputW;
		dstframe->realsize = outputW * outputH * 3 / 2;
		// scale image: RGBA, BGRA, or YUV
		swsctx = lookup_frame_converter(
				srcframe->realwidth,
				srcframe->realheight,
				srcframe->pixelformat);
		if(swsctx == NULL) {
			swsctx = create_frame_converter(
				srcframe->realwidth,
				srcframe->realheight,
				srcframe->pixelformat,
				outputW,
				outputH,
				PIX_FMT_YUV420P);
		}
		if(swsctx == NULL) {
			ga_error("RGB2YUV filter: fatal - cannot create frame converter (%d,%d,%d)->(%x,%d,%d)\n",
				srcframe->realwidth, srcframe->realheight, srcframe->pixelformat,
				outputW, outputH, PIX_FMT_YUV420P);
		}
		//
		if(srcframe->pixelformat == PIX_FMT_RGBA
		|| srcframe->pixelformat == PIX_FMT_BGRA/*rgba*/) {
			src[0] = srcframe->imgbuf;
			src[1] = NULL;
			srcstride[0] = srcframe->realstride; //srcframe->stride;
			srcstride[1] = 0;
		} else if(srcframe->pixelformat == PIX_FMT_YUV420P) {
			src[0] = srcframe->imgbuf;
			src[1] = src[0] + ((srcframe->realwidth * srcframe->realheight));
			src[2] = src[1] + ((srcframe->realwidth * srcframe->realheight)>>2);
			src[3] = NULL;
			srcstride[0] = srcframe->linesize[0];
			srcstride[1] = srcframe->linesize[1];
			srcstride[2] = srcframe->linesize[2];
			srcstride[3] = NULL;
		} else {
			ga_error("filter-RGB2YUV: unsupported pixel format (%d)\n", srcframe->pixelformat);
			exit(-1);
		}
		//
		dst[0] = dstframe->imgbuf;
		dst[1] = dstframe->imgbuf + outputH*outputW;
		dst[2] = dstframe->imgbuf + outputH*outputW + (outputH*outputW>>2);
		dst[3] = NULL;
		dstframe->linesize[0] = dststride[0] = outputW;
		dstframe->linesize[1] = dststride[1] = outputW>>1;
		dstframe->linesize[2] = dststride[2] = outputW>>1;
		dstframe->linesize[3] = dststride[3] = 0;
		//
		sws_scale(swsctx,
			src, srcstride, 0, srcframe->realheight,
			dst, dstframe->linesize);
		// embed first, and then save
#ifdef ENABLE_EMBED_COLORCODE
		vsource_embed_colorcode_inc(dstframe);
#endif
		// only save the first channel
		if(iid == 0 && savefp != NULL) {
			ga_save_yuv420p(savefp, outputW, outputH, dst, dstframe->linesize);
		}
		//
		dpipe_put(srcpipe, srcdata);
		dpipe_store(dstpipe, dstdata);
		//
	}
	//
filter_quit:
	if(srcpipe) {
		srcpipe = NULL;
	}
	if(dstpipe) {
		dpipe_destroy(dstpipe);
		dstpipe = NULL;
	}
	//
	if(swsctx)	sws_freeContext(swsctx);
	//
	ga_error("RGB2YUV filter: thread terminated.\n");
	//
	return NULL;
}

/* filter_RGB2YUV_start: arg is two pointers to pipeline format string */
/*	1st ptr: source pipeline */
/*	2nd ptr: destination pipeline */
/* the number of pipeline(s) is equivalut to the number of video source(s) */

static int
filter_RGB2YUV_start(void *arg) {
	int iid;
	const char **filterpipe = (const char **) arg;
	static char *filter_param[VIDEO_SOURCE_CHANNEL_MAX][2];
#define	MAXPARAMLEN	64
	static char params[VIDEO_SOURCE_CHANNEL_MAX][2][MAXPARAMLEN];
	//
	if(filter_started != 0)
		return 0;
	filter_started = 1;
	for(iid = 0; iid < video_source_channels(); iid++) {
		snprintf(params[iid][0], MAXPARAMLEN, filterpipe[0], iid);
		snprintf(params[iid][1], MAXPARAMLEN, filterpipe[1], iid);
		filter_param[iid][0] = params[iid][0];
		filter_param[iid][1] = params[iid][1];
		if(pthread_create(&filter_tid[iid], NULL, filter_RGB2YUV_threadproc, filter_param[iid]) != 0) {
			filter_started = 0;
			ga_error("filter RGB2YUV: create thread failed.\n");
			return -1;
		}
		pthread_detach(filter_tid[iid]);
	}
	return 0;
}

/* filter_RGB2YUV_stop: no arguments are required */

static int
filter_RGB2YUV_stop(void *arg) {
	int iid;
	if(filter_started == 0)
		return 0;
	filter_started = 0;
	for(iid = 0; iid < video_source_channels(); iid++) {
		pthread_cancel(filter_tid[iid]);
	}
	return 0;
}

ga_module_t *
module_load() {
	static ga_module_t m;
	bzero(&m, sizeof(m));
	m.type = GA_MODULE_TYPE_FILTER;
	m.name = strdup("filter-RGB2YUV");
	m.init = filter_RGB2YUV_init;
	m.start = filter_RGB2YUV_start;
	m.stop = filter_RGB2YUV_stop;
	m.deinit = filter_RGB2YUV_deinit;
	//m.threadproc = filter_RGB2YUV_threadproc;
	return &m;
}

