/*
 *******************************************************************************
 *  Copyright (c) 2010-2022 VATICS(KNERON) Inc. All rights reserved.
 *
 *  +-----------------------------------------------------------------+
 *  | THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY ONLY BE USED |
 *  | AND COPIED IN ACCORDANCE WITH THE TERMS AND CONDITIONS OF SUCH  |
 *  | A LICENSE AND WITH THE INCLUSION OF THE THIS COPY RIGHT NOTICE. |
 *  | THIS SOFTWARE OR ANY OTHER COPIES OF THIS SOFTWARE MAY NOT BE   |
 *  | PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY OTHER PERSON. THE   |
 *  | OWNERSHIP AND TITLE OF THIS SOFTWARE IS NOT TRANSFERRED.        |
 *  |                                                                 |
 *  | THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT   |
 *  | ANY PRIOR NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY |
 *  | VATICS(KNERON) INC.                                             |
 *  +-----------------------------------------------------------------+
 *
 *******************************************************************************
 */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>

#include "lvgl/lvgl.h"
#include "lvgl/custom/lv_custom.h"
#include "lvgl_pcsimulator.h"
#include "lvgl_pcsimulator_msg_sender.h"
#include "lvgl_widgets.h"

#if defined(PC_SIMULATOR)
#include "lv_drivers/display/monitor.h"
#include "lv_drivers/indev/mouse.h"
#include "lv_drivers/indev/mousewheel.h"
#include "lv_drivers/indev/keyboard.h"
#else
#include "lv_drivers/display/v4l2dev.h"
#include "MsgBroker/msg_broker.h"
#include "MemBroker/mem_broker.h"
#include "vmf/video_display_mechanism.h"
#include "comm/video_buf.h"
#include "comm/frame_info.h"
#endif

static uint32_t g_dwMonWidth = 1920;
static uint32_t g_dwMonHeight = 1080;

static uint32_t g_dwPoolSize = 32;

static uint32_t g_dwScrWidth = 480;
static uint32_t g_dwScrHeight = 320;
static uint32_t g_dwScrXPos = 0;
static uint32_t g_dwScrYPos = 0;
static bool g_bIsYuvDevice = true;
static int g_iExampleId = EXAMPLE_ID_DEMO;

#if !defined(PC_SIMULATOR)
static int32_t g_iBtnIdx = -1;
#endif
static int g_iTerminate = false;
lv_point_t btn_positon[4];

static void signal_handler(int);
static void print_usage(const char *);
static void print_msg(const char *, ...);

#if !defined(PC_SIMULATOR)
static int init_video_display(VMF_VDISP_HANDLE_T **);
static void *video_loop(void *);
static bool button_read(lv_indev_drv_t *, lv_indev_data_t *);
static int get_button_index(void);
static void msg_callback(MsgContext *, void *);
#endif

static void select_example(int);
static void *gui_loop(void *);

void signal_handler(int iSigno)
{
	(void) iSigno;
	g_iTerminate = true;
}

void print_usage(const char *exec)
{
		print_msg("Usage:%s [-W <monitor width>][-H <monitor height>][-w <screen width>][-h <screen height>][-x <screen x positon>][-y <screen y positon][-i <(0/1)yuv device>][-m example_id]\n"
#if !defined(PC_SIMULATOR)
			"\t-W\tMonitor width\n"
			"\t-H\tMonitor heigth\n"
#endif
			"\t-w\tScreen width\n"
			"\t-h\tScreen heigth\n"
			"\t-x\tScreen x position\n"
			"\t-y\tScreen y position\n"
#if !defined(PC_SIMULATOR)
			"\t-i\tWhether the moitor receive YUV as input\n"
#endif
			"\t-m\texample_id\n"
			, exec);
}

void print_msg(const char *fmt, ...)
{
	va_list ap;		
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}

void select_example(int iExampleId)
{
	switch(iExampleId){
		case EXAMPLE_ID_DEMO: lv_ex_demo();
				break;
		case EXAMPLE_ID_FONT: lv_ex_font();
				break;
		case EXAMPLE_ID_WIN: lv_ex_win_1();
				break;
		case EXAMPLE_ID_CHART: lv_ex_chart_1();
				break;
		case EXAMPLE_ID_DDLIST: lv_ex_ddlist_1();
				break;
		case EXAMPLE_ID_CALENDAR: lv_ex_calendar_1();
				break;
		case EXAMPLE_ID_ROLLER: lv_ex_roller_1();
				break;
		case EXAMPLE_ID_PRELOAD: lv_ex_preload_1();
				break;
		case EXAMPLE_ID_PAGE: lv_ex_page_1();
				break;
		case EXAMPLE_ID_MBOX: lv_ex_mbox_1();
				break;
		case EXAMPLE_ID_LMETER: lv_ex_lmeter_1();
				break;
		case EXAMPLE_ID_LIST: lv_ex_list_1();
				break;
		case EXAMPLE_ID_LINE: lv_ex_line_1();
				break;
		case EXAMPLE_ID_LED: lv_ex_led_1();
				break;
		case EXAMPLE_ID_LABEL: lv_ex_label_1();
				break;
		case EXAMPLE_ID_KB: lv_ex_kb_1();
				break;
		case EXAMPLE_ID_GAUGE: lv_ex_gauge_1();
				break;
		case EXAMPLE_ID_CONT: lv_ex_cont_1();
				break;
		case EXAMPLE_ID_CB: lv_ex_cb_1();
				break;
		case EXAMPLE_ID_BTNM: lv_ex_btnm_1();
				break;
		case EXAMPLE_ID_BTN: lv_ex_btn_1();
				break;
		case EXAMPLE_ID_BAR: lv_ex_bar_1();
				break;
		case EXAMPLE_ID_ARC: lv_ex_arc_1();
				break;
		case EXAMPLE_ID_SLIDER: lv_ex_slider_1();
				break;
		case EXAMPLE_ID_SPINBOX: lv_ex_spinbox_1();
				break;
		case EXAMPLE_ID_SW: lv_ex_sw_1();
				break;
		case EXAMPLE_ID_TABLE: lv_ex_table_1();
				break;
		case EXAMPLE_ID_TABVIEW: lv_ex_tabview_1();
				break;
		case EXAMPLE_ID_TILEVIEW: lv_ex_tileview_1();
				break;
		case EXAMPLE_ID_COLORPICKER: lv_ex_color_picker_1();
				break;
		case EXAMPLE_ID_CANVAS: lv_ex_canvas_1();
				break;
		case EXAMPLE_ID_TA: lv_ex_ta_1();
				break;
		case EXAMPLE_ID_IMG: lv_ex_img_1();
				break;
		case EXAMPLE_ID_IMGBTN: lv_ex_imgbtn_1();
				break;
		case EXAMPLE_ID_GROUP: lv_ex_group_keyboard_slider_1();
				break;
		case EXAMPLE_ID_IMG_2: lv_ex_img_2();
				break;
		case EXAMPLE_ID_ANIMATION_1: lv_ex_animation_1();
				break;
		case EXAMPLE_ID_IMG_3: lv_ex_img_3();
				break;
		default:
				print_msg("[Fail] Example ID error\n");
				break;
	}
}

#if !defined(PC_SIMULATOR)

int init_video_display(VMF_VDISP_HANDLE_T **pptDispHandle)
{
	VMF_VDISP_INITOPT_T tDisplayOpt;
	memset(&tDisplayOpt, 0, sizeof tDisplayOpt);

	tDisplayOpt.dwInPixFormat = VMF_MAKEFOURCC('Y', 'M', '1', '2');;
	tDisplayOpt.dwMaxInWidth  = VMF_32_ALIGN(g_dwMonWidth);
	tDisplayOpt.dwMaxInHeight = VMF_16_ALIGN(g_dwMonHeight);

	if ((*pptDispHandle = VMF_VDISP_Init(&tDisplayOpt)) == NULL) {
		print_msg("[%s:%d] VMF_VDISP_Init failed!\n", __func__, __LINE__);
		return -1;
	} 

	return 0;
}

void *video_loop(void *pParam)
{
	(void) pParam;
	pthread_t thrd;
	uint32_t dwDispQIdx = 0;
	VMF_VDISP_HANDLE_T *ptDispHandle = NULL;
	uint32_t dwYSize, dwYUVSize;
	VMF_FRAME_BUF_T atDispBuf[VMF_VIDEO_DISPLAY_MIN_QUEUE_SIZE];

	if (init_video_display(&ptDispHandle)) {
		print_msg("[%s:%d] init_video_display failed!\n", __func__, __LINE__);
		goto RELEASE;
	}

	pthread_create(&thrd, NULL, gui_loop, ptDispHandle);
	pthread_setname_np(thrd, "gui_loop");

	dwYSize = VMF_32_ALIGN(g_dwMonWidth) * VMF_16_ALIGN(g_dwMonHeight);
	dwYUVSize = dwYSize * 3 >> 1;
	memset(atDispBuf, 0, sizeof atDispBuf);

	for (int i = 0; i < VMF_VIDEO_DISPLAY_MIN_QUEUE_SIZE; i++) {
		atDispBuf[i].apdwData[0] = MemBroker_GetMemory(dwYUVSize, VMF_ALIGN_TYPE_8_BYTE);
		if (!atDispBuf[i].apdwData[0]) {
			print_msg("[%s] allocate display buffer failed!\n", __func__);
			goto RELEASE;
		}
		atDispBuf[i].apdwData[1] = atDispBuf[i].apdwData[0] + dwYSize;
		atDispBuf[i].apdwData[2] = atDispBuf[i].apdwData[1] + (dwYSize >> 2);
		//Fill YUV buffers on OSD0
		memset(atDispBuf[i].apdwData[0], 0, dwYUVSize);
		MemBroker_CacheFlush(atDispBuf[i].apdwData[0], dwYUVSize);
	}

	while (!g_iTerminate) {
		VMF_VDISP_ProcessOneFrame(ptDispHandle, &atDispBuf[dwDispQIdx], &dwDispQIdx);
	}

RELEASE:
	if (thrd) {
		VMF_VDISP_PIP_Stop(ptDispHandle);
		pthread_join(thrd, NULL);
	}

	if (ptDispHandle)
		VMF_VDISP_Release(ptDispHandle);

	for (int i = 0; i < 3; i++) {
		if (atDispBuf[i].apdwData[0])
			MemBroker_FreeMemory(atDispBuf[i].apdwData[0]);
	}

	return NULL;
}

bool button_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
	(void) drv;
	static int32_t last_btn = 0;
	int curr_btn = get_button_index();
	if (curr_btn >= 0) {
		last_btn = curr_btn; 
		data->state = LV_INDEV_STATE_PR;
	} else {
		data->state = LV_INDEV_STATE_REL;
	}
	data->btn_id = last_btn;
	return false;
}

int get_button_index(void)
{
	return g_iBtnIdx;
}

void msg_callback(MsgContext *msg_context, void *user_data)
{
	(void) user_data;
	print_msg("msg_context->pszHost=%s, msg_context->pszCmd=%s\n", 
			msg_context->pszHost, msg_context->pszCmd);

	if (!strcasecmp(msg_context->pszHost, MODULE_NAME)) {
		if (!strcasecmp(msg_context->pszCmd, MSG_LVGL_BTN)) {
			g_iBtnIdx = *(int32_t *) msg_context->pbyData;
		} else if (!strcasecmp(msg_context->pszCmd, MSG_LVGL_RST)) {
			g_iBtnIdx = -1;
		} else if (!strcasecmp(msg_context->pszCmd, MSG_LVGL_EG)) {
			lv_ex_clean_display();
			select_example(*(int32_t *) msg_context->pbyData);
		}
	}

	if (msg_context->bHasResponse) {
		msg_context->dwDataSize = 0;
	}
}

#endif

void *gui_loop(void *pParam)
{
	void *disp_handle =  NULL;
#if defined(PC_SIMULATOR)
	(void) pParam;
#else
	disp_handle = pParam;
#endif
	uint32_t disp_buf_size;
	lv_color_t *buf;
	lv_disp_buf_t disp_buf;
	lv_disp_drv_t disp_drv;
	lv_indev_drv_t indev_drv;
	lv_indev_t *my_indev = NULL;

	lv_init(g_dwPoolSize);

	lv_scr_setup(disp_handle, g_dwScrWidth, g_dwScrHeight,
		g_dwScrXPos, g_dwScrYPos, g_bIsYuvDevice);

#if defined(PC_SIMULATOR)
	monitor_init();
#else
	v4l2dev_init();
#endif

	disp_buf_size = 10 * lv_scr_get_hor_res();
	buf = malloc(disp_buf_size * sizeof *buf);

	/*Initialize a descriptor for the buffer*/
	lv_disp_buf_init(&disp_buf, buf, NULL, disp_buf_size);

	/*Initialize and register a display driver*/
	lv_disp_drv_init(&disp_drv);
	disp_drv.buffer = &disp_buf;
#if defined(PC_SIMULATOR)
	disp_drv.flush_cb = monitor_flush;
#else
	disp_drv.flush_cb = v4l2dev_flush;
#endif
	lv_disp_drv_register(&disp_drv);
	
	/*Initialize and register a input driver*/
	lv_indev_drv_init(&indev_drv);
#if defined(PC_SIMULATOR)
	indev_drv.type = LV_INDEV_TYPE_POINTER;
	indev_drv.read_cb = mouse_read;
#else
	indev_drv.type = LV_INDEV_TYPE_BUTTON;
	indev_drv.read_cb = button_read;
#endif
	my_indev = lv_indev_drv_register(&indev_drv);
	lv_indev_set_button_points(my_indev, btn_positon);

	/*Draw the menu on screen*/
	select_example(g_iExampleId);

	while (!g_iTerminate) {
		lv_task_handler();
#if defined(PC_SIMULATOR)
		SDL_Delay(5);
#else
		usleep(5000);
#endif
	}

	free(buf);

#if defined(PC_SIMULATOR)
	monitor_release();
#else
	v4l2dev_release();
#endif

	lv_release();

	return NULL;
}

int main(int argc, char **argv)
{
	int ch;
	pthread_t thrd;

	while ((ch = getopt(argc, argv, "W:H:w:h:x:y:p:i:m:h")) != -1) {
		switch(ch) {
		case 'W':
			g_dwMonWidth = atoi(optarg);
			break;
		case 'H':
			g_dwMonHeight = atoi(optarg);
			break;
		case 'w':
			g_dwScrWidth = atoi(optarg);
			break;
		case 'h':
			g_dwScrHeight = atoi(optarg);
			break;
		case 'x':
			g_dwScrXPos = atoi(optarg);
			break;
		case 'y':
			g_dwScrYPos = atoi(optarg);
			break;
		case 'p':
			g_dwPoolSize = atoi(optarg);
			break;
		case 'i':
			g_bIsYuvDevice = atoi(optarg);
			break;
		case 'm':
			g_iExampleId = atoi(optarg);
			break;			
		default:
			print_usage(argv[0]);
			return -1;
		}
	}
	if (g_iExampleId >= EXAMPLE_ID_TOTAL || g_iExampleId < 0) {
		printf("EXAMPLE ID should be 0 ~ %d!\n", EXAMPLE_ID_TOTAL - 1);
		print_usage(argv[0]);
		return -1;
	}

	signal(SIGINT, signal_handler);
	signal(SIGINT, signal_handler);

#if defined(PC_SIMULATOR)
	pthread_create(&thrd, NULL, gui_loop, NULL);
	pthread_setname_np(thrd, "gui_loop");
#else
	pthread_create(&thrd, NULL, video_loop, NULL);
	pthread_setname_np(thrd, "video_loop");

	MsgBroker_RegisterMsg(LVGL_CMD_FIFO);
	MsgBroker_Run(LVGL_CMD_FIFO, msg_callback, NULL, &g_iTerminate);
	MsgBroker_UnRegisterMsg();
#endif

	pthread_join(thrd, NULL);

	return 0;
}
