#include <getopt.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "hb_videostitch_interface.h"
#include "hb_mem_mgr.h"
#include "utils.h"
#include "hb_vpm_data_info.h"
#include "hb_vio_interface.h"

#define  PIPELINE_NUMS  4
uint32_t g_loop = 5;
uint32_t g_need_dump = 0;
uint32_t g_debug = 0;
const char *g_stitch_cfg_file = NULL;
const char *g_vpm_file = NULL;
const char *g_res_path = "/home/hobot/res";
hb_mem_common_buf_t gdc_config_buf[PIPELINE_NUMS];
hb_mem_graphic_buf_t stitch_dst_buf;
hb_mem_common_buf_t alpha_buffer;
hb_mem_common_buf_t beta_buffer;


void print_usage(const char *prog)
{
	printf("Usage: %s \n", prog);
	puts("  -l --loop           need while/loop\n"
		 "  -d --dump           dump img, 1: dump stitch output 2: dump gdc output 3: dump all\n"
		 "  -s --stitch_cfg     stitch_cfg file\n"
		 "  -v --vpm_cfg        vpm_cfg file\n"
		 "  -r --res_path       res file path, default = /home/hobot/res\n"
		 "  -D --debug          print stitch cfg\n");
	exit(1);
}

void parse_opts(int argc, char *argv[])
{
	int cmd_ret;
	static const char short_options[] =
			"l:d:s:v:D:r:h";
	static const struct option long_options[] = {
		{ "loop", 1, 0, 'l' },
		{ "dump", 1, 0, 'd' },
		{ "stitch_cfg", 1, 0, 's' },
		{ "vpm_cfg", 1, 0, 'v' },
		{ "debug", 1, 0, 'D' },
		{ "res", 1, 0, 'r' },
		{ "help", 1, 0, 'h' },
		{ NULL, 0, 0, 0 },
	};

	while (1) {
		cmd_ret = getopt_long(argc, argv, short_options, long_options, NULL);
		if (cmd_ret == -1)
			break;

		switch (cmd_ret) {
			case 'l':
				g_loop = atoi(optarg);
				printf("loop = %d\n", g_loop);
				break;
			case 'd':
				g_need_dump = atoi(optarg);
				printf("need_dump = %d\n", g_need_dump);
				break;
			case 's':
				g_stitch_cfg_file = optarg;
				printf("cfg_file = %s\n", g_stitch_cfg_file);
				break;
			case 'v':
				g_vpm_file = optarg;
				printf("vpm_cfg = %s\n", g_vpm_file);
				break;
			case 'r':
				g_res_path = optarg;
				printf("g_res_path = %s\n", g_res_path);
				break;
			case 'D':
				g_debug = atoi(optarg);
				printf("debug = %d\n", g_debug);
				break;
			default:
				print_usage(argv[0]);
				break;
		}
	}
	if (g_stitch_cfg_file == NULL || g_vpm_file == NULL)
		print_usage(argv[0]);
}


int32_t init_gdc(uint32_t pipeline_id, char* filename) {
	int32_t ret = 0;
	struct stat fileStat;

	if(stat(filename, &fileStat) != 0) {
		printf("Failed to get file stats. cfg file = %s\n", filename);
		return -1;
	}

	ret = hb_mem_alloc_com_buf(fileStat.st_size, HB_MEM_USAGE_MAP_INITIALIZED |
									HB_MEM_USAGE_PRIV_HEAP_2_RESERVERD | HB_MEM_USAGE_CPU_READ_OFTEN |
									HB_MEM_USAGE_CPU_WRITE_OFTEN | HB_MEM_USAGE_CACHED, &gdc_config_buf[pipeline_id]);
	if (ret < 0) {
		printf("hb_mem_alloc_com_buf alpha_lut faild, ret = %d\n", ret);
		return -1;
	}
	load_file_2_buff(filename, (char *)gdc_config_buf[pipeline_id].virt_addr, fileStat.st_size);
	hb_mem_flush_buf_with_vaddr((uint64_t)gdc_config_buf[pipeline_id].virt_addr, fileStat.st_size);
	ret = hb_vio_start_pipeline(pipeline_id);
	if (ret < 0) {
		hb_mem_free_buf(gdc_config_buf[pipeline_id].fd);
		printf("vpm start %d err!, ret = %d\n", pipeline_id, ret);
		return -1;
	}
	ret = hb_vio_set_gdc_cfg(pipeline_id, (uint32_t *)gdc_config_buf[pipeline_id].virt_addr, fileStat.st_size);
	if (ret < 0) {
		hb_vio_stop_pipeline(pipeline_id);
		hb_mem_free_buf(gdc_config_buf[pipeline_id].fd);
		printf("gdc_cfg_bin_update err\n");
		return -1;
	}
	return 0;
}

void deinit_gdc(uint32_t pipeline_id)
{
	hb_vio_stop_pipeline(pipeline_id);
	hb_mem_free_buf(gdc_config_buf[pipeline_id].fd);
}

int32_t init_stitch(stitch_test_ctx_t *test_ctx) {
	int32_t ret = 0;

	// Prepare stitch dst img buf
	hb_mem_alloc_graph_buf(test_ctx->dst_w, test_ctx->dst_h, MEM_PIX_FMT_NV12,
							HB_MEM_USAGE_MAP_INITIALIZED | HB_MEM_USAGE_PRIV_HEAP_2_RESERVERD |
							HB_MEM_USAGE_CPU_READ_OFTEN | HB_MEM_USAGE_CPU_WRITE_OFTEN |
							HB_MEM_USAGE_CACHED, test_ctx->dst_w, test_ctx->dst_h, &stitch_dst_buf);
	hb_mem_flush_buf_with_vaddr((uint64_t)stitch_dst_buf.virt_addr[0], stitch_dst_buf.size[0]);
	hb_mem_flush_buf_with_vaddr((uint64_t)stitch_dst_buf.virt_addr[1], stitch_dst_buf.size[1]);
	test_ctx->dst_frame.virt_y_addr = (char *)stitch_dst_buf.virt_addr[0];
	test_ctx->dst_frame.virt_uv_addr = (char *)stitch_dst_buf.virt_addr[1];
	test_ctx->dst_frame.y_size = stitch_dst_buf.size[0];
	test_ctx->dst_frame.uv_size = stitch_dst_buf.size[1];

	// Prepare blending lut buf
	if (test_ctx->alpha_file_size != 0) {
		ret = hb_mem_alloc_com_buf(test_ctx->alpha_file_size, HB_MEM_USAGE_MAP_INITIALIZED |
								HB_MEM_USAGE_PRIV_HEAP_2_RESERVERD | HB_MEM_USAGE_CPU_READ_OFTEN |
								HB_MEM_USAGE_CPU_WRITE_OFTEN | HB_MEM_USAGE_CACHED, &alpha_buffer);
		if (ret < 0) {
			printf("hb_mem_alloc_com_buf alpha_lut faild, ret = %d\n", ret);
			return -1;
		}
		load_file_2_buff(test_ctx->alpha_file, (char *)alpha_buffer.virt_addr, test_ctx->alpha_file_size);
		hb_mem_flush_buf_with_vaddr((uint64_t)alpha_buffer.virt_addr, test_ctx->alpha_file_size);
		test_ctx->lut_table.alpha_lut_table.pa_lut = alpha_buffer.phys_addr;
		test_ctx->lut_table.alpha_lut_table.lut_parameter = (char *)alpha_buffer.virt_addr;
		test_ctx->lut_table.alpha_lut_table.size = test_ctx->alpha_file_size;
	}
	if (test_ctx->beta_file_size != 0) {
		ret = hb_mem_alloc_com_buf(test_ctx->beta_file_size, HB_MEM_USAGE_MAP_INITIALIZED |
								HB_MEM_USAGE_PRIV_HEAP_2_RESERVERD | HB_MEM_USAGE_CPU_READ_OFTEN |
								HB_MEM_USAGE_CPU_WRITE_OFTEN | HB_MEM_USAGE_CACHED, &beta_buffer);
		if (ret < 0) {
			printf("hb_mem_alloc_com_buf beta_lut faild, ret = %d\n", ret);
			return -1;
		}
		load_file_2_buff(test_ctx->beta_file, (char *)beta_buffer.virt_addr, test_ctx->beta_file_size);
		hb_mem_flush_buf_with_vaddr((uint64_t)beta_buffer.virt_addr, test_ctx->beta_file_size);
		test_ctx->lut_table.beta_lut_table.pa_lut = beta_buffer.phys_addr;
		test_ctx->lut_table.beta_lut_table.lut_parameter = (char *)beta_buffer.virt_addr;
		test_ctx->lut_table.beta_lut_table.size = test_ctx->beta_file_size;
	}

	// init and cfg stitch
	ret = hb_videostitch_init();
	if (ret < 0) {
		printf("hb_videostitch_init faild, ret = %d\n", ret);
		return -1;
	}
	test_ctx->mode_ctrl.working_mode = STITCH_MODE_EXTERNAL_BUF;
	ret = hb_videostitch_set_cfg(test_ctx->mode_ctrl, test_ctx->roi_num, test_ctx->roi_cfg, &test_ctx->lut_table, NULL);
	if (ret < 0) {
		printf("hb_videostitch_set_cfg faild, ret = %d\n", ret);
		return -1;
	}

	return 0;
}

int32_t deinit_stitch(stitch_test_ctx_t *test_ctx) {
	int32_t ret = 0;
	ret = hb_videostitch_deinit();
	hb_mem_free_buf(stitch_dst_buf.fd[0]);

	if (test_ctx->alpha_file_size != 0)
		hb_mem_free_buf(alpha_buffer.fd);

	if (test_ctx->beta_file_size != 0)
		hb_mem_free_buf(beta_buffer.fd);
	return ret;
}

int main(int argc, char *argv[]) {

	int32_t i = 0, j = 0;
	int32_t ret = 0;
	uint32_t loop = 0;
	char name[64] = { 0 };
	char res_file_name[128] = {0};

	stitch_test_ctx_t test_ctx = {0};
	hb_vio_buffer_t gdc_src_buf = {0};
	hb_vio_buffer_t gdc_dst_buf[PIPELINE_NUMS] = {0};
	char* gdc_config[PIPELINE_NUMS] = {
		"camera_0_gdc_cfg.bin",
		"camera_1_gdc_cfg.bin",
		"camera_2_gdc_cfg.bin",
		"camera_3_gdc_cfg.bin"
	};
	// hb_mem_common_buf_t gdc_config_buf[PIPELINE_NUMS];
	char* gdc_yuv_file[PIPELINE_NUMS] = {
		"camera_0.yuv",
		"camera_1.yuv",
		"camera_2.yuv",
		"camera_3.yuv"
	};

	hb_mem_module_open();
	parse_opts(argc, argv);

	// parser stitch cfg json flie
	if (stitch_cfg_parser(g_stitch_cfg_file, &test_ctx) != 0) {
		printf("parser stitch cfg file faild, cfg_file = %s\n", g_stitch_cfg_file);
		return -1;
	}
	if (g_debug) {
		show_cfg(&test_ctx);
	}

	// init vps
	ret = hb_vio_init(g_vpm_file);
	if (ret < 0) {
		printf("hb_vio_init err\n");
		return -1;
	}
	// init gdc
	for (i = 0; i < PIPELINE_NUMS; i++) {
		memset(res_file_name, 0, sizeof(res_file_name));
		sprintf(res_file_name, "%s/%s", g_res_path, gdc_config[i]);
		ret = init_gdc(i, res_file_name);
		if (ret < 0) {
			for (j = 0; j < i; j++) {
				deinit_gdc(j);
			}
			hb_vio_deinit();
			return -1;
		}
	}
	// init stitch
	ret = init_stitch(&test_ctx);
	if (ret < 0) {
		goto err1;
	}

	for (loop = 0; loop < g_loop; loop++) {
		// gdc process
		for (i = 0; i < PIPELINE_NUMS; i++) {
			 ret = hb_vio_get_data(i, HB_VIO_GDC_FEEDBACK_SRC_DATA, &gdc_src_buf);
			if (ret < 0) {
				printf("vio get gdc_buffer fail,pipeline=%d\n", i);
				goto err;
			}
			// push yuv data to src_buf
			int img_size = gdc_src_buf.img_addr.stride_size * gdc_src_buf.img_addr.height;
			memset(res_file_name, 0, sizeof(res_file_name));
			sprintf(res_file_name, "%s/%s", g_res_path, gdc_yuv_file[i]);
			ret = read_yuv420_file(res_file_name,
							gdc_src_buf.img_addr.addr[0],
							gdc_src_buf.img_addr.addr[1],
							img_size);
			// run gdc
			ret |= hb_vio_run_gdc(i, &gdc_src_buf, &gdc_dst_buf[i], 0);
			if (ret < 0) {
				printf("hb_vio_run_gdc error! ret = %d\n", ret);
				goto err;
			}
			hb_vio_free_gdcbuf(i, &gdc_src_buf);
			printf("vio get gdc_buffer success,pipeline=%d\n", i);
			if (g_need_dump & 0x2) {
				sprintf(name, "gdc_pipe%d_dst_w%d_h%d_cnt%d.yuv", i, gdc_dst_buf[i].img_addr.width,
					gdc_dst_buf[i].img_addr.height, loop);
				dumpToFile2plane(name, gdc_dst_buf[i].img_addr.addr[0],
								gdc_dst_buf[i].img_addr.addr[1],
								gdc_dst_buf[i].img_info.size[0],
								gdc_dst_buf[i].img_info.size[1]);
			}
			// gdc hb_vio_buffer_t covert to stitch frame_t
			test_ctx.src_frames[i].pic_id = gdc_dst_buf[i].img_info.frame_id;
			test_ctx.src_frames[i].buf_index =  gdc_dst_buf[i].img_info.buf_index;
			test_ctx.src_frames[i].virt_y_addr = gdc_dst_buf[i].img_addr.addr[0];
			test_ctx.src_frames[i].virt_uv_addr = (char *)gdc_dst_buf[i].img_addr.addr[1];
			test_ctx.src_frames[i].y_size = gdc_dst_buf[i].img_info.size[0];
			test_ctx.src_frames[i].uv_size = gdc_dst_buf[i].img_info.size[1];
		}

		// stitch blending process
		ret = hb_videostitch_blending_process(test_ctx.src_nums, test_ctx.src_frames, &test_ctx.dst_frame);
		if (ret < 0) {
			printf("hb_videostitch_blending_processe faild, ret = %d\n", ret);
			goto err;
		}
		if (g_need_dump & 0x1) {
			sprintf(name, "stitch_dst_w%d_h%d_cnt%d.yuv", test_ctx.dst_w, test_ctx.dst_h, loop);
			dumpToFile2plane(name, (char *)(test_ctx.dst_frame.virt_y_addr),
							(char *)(test_ctx.dst_frame.virt_uv_addr), test_ctx.dst_frame.y_size,
							test_ctx.dst_frame.uv_size);
		}
		for (i = 0; i < PIPELINE_NUMS; i++) {
			hb_vio_free_gdcbuf(i, &gdc_dst_buf[i]);
		}
	}

err:
	// deinit all module
	ret |= deinit_stitch(&test_ctx);
err1:
	for (i = 0; i < PIPELINE_NUMS; i++) {
		deinit_gdc(i);
	}
	ret |= hb_vio_deinit();
	if (ret < 0) {
		printf("GDC & STITCH TEST FAILD\n");
		return -1;
	}

	printf("GDC & STITCH TEST PASSED\n");
	return 0;
}
