/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017-2021 AXELL CORPORATION, all rights reserved.
 */
/*
 * @history 2019_12_27  [SDK3.0] メモリ属性変更目的で生成したヒープが解放できない不具合を修正 (#2759)
 * @history 2021_06_30  [SDK3.3] OpenVGサンプルでBMUとGFXの連携に関するAPIの引数と解説コメントを修正 (#3528)
 */
#include "kernel.h"
#include "ffsys.h"
#include "gmalloc.h"
#include "gfx_sample.h"
#include "sample_common.h"
#include "vram/vrammgr.h"
#include "bmu/bmumgr.h"
#include "bmugfx.h"
#include "com.h"
#include "dsp.h"

#include "openvg_port_uc3.h"

/* プロトタイプ */
static int32_t Gfx_init_module(void);
static int32_t Gfx_term_module(void);
static int32_t Gfx_init_module_bmu(void);
static int32_t Gfx_term_module_bmu(void);
static int32_t Gfx_gmeminit(uint32_t mplsz, void *mpl);
static void Gfx_gmemfinal(void);
static void Gfx_SampleHelp(void);
static void BmuGfxMainPrepare(void);
void *memset(void *s, int c, size_t n);

static void (*func[])() = {
	Gfx_SampleHelp,
	ClearMain,
	PathMain,
	StrokeMain,
	ImageMain,
	MatrixMain,
	PaintMain,
	ScissorMain,
	MaskMain,
	ColorTransformMain,
	BlendMain,
	Gfx_SampleHelp,
	AntialiasingMain,
	ImageFilterMain,
	ExtImageMain,
	DirectImageMain,
	FastPathMain,
	ConvolutionMain,
	CopyRectMain,
	MultifuncMain,
	BmuGfxMainPrepare,
};

static uintptr_t openvg_heap_base;
static int       openvg_heap_size;
static int32_t gMemid   = 0;
static void *  gMembase = NULL;

static VXDisplay				*vx_display;
static VXWindow					vx_window;
static EGLDisplay				egl_display;
static EGLConfig				egl_config;
static EGLContext				egl_context;
static EGLSurface				egl_surface;

static EGLint config_attr[] =
{
	EGL_CONFIG_ID, 2,
	EGL_NONE
};

static EGLint surface_attr[] =
{
	EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
	EGL_ALPHA_FORMAT, EGL_ALPHA_FORMAT_NONPRE,
	EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
	EGL_NONE
};

void *gfx_bg_adr = NULL;
void *gfx_gb_adr = NULL;
uint32_t gfx_bg_srcadr = 0x00000000;
uint32_t gfx_bg_snkadr = 0x00000000;
uint32_t gfx_gb_srcadr = 0x00000000;
uint32_t gfx_gb_snkadr = 0x00000000;
static struct {
	AG903_BMUMgrHandle *hnd;
} bg;

static struct {
	AG903_BMUMgrHandle *hnd;
} gb;

/**
 * @brief		メイン処理
 * @param[in]	param	制御パラメータ
 * @return		なし
 * @note		なし
 */
void GFX_main(uint8_t param)
{
	uint8_t		mode = 0;
	char		input[2+2];

	((void)param);

	Gfx_SampleHelp();	/* Help表示 */

	Gfx_init_module();

	while (mode != 0xFF) {
		EPRINT(" -- Choose sample number (00h<HELP> to FFh): ");
		COM_GetNum_Wait(input, sizeof(input));
		mode = (ASCtoBIN(input[0]) << 4)
			 | (ASCtoBIN(input[1]) << 0);
		if(0xFF == mode) {
			break;
		}
		if(mode >= (sizeof(func)/sizeof(void*))) {
			func[0](); /* Help */
		}
		else {
			func[mode]();	/* Sample func実行 */
		}
	}

	Gfx_term_module();

	return;
}

/**
 * @brief		サブ処理
 * @param[in]	param	制御パラメータ
 * @return		なし
 * @note		なし
 */
void GFX_sub(uint32_t param)
{
	((void)param);
	/* 処理なし */
	return;
}

/**
 * @brief		モジュール初期化
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Gfx_init_module(void)
{
	EGLint		num_config;
	uint32_t	current;

	current = AG903_SAMPLE_VRAM_ADDR;
	// sys_memを後でキャッシュ不可能に戻す場合はアプリが整合性を保障する
	openvg_heap_base = current;
	openvg_heap_size = GFX_SYSMEM_SIZE;
	sys_meminit(openvg_heap_size, (void *)openvg_heap_base, SYSMEM_NORMAL_CACHE_ON);

	current += GFX_SYSMEM_SIZE;
	Gfx_gmeminit(GFX_GMEM_SIZE, (void*)current);
	gmInitialize(M2GM(gMembase), GFX_GMEM_SIZE);

	openvg_port_uc3_init();

	eglBindAPI(EGL_OPENVG_API);

	vx_display = VXOpenDisplay(0);
	vx_window = VXCreateWindow(vx_display, 0, 0, GFX_WIDTH, GFX_HEIGHT, 16);
	egl_display = eglGetDisplay(vx_display);
	eglInitialize(egl_display, NULL, NULL);
	eglChooseConfig(egl_display, config_attr, &egl_config, 1, &num_config);
	egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, NULL);
	egl_surface = eglCreateWindowSurface(egl_display, egl_config, vx_window, surface_attr);
	eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);

	Board_SelectDviTX();

	dsp_init();

	EGLint addr;
	eglQuerySurface(egl_display, egl_surface, EGL_FRONT_BUFFER_ADDRESS_EXT, &addr);
	memset((void*)GM2M(addr), 0, GFX_WIDTH*GFX_HEIGHT*16/8);
	dsp_set_fbase(GM2M(addr));

	return AG903_ENONE;
}

/**
 * @brief		モジュール終了
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Gfx_term_module(void)
{
	eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglDestroySurface(egl_display, egl_surface);
	eglDestroyContext(egl_display, egl_context);
	eglTerminate(egl_display);
	VXDestroyWindow(egl_display, vx_window);
	VXCloseDisplay(vx_display);
	openvg_port_uc3_term();
	gmTerminate();
	Gfx_gmemfinal();
	/* いちど返却した後、キャッシュ不可に戻すためにもう一度 */
	sys_memfinal();
	sys_meminit(GFX_SYSMEM_SIZE, (void *)AG903_SAMPLE_VRAM_ADDR, SYSMEM_NORMAL_CACHE_OFF);
	sys_memfinal();
	dsp_term();

	return AG903_ENONE;
}

/**
 * @brief		モジュール初期化
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Gfx_init_module_bmu(void)
{
	EGLint		num_config;
	uint32_t	current;
	void       *gmptr;
	
	current = AG903_SAMPLE_VRAM_ADDR;
	// sys_memを後でキャッシュ不可能に戻す場合はアプリが整合性を保障する
	openvg_heap_base = current;
	openvg_heap_size = GFX_SYSMEM_SIZE;
	sys_meminit(openvg_heap_size, (void *)openvg_heap_base, SYSMEM_NORMAL_CACHE_ON);

	current += GFX_SYSMEM_SIZE;
	Gfx_gmeminit(GFX_GMEM_SIZE, (void*)current);
	gmInitialize(M2GM(gMembase), GFX_GMEM_SIZE);

	// バッファ管理するキャプチャ用バッファを確保します
	gmptr = gmMalloc(BG_SIZE*BG_FRAMES, 4096);
	if (gmptr == (void *)GM_BAD_ADDR) {
		GFX_ERROR("Not enough memory");
		return -AG903_ENOMEM;
	}
	gfx_bg_adr = (void *)GM2M(gmptr);

	// バッファ管理する表示用バッファを確保します
	gmptr = gmMalloc(GB_SIZE*GB_FRAMES, 4096);
	if (gmptr == (void *)GM_BAD_ADDR) {
		GFX_ERROR("Not enough memory");
		gmFree((void *)M2GM(gfx_bg_adr));
		return -AG903_ENOMEM;
	}
	gfx_gb_adr = (void *)GM2M(gmptr);

	// PGP-GFX間BMUの初期化(川上側)
	AG903_BMUMgrGetHandle(&bg.hnd);
	AG903_BMUMgrSetMode(bg.hnd, AG903_BMU_SINK_WAIT_DISABLE, AG903_BMU_BUF_MGR_MODE2);
	AG903_BMUMgrSetSrcModule(bg.hnd, AG903_BMU_SRC_PGP0);   // PGP0-BMU
	AG903_BMUMgrAddSinkModule(bg.hnd, AG903_BMU_SINK_GFX0);	//      BMU-GFX0
	AG903_BMUMgrSetBufferConfig(bg.hnd, gfx_bg_adr, BG_SIZE, BG_FRAMES);
	AG903_BMUMgrEnable(bg.hnd);
	// バッファ管理されるアドレスの取得
	AG903_BMUMgrGetBMUSrcAddress(bg.hnd, &gfx_bg_srcadr);
	AG903_BMUMgrGetBMUSinkAddress(bg.hnd, &gfx_bg_snkadr);

	// GFX-DSP間BMUの初期化(川下側)
	AG903_BMUMgrGetHandle(&gb.hnd);
	AG903_BMUMgrSetMode(gb.hnd, AG903_BMU_SINK_WAIT_DISABLE, AG903_BMU_BUF_MGR_MODE1);
	AG903_BMUMgrSetSrcModule(gb.hnd, AG903_BMU_SRC_GFX0);   // GFX0-BMU
	AG903_BMUMgrAddSinkModule(gb.hnd, AG903_BMU_SINK_DSP0); //      BMU-DSP0
	AG903_BMUMgrSetBufferConfig(gb.hnd, gfx_gb_adr, GB_SIZE, GB_FRAMES);
	AG903_BMUMgrEnable(gb.hnd);
	// バッファ管理されるアドレスの取得
	AG903_BMUMgrGetBMUSrcAddress(gb.hnd, &gfx_gb_srcadr);
	AG903_BMUMgrGetBMUSinkAddress(gb.hnd, &gfx_gb_snkadr);

	openvg_port_uc3_init();

	eglBindAPI(EGL_OPENVG_API);

	vx_display = VXOpenDisplay(0);
	vx_window = VXCreateWindow(vx_display, 0, 0, GFX_WIDTH, GFX_HEIGHT, 16);

	egl_display = eglGetDisplay(vx_display);
	eglInitialize(egl_display, NULL, NULL);
	eglChooseConfig(egl_display, config_attr, &egl_config, 1, &num_config);
	egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, NULL);
	EGLint surface_attr_bmu[] =
		{
			EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER, // BMUでフリップするためSINGLE。ただし枚数分のサイズ。
			EGL_COLOR_BUFFER0_ADDRESS_EXT, gfx_gb_srcadr,
			EGL_ALPHA_FORMAT, EGL_ALPHA_FORMAT_NONPRE,
			EGL_COLORSPACE, EGL_COLORSPACE_sRGB,
			EGL_NONE
		};
	egl_surface = eglCreateWindowSurface(egl_display, egl_config, vx_window, surface_attr_bmu);
	eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);

	Board_SelectDviTX();

	dsp_init_bmu();

	return AG903_ENONE;
}

/**
 * @brief		モジュール終了
 * @param[in]	なし
 * @retval		AG903_ENONE		正常終了
 * @note		なし
 */
static int32_t Gfx_term_module_bmu(void)
{
	eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglDestroySurface(egl_display, egl_surface);
	eglDestroyContext(egl_display, egl_context);
	eglTerminate(egl_display);
	VXDestroyWindow(egl_display, vx_window);
	VXCloseDisplay(vx_display);
	openvg_port_uc3_term();
	AG903_BMUMgrDisable(gb.hnd);
	AG903_BMUMgrDisable(bg.hnd);
	gmFree((void *)M2GM(gfx_gb_adr));
	gmFree((void *)M2GM(gfx_bg_adr));
	gmTerminate();
	Gfx_gmemfinal();
	/* いちど返却した後、キャッシュ不可に戻すためにもう一度 */
	sys_memfinal();
	sys_meminit(GFX_SYSMEM_SIZE, (void *)AG903_SAMPLE_VRAM_ADDR, SYSMEM_NORMAL_CACHE_OFF);
	sys_memfinal();
	dsp_term_bmu();

	return AG903_ENONE;
}

/**
 * @brief		ヘルプ表示
 * @param		なし
 * @return		なし
 * @note		なし
 */
static void Gfx_SampleHelp(void)
{
	PRINT("\t# 00 ... Help");
	PRINT("\t# 01 ... Clear");
	PRINT("\t# 02 ... Path");
	PRINT("\t# 03 ... Stroke");
	PRINT("\t# 04 ... Image");
	PRINT("\t# 05 ... Matrix");
	PRINT("\t# 06 ... Paint");
	PRINT("\t# 07 ... Scissor");
	PRINT("\t# 08 ... Mask");
	PRINT("\t# 09 ... ColorTransform");
	PRINT("\t# 0A ... Blend");
	PRINT("\t# 0C ... Antialiasing");
	PRINT("\t# 0D ... ImageFilter");
	PRINT("\t# 0E ... ExtImage");
	PRINT("\t# 0F ... DirectImage");
	PRINT("\t# 10 ... FastPath");
	PRINT("\t# 11 ... Convolution");
	PRINT("\t# 12 ... CopyRect");
	PRINT("\t# 13 ... Multifunc(not return)");
	PRINT("\t# 14 ... BMU");
	PRINT("\t# FF ... Exit Sample");
	
	return;
}

/**
 * @brief		表示するサーフェースの表裏をフリップします
 * @param		なし
 * @return		なし
 * @note		
 */
void GfxSwapBuffer(void)
{
	EGLint addr;

	vgFinish();
	eglSwapBuffers(egl_display, egl_surface);
	eglQuerySurface(egl_display, egl_surface, EGL_FRONT_BUFFER_ADDRESS_EXT, &addr);
	dsp_set_fbase(GM2M(addr));
}

void GfxSwapBuffer_bmu(void)
{
	EGLint addr;

	vgFinish();
	eglSwapBuffers(egl_display, egl_surface);
	eglQuerySurface(egl_display, egl_surface, EGL_FRONT_BUFFER_ADDRESS_EXT, &addr);
	PRINT("%x\n", addr);
}

void GfxWriteMemory(void *dst, void *src, size_t size)
{
	uint32_t *dst32 = (uint32_t *)dst;
	uint32_t *src32 = (uint32_t *)src;
	uint32_t ii;
	
	for (ii = 0; ii < (size>>2); ii++) {
		*dst32++ = *src32++;
	}
}

void *GfxGetFreeArea(int width, int height, int bpp)
{
	void *gmptr = gmMalloc(((width * bpp + 7) / 8) * height, 32);
	if (gmptr == (void *)GM_BAD_ADDR) {
		GFX_ERROR("Not enough memory");
		return NULL;
	} else {
		return (void *)GM2M(gmptr);
	}
}

void GfxReleaseFreeArea(void *addr)
{
	if (addr != NULL) {
		gmFree((void*)M2GM(addr));
	}
}

void GfxFsRead(int8_t *filename, uint32_t size, void *buf)
{
	FILE *fp;
	ER err = E_OK;
	
	err = mountp('A', 0, Fsif_GetDevid(FSIF_DEV_SD));
	if (err == E_OK) {
		fp = fopen((const char *)filename, "rb");
		if (fp != NULL) {
			err = E_OK;
			fread(buf, 1, size, fp);
			fclose(fp);
		} else {
			err = E_SYS;
		}
		umountp(0, Fsif_GetDevid(FSIF_DEV_SD));
	}

	if (err != E_OK) {
		GFX_ERROR(filename);
		sys_memset(buf, 0xff, size); // clear insted of file image
	}
}

static int32_t Gfx_gmeminit(uint32_t mplsz, void *mpl)
{
	int32_t		rc = AG903_ENONE;
	AG903_VRAMMgrMplPrm	mplprm;

	if(0 != gMemid) {
		return -AG903_EFAULT;	/* 初期化済み */
	}

	mplprm.memtype = AG903_VRAM_STRONGLY_ORDERED;
	mplprm.mplatr  = AG903_VRAM_ATRFIFO;
	mplprm.mplsz   = mplsz;
	mplprm.mpl     = mpl;

	gMemid = AG903_VRAMMgrCreateMpl(&mplprm);

	if (gMemid <= 0) {
		gMemid = 0;
		rc = -AG903_ENOMEM;
		GFX_ERROR("Cannot allocate VRAM pool");
	}

	/* ヒープ全体を確保することはできないため若干小さくしその余裕でアラインも多めにとる */
	gMembase = AG903_VRAMMgrMallocAlign(gMemid, 4096, mplsz - 2*4096);
	if (gMembase == NULL) {
		EPRINT("Err: Not enough gmem");
		AG903_VRAMMgrDeleteMpl(gMemid);
		gMemid = 0;
		gMembase = NULL;
		return -AG903_ENOMEM;
	}
		
	return rc;
}

static void Gfx_gmemfinal(void)
{
	if (gMemid > 0) {
		AG903_VRAMMgrFree(gMemid, gMembase);
		AG903_VRAMMgrDeleteMpl(gMemid);
		gMemid = 0;
		gMembase = NULL;
	}
	return;
}

static void BmuGfxMainPrepare(void)
{
	Gfx_term_module();
	Gfx_init_module_bmu();	
	BmuGfxMain();
	Gfx_term_module_bmu();	
	Gfx_init_module();
}
