/**
 * @file v4l2dev.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "v4l2dev.h"
#if USE_V4L2DEV

#include "vmf/video_display_mechanism.h"
#include "vmf/vector_dma.h"
#include "vmf/yuv_rgb_cvt.h"
#include "comm/video_buf.h"
#include "comm/frame_info.h"
#include "comm/vmf_log.h"
#include "MemBroker/mem_broker.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/*********************
 *      DEFINES
 *********************/
#define DUMP 0
#define LVGL_TAG "LVGL"

/**********************
 *      TYPEDEFS
 **********************/
typedef void (*CVT_FUNC)(const lv_area_t *, lv_color_t *);

/**********************
 *      STRUCTURES
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void init_dma_handle(void);
static void setup_video_pip(void);
#if DUMP
static void get_rgb(const char *, uint8_t *);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/
static uint8_t *g_pbyCanvas = NULL;
static uint32_t g_dwCanvasSize;
static uint32_t g_dwCanvasStride;
static uint32_t dwDispIdx = 0;
static VMF_FRAME_BUF_T g_atDispBuf[VMF_VIDEO_DISPLAY_MIN_QUEUE_SIZE];
static uint8_t *g_apbyDispPhysAddr[VMF_VIDEO_DISPLAY_MIN_QUEUE_SIZE];
static VMF_DMA_HANDLE_T *g_ptDmaHandle = NULL;
static VMF_DMA_DESCRIPTOR_T *g_ptDmaDesc = NULL;
static VMF_DMA_ADDR_T g_tDmaAddr;
static VMF_CVT_HANDLE_T *g_ptCvtHandle = NULL;
static CVT_FUNC g_fnCvtCallBack = NULL;
static void default_rgb(const lv_area_t *, lv_color_t *);
static void rgb_2_yuv(const lv_area_t *, lv_color_t *);

/**********************
 * GLOBAL VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/
#define PIXEL_STRIDE 4

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void v4l2dev_init(void)
{
	g_dwCanvasStride = lv_scr_get_hor_res() * PIXEL_STRIDE;
	g_dwCanvasSize = g_dwCanvasStride * lv_scr_get_ver_res();
	g_pbyCanvas = MemBroker_GetMemory(g_dwCanvasSize, VMF_ALIGN_TYPE_8_BYTE);
	if (!g_pbyCanvas) {
		LogE(LVGL_TAG, "Unalbe to allcoate g_pbyCanvas buffer\n");
		goto RELEASE;
	}
	memset(g_pbyCanvas, 0, g_dwCanvasSize);

	setup_video_pip();
	memset(g_atDispBuf, 0, sizeof g_atDispBuf);
	for (int i = 0; i < VMF_VIDEO_DISPLAY_MIN_QUEUE_SIZE; i++) {
		g_atDispBuf[i].apdwData[0] = MemBroker_GetMemory(g_dwCanvasSize, VMF_ALIGN_TYPE_8_BYTE);
		if (!g_atDispBuf[i].apdwData[0]) {
			LogE(LVGL_TAG, "Unalbe to allcoate display buffer\n");
			goto RELEASE;
		}
		memset(g_atDispBuf[i].apdwData[0], 0, g_dwCanvasSize);
		g_apbyDispPhysAddr[i] = MemBroker_GetPhysAddr(g_atDispBuf[i].apdwData[0]);
	}

	init_dma_handle();
	g_tDmaAddr.pbySrcYPhysAddr = MemBroker_GetPhysAddr(g_pbyCanvas);
	g_tDmaAddr.dwTransSize = g_dwCanvasSize;

	LogI(LVGL_TAG, "V4L2 device for LVGL is set up sucessfully\n");
	return;

RELEASE:
	v4l2dev_release();
}

void v4l2dev_release(void)
{
	if (g_pbyCanvas)
		MemBroker_FreeMemory(g_pbyCanvas);

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

	if (g_ptDmaDesc)
		VMF_DMA_Descriptor_Destroy(g_ptDmaDesc);

	if (g_ptDmaHandle)
		VMF_DMA_Release(g_ptDmaHandle);

	if (g_ptCvtHandle)
		VMF_CVT_Release(g_ptCvtHandle);

	LogI(LVGL_TAG, "V4L2 device for LVGL is released sucessfully\n");
}

/**
 * Flush a buffer to the marked area
 * @param drv pointer to driver where this function belongs
 * @param area an area where to copy `color_p`
 * @param color_p an array of pixel to copy to the `area` part of the screen
 */
void v4l2dev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
{
	static bool bIsInit = false;
	static lv_coord_t hor_res = 0, ver_res = 0;
	static VMF_VDISP_HANDLE_T *ptDispHandle = NULL;

	if (!bIsInit) {
		bIsInit = true;
		hor_res = lv_scr_get_hor_res();
		ver_res = lv_scr_get_ver_res();
		ptDispHandle = lv_scr_get_disp_handle();
	}

    if(!g_pbyCanvas
		|| !drv
		|| !area
		|| !color_p
		|| area->x2 < 0
		|| area->y2 < 0
		|| area->x1 > hor_res - 1
		|| area->y1 > ver_res - 1) {
        lv_disp_flush_ready(drv);
        return;
    }

    /*Truncate the area to the screen*/
	g_fnCvtCallBack(area, color_p);
	MemBroker_CacheCopyBack(g_pbyCanvas, g_dwCanvasSize);
	
	g_tDmaAddr.pbyDstYPhysAddr = g_apbyDispPhysAddr[dwDispIdx];
	VMF_DMA_Descriptor_Update_Addr(g_ptDmaDesc, &g_tDmaAddr);
	VMF_DMA_Setup(g_ptDmaHandle, &g_ptDmaDesc, 1);
	VMF_DMA_Process(g_ptDmaHandle);

	VMF_VDISP_PIP_ProcessOneFrame(ptDispHandle, &g_atDispBuf[dwDispIdx], &dwDispIdx);
#if DUMP
	get_rgb("g_pbyCanvas", g_pbyCanvas);
	get_rgb("display", g_atDispBuf[dwDispIdx].apdwData[0]);
#endif
    lv_disp_flush_ready(drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

void init_dma_handle(void)
{
	VMF_DMA_1D_INIT_T tDmaInitOpt;
	memset(&tDmaInitOpt, 0, sizeof tDmaInitOpt);
	tDmaInitOpt.dwFormatFlag = 0;
	g_ptDmaDesc = VMF_DMA_Descriptor_Create(DMA_1D, &tDmaInitOpt);
	g_ptDmaHandle = VMF_DMA_Init(1, 128);
}

void setup_video_pip(void)
{
	VMF_VDISP_PIP_CONFIG_T tPipConfig;
	uint32_t format;

	if (lv_scr_is_yuv_device()) {
		VMF_CVT_INITOPT_T tCvtInitOpt;
		memset(&tCvtInitOpt, 0, sizeof tCvtInitOpt);
		tCvtInitOpt.eSwing = VMF_CVT_STUDIO;
		tCvtInitOpt.eItur = VMF_CVT_BT709;
		tCvtInitOpt.eSample = VMF_CVT_YCbCr444;
		tCvtInitOpt.eCvt = VMF_CVT_RGB_2_YCbCr;
		tCvtInitOpt.dwWidth = 1;
		tCvtInitOpt.dwHeight = 1;
		g_ptCvtHandle = VMF_CVT_Init(&tCvtInitOpt);
		format = VMF_MAKEFOURCC('V', 'U', 'Y', 'A');
		g_fnCvtCallBack = &rgb_2_yuv;
	} else {
		format = VMF_MAKEFOURCC('A', 'R', '2', '4');
		g_fnCvtCallBack = &default_rgb;
	}

	memset(&tPipConfig, 0, sizeof tPipConfig);
	tPipConfig.dwInPixFormat = format;
	tPipConfig.dwWidth  = (uint32_t) lv_scr_get_hor_res();
	tPipConfig.dwHeight = (uint32_t) lv_scr_get_ver_res();
	tPipConfig.dwStride = g_dwCanvasStride;
	tPipConfig.dwStartX = (uint32_t) lv_scr_get_hor_pos();
	tPipConfig.dwStartY = (uint32_t) lv_scr_get_ver_pos();
	VMF_VDISP_PIP_SetInfo(lv_scr_get_disp_handle(), &tPipConfig);
}

#if DUMP
void get_rgb(const char *fmt, uint8_t *data)
{
	static int count = 0;
	int limit = 10;
	FILE *fp = NULL;
	char name[32];

	if (count >= limit)
		return;
	snprintf(name, 32, "%s_%i.rgb", fmt, count);
	fp = fopen(name, "wb");
	fwrite(data, 1, g_dwCanvasSize, fp);
	fflush(fp);
	fclose(fp);

	count++;
}
#endif

void default_rgb(const lv_area_t *area, lv_color_t *color_p)
{
	lv_coord_t w = area->x2 - area->x1 + 1;
	for (lv_coord_t y = area->y1; y <= area->y2; y++) {
		uint32_t dwPos = area->x1 * PIXEL_STRIDE + y * g_dwCanvasStride;
		memcpy(&g_pbyCanvas[dwPos], color_p, w * PIXEL_STRIDE);
		color_p += w;
	}
}

void rgb_2_yuv(const lv_area_t *area, lv_color_t *color_p)
{
	lv_coord_t w = area->x2 - area->x1 + 1;
	VMF_CVT_ResetWidth(g_ptCvtHandle, w);
	for (lv_coord_t y = area->y1; y <= area->y2; y++) {
		uint32_t dwPos = area->x1 * PIXEL_STRIDE + y * g_dwCanvasStride;
		VMF_CVT_BUFFER_T tCvtBuf;
		memset(&tCvtBuf, 0, sizeof tCvtBuf);
		tCvtBuf.apbyInBuf[0] = (uint8_t *) color_p;
		tCvtBuf.apbyOutBuf[0] = &g_pbyCanvas[dwPos];
		VMF_CVT_ProcessOneFrame(g_ptCvtHandle, &tCvtBuf);
		color_p += w;
	}
}

#endif
