/*
 * @history		2019_12_27  [SDK3.0] BMUバッファのサイズとアラインが4096の倍数になるよう修正 (#2188)
 * @history     2020_07_22  [SDK3.1] GVDが占有するメモリ領域を指定するAPIを追加 (#2965)
 */
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>

#include "kernel.h"
#include "com.h"
#include "sample_common.h"
#include "mps_common.h"
#include "mtx_movie.h"
#include "mtx_sample.h"

#include "int/intmgr.h"
#include "bmu/bmumgr.h"
#include "dsp/dspmgr.h"
#include "timr/timrmgr.h"
#include "osp/ospmgr.h"

/* マクロ */
#define	PRINT(FMT, ...)		sys_print(0, FMT,##__VA_ARGS__)

static gvdFatalErrorInfo		gFatal = {0, 0};
mpsOutBufInfo				gOutbufinfo[MTX_MOV_OUTBUF_COUNT];	/* 出力バッファ情報 */
static char					MTX_MOV_STREAM_NAME[100] = "A:\\mtx_mov.mp4";

extern bool					gFlagExit;			/* サンプル終了フラグ */
extern bool					gFlagMPSDone;		/* 動画処理終了フラグ */
extern ER_ID				gWindowTaskID;		/* ウィンドウ制御タスクID */
extern ER_ID				gAsyncTaskID;		/* 非同期処理タスクID */
/* ライブラリハンドラ */
extern AG903_DSPMgrHandle	*MTX_dsp_handle;
extern AG903_TIMRMgrHandle	*MTX_tim_handle;
extern AG903_BMUMgrHandle	*MTX_bmu_handle[2];
extern AG903_OSPMgrHandle	*MTX_osp_handle;

/* 非同期処理タスク
 * これ以外のタスクで余った時間にコールされる
 * デコーダの制御も行うため、コールされる頻度が落ちるとデコード性能に影響します */
extern void async_tsk(VP_INT);
static const T_CTSK ctsk_async = {
	TA_HLNG | TA_ACT | TA_FPU, (VP_INT)0, (FP)async_tsk, 8, 0x800, 0, "Async"
};


/* メモリ確保関連 */
static int32_t cbMPSDecodeDone(void *param_gvd, void *param_user, gvdError error);
static int32_t cbMPSFatalerror(void *param_gvd, void *param_user, gvdError error);

int32_t movie_init(void)
{
	int32_t err;
	int32_t cnt;

	size_t work_size;
	void *work_addr;

	mpsSetting mpssetting;
	mpsConfig mpsconfig;
	mpsContext *mpsctx;

	/* MPSの初期化 */
	mpssetting.max_stream = MTX_MOV_STREAM_COUNT;
	mpssetting.stream_buffer_size = MTX_MOV_INBUF_TOTAL;
	mpssetting.stream_buffer_num = MTX_MOV_INBUF_COUNT;
	mpssetting.func_decode_done_handler =
		(int32_t (*)(void *, void *, gvdError))cbMPSDecodeDone;
	mpssetting.func_fatalerror_handler =
		(int32_t (*)(void *, void *, gvdError))cbMPSFatalerror;
	err = MPS_Initialize(mpssetting);
	if(err)
	{
		MTX_ErrPrintf("ERROR: MPS_Initialize() : %d\n", err);
		return err;
	}

	ERRCHK(GVD_ERR_SUCCESS, GVD_SetFatalErrorAddress((void *)0xFE007000));
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetOccupyMemoryAddress((void *)0x83FF0000));

	/* GVD用ワークメモリの確保 */
	work_size = MPS_GetRequiredWorkMemorySize();
	MTX_Printf("[Memory] GVD requested %u [Byte]\n", work_size);

	/* GVD用ワークメモリは32KB以上のアラインが必須 */
	work_addr = sys_memalign(work_size, 32*1024);
	if(work_addr == 0)
	{
		MTX_ErrPrintf("ERROR: [GVD WorkBuffer] allocate faild.\r\n");
		return -1;
	}

	mpsconfig.num_out_buffer = MTX_MOV_OUTBUF_COUNT;
	mpsconfig.memory_work_top = work_addr;
	mpsconfig.memory_work_size = work_size;

	/* GVD用ヒープメモリは2KB以上のアラインが必須 */
	mpsconfig.memory_heap_top = sys_memalign(MTX_MOV_HEAP_SIZE, 2048);
	if(mpsconfig.memory_heap_top == NULL)
	{
		MTX_ErrPrintf("ERROR: Heap memory alloc failed.\r\n");
		return -AG903_ENOMEM;
	}
	mpsconfig.memory_heap_size = MTX_MOV_HEAP_SIZE;

	/* MP4コンテナの解析
	 * 以降コンテナ情報が使える */
	err = MPS_Parse(0, MTX_MOV_STREAM_NAME);
	if (err != AG903_ENONE)
	{
		MTX_ErrPrintf("ERROR: MP4 Parse failed\n");
		return err;
	}

	/* 出力バッファの確保 */
	{
		uint32_t stream_stride;
		mpsctx = MPS_getContext(0);
		if(mpsctx->info.width == 0 || mpsctx->info.height == 0)
		{
			mpsctx->info.width = MTX_MOV_STREAM_WIDTH;
			mpsctx->info.height = MTX_MOV_STREAM_HEIGHT;
			mpsctx->info.frame_length = 1;
			mpsctx->info.time_scale = 30;
		}

		stream_stride = MTX_ALIGNn(64, mpsctx->info.width)
		              * MTX_ALIGNn(32, mpsctx->info.height)
		              * 2;
		/* 一括バッファ確保時に各バッファがアドレスアライン制約を満たすための調整 */
		stream_stride = MTX_ALIGNn(4096, stream_stride);

		/* BMUで使用する分をまとめて確保 */
		gOutbufinfo[0].size_buffer =
			stream_stride * MTX_MOV_OUTBUF_COUNT;
		gOutbufinfo[0].buffer =
			sys_memalign(gOutbufinfo[0].size_buffer, 4096);
		if(gOutbufinfo[0].buffer == NULL)
		{
			MTX_ErrPrintf("ERROR: [BMU buffer area] allocate faild.\r\n");
			return -AG903_ENOMEM;
		}
		mpsconfig.output_select = GVD_OUTMOD_BMU;

		/* フレームバッファの初期化 */
		uint32_t *p = (uint32_t *)gOutbufinfo[0].buffer;
		for(cnt=0; cnt<gOutbufinfo[0].size_buffer>>2; cnt++)
		{
			*(p + cnt) = 0x00000000;
		}
	}

	/* GVDの初期化 */
	err = MPS_Start(mpsconfig);
	if(err)
	{
		MTX_ErrPrintf("ERROR: MPS_Start() : %d\n", err);
		return err;
	}

	/* 入出力バッファ数を設定 */
	mpsctx = MPS_getContext(0);
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetMaxNumInputBuffers(mpsctx->gvdctx, MTX_MOV_INBUF_COUNT));
	ERRCHK(GVD_ERR_SUCCESS, GVD_SetMaxNumOutputBuffers(mpsctx->gvdctx, MTX_MOV_OUTBUF_COUNT));

	return 0;
}


int32_t movie_play(void)
{
	int cnt;
	int32_t err;
	mpsContext *ctx = NULL;

	ctx = MPS_getContext(0);
	gFlagMPSDone = false;

	/* 非同期処理開始 */
	gAsyncTaskID = acre_tsk((T_CTSK*)&ctsk_async);

	/* 動画ストリームのオープン */
	/* ParseしていなければMPS_Open内でParseされる
	 * Parse済みであればコンテナ情報をそのまま使用する */
	err = MPS_Open(0, MTX_MOV_STREAM_NAME);
	if (err != AG903_ENONE)
	{
		MTX_ErrPrintf("ERROR: Movie file open failed\n");
		return err;
	}

	/* 再生 */
	MTX_Printf("Play #%d\n", 0);
	MPS_Play(0);

	volatile mpsAsyncFlag *f;
	f = &ctx->flag;

	while(1)
	{
		if(gFlagExit == false)
		{
			if(f->bDecDone && !f->bSought)
			{
				/* ループ時に先頭フレームへのシークが必要.
				 * 初回のみMPS_Open内で自動的に先頭フレームへシークされる */
				MPS_Seek(0, 0, MPS_SEEK_FRAME);
				MPS_Play(0);
			}
		}
		else
		{
			MPS_Pause(0);
			break;
		}
		sys_dlytsk(33); /* フラグチェック周期 */
	}

	/* 動画ストリームのクローズ */
	MPS_Close(0);

	/* 出力バッファの解放 */
	for(cnt=0; cnt<MTX_MOV_OUTBUF_COUNT; cnt++) {
		if(gOutbufinfo[cnt].buffer != NULL) {
			sys_free(gOutbufinfo[cnt].buffer);
			gOutbufinfo[cnt].buffer = NULL;
		}
	}

	gFlagMPSDone = true; /* 再生処理終了を通知 */
	return 0;
}


/* 表示の更新 */
void WindowUpdate(void)
{
	AG903_OSPMgrClearFIFO(MTX_osp_handle);
	AG903_OSPMgrSetFIFO(MTX_osp_handle, 5);
	AG903_OSPMgrEnable(MTX_osp_handle);
}

/*----------------------------
  コールバック
  ----------------------------*/

/* GVDのcommit_output_bufferのコールバック */
static int32_t cbMPSDecodeDone(void *param_gvd, void *param_user, gvdError error)
{
	((void)(error));
	((void)(param_gvd));
	((void)(param_user));
	return 0;
}

/* GVDの致命的エラーのコールバック */
static int32_t cbMPSFatalerror(void *param_gvd, void *param_user, gvdError error)
{
	((void)param_user);
	((void)error);
	gFatal = *(gvdFatalErrorInfo *)param_gvd;
	MTX_ErrPrintf("ERROR: Fatal error detected. cont=%hhu, cause=%hhu\n",
			   gFatal.context_id, gFatal.cause);
	return 0;
}

