/**
 * @brief Movie Player Sample ファイルインタフェース
 * @file mps_fileio.c
 * @author AXELL
 * @history 17_02_22  初版
 * @history 2017_10_26  Ver2.0
 * @history 2019_03_08  [SDK2.2] MPSでのGVDサーチレンジの設定によってデコーダが終了しなくなる不具合等を修正 (#2449)
 * @history 2020_07_22  [SDK3.1] MPSでのストリーム終端の取り扱いを変更 (#3035)
 * @description GVD,MPSのファイルインタフェース定義
 * @note エラーコードはAG903_error.hを参照のこと.
 */

/*
 * This program was created by AXELL CORPORATION.
 * Copyright (C) 2017-2020 AXELL CORPORATION, all rights reserved.
 */

#include "mps/mps_fileio.h"
#include <ffsys.h>
#if defined(__ICCARM__)
#include "../os/iTRON/uC3/Kernel/inc/kernel.h"
#elif defined(__GNUC__)
#include "../os/iTRON/uC3_exeGCC/Kernel/inc/kernel.h"
#endif

/**
 *	@brief ストリーム末尾付加フラグ
 *	@note 配列の要素はコンテキストIDとして管理されます.
 */
static volatile bool gFlagTrail[16] = {false};

/**
 *	 1 : Annex B Format<p>
 *	 0 : NAL File Format<p>
 *	-1 : Unknown
 * @brief ストリーム形式フラグ
*/
static volatile int32_t gFlagAnnexB[16] = {
	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
};

/**
 * @brief ファイルを開く
 * @param filename [in] ファイル名
 * @param mode [in] オプション
 * @return NULL オープン失敗
 * @return それ以外 ファイルポインタ
 */
int32_t *mps_fopen(const char *filename, const char *mode)
{
	FILE *fp;
	fp = fopen(filename, mode);
	if (fp == NULL) {
	    return 0;
	}
	return (int32_t *)fp;
}

/**
 * @brief ファイルを閉じる
 * @param stream [in]ファイルディスクリプタ
 * @return エラーコード
 */
int32_t mps_fclose(int32_t *stream)
{
	return (int32_t)fclose((FILE *)stream);
}

/**
 * @brief ファイル読み込み
 * @param ptr [out]データ出力先
 * @param size [in]1回あたりの読み込みサイズ
 * @param nmenb [in]読み込み回数
 * @param stream [in]ファイルディスクリプタ
 * @return 読み込んだデータのサイズ
 */
size_t mps_fread(void *ptr, size_t size, size_t nmemb, int32_t *stream)
{
	return fread(ptr, size, nmemb, (FILE *)stream);
}

/**
 * @brief ファイル書き込み
 * @param ptr [in]データ入力元
 * @param size [in]1回あたりの書き出しサイズ
 * @param nmenb [in]書き出し回数
 * @param stream [in]ファイルディスクリプタ
 * @return 書き込んだデータのサイズ
 */
size_t mps_fwrite(const void *ptr, size_t size, size_t nmemb, int32_t *stream)
{
	return fwrite(ptr, size, nmemb, (FILE *)stream);
}

/**
 * @brief ファイルエラー取得
 * @return エラーコード
 */
int32_t mps_ferror()
{
	return (int32_t)ffs_error();
}

/**
 * @brief ファイルシーク
 * @param fp [in]ファイルディスクリプタ
 * @param offset [in]シーク先オフセット
 * @param from [in]シークオフセット基準
 * @return エラーコード
 */
int32_t mps_fseek(int32_t *stream, long offset, int32_t from)
{
	return (int32_t)fseek((FILE *)stream, offset, from);
}

/**
 * @brief GVD-ファイル構造体初期化インタフェース
 * @param gvdfile [out]GVDファイル構造体
 * @return エラーコード
 */
gvdError gvdFileInitialize(gvdFile gvdfile)
{
	gvdfile->error = 0;
	gvdfile->fd = NULL;
	gvdfile->pos = 0;
	gvdfile->size = 0;
	return GVD_ERR_SUCCESS;
}

/**
 * @brief GVD-ファイルオープンインタフェース
 * @param gvdfile [in]GVDファイル構造体
 * @param filename [in]ファイル名
 * @return エラーコード
 */
gvdError gvdFileOpen(gvdFile gvdfile, const char *filename)
{
	mpsContext *ctx;
	gvdInitializeParam *GVDParam = MPS_getGVDInitParam();

	gvdfile->fd = mps_fopen(filename, "rb");
	if(gvdfile->fd == 0)
	    return GVD_ERR_FILE_OPERATION_FAILURE;

	int32_t id;
	uint32_t offset;

	for(id=0; id<(int32_t)GVDParam->stream_num; id++)
	{
	    ctx = MPS_getContext(id);
#ifdef MPS_USE_STDLIB
	    if(0 == strcmp(filename, ctx->filename))
			break;
#else
	    {
			bool bFound = false;
			int32_t i;
			for(i=0; filename[i] == ctx->filename[i]; i++)
			{
				if(filename[i] == '\0')
				{
					bFound = true;
					break;
				}
			}
			if(bFound) break;
	    }
#endif
	}

	if((int32_t)GVDParam->stream_num <= id)
	{
	    MPS_ErrPrintf("Error: Filename is not match.\r\n");
	    return GVD_ERR_BAD_PARAMETER;
	}
	else
	{
	    gvdfile->id = id;
		ctx->fp = gvdfile->fd;
	}

	{
	    ctx = MPS_getContext(id);
	    if(ctx->info.stream_size > 0)
	    {
	    	/* MP4 : ストリームの末尾までをファイルサイズとして登録 */
	    	mpsInfoPos *pos = MPS_getInfoPos(gvdfile->id);
			gvdfile->size = pos->mdat + ctx->info.stream_size;
	    }
	    else
	    {
	    	/* H.264 RAW : ファイル末尾をファイルサイズとして登録 */
			struct stat stbuf;
			if (fstat((FILE *)(gvdfile->fd), &stbuf) != 0) {
				mps_fclose(gvdfile->fd);
				gvdfile->fd = NULL;
				return GVD_ERR_FILE_OPERATION_FAILURE;
			}
			gvdfile->size = stbuf.st_size;
            ctx->info.stream_size = gvdfile->size;
	    }

		gvdfile->nal_next = 0;
	    gvdfile->nal_prev = 0;
	    gvdfile->nal_size = 0;
	}

	/* ストリーム末尾付加フラグをクリア */
	gFlagTrail[gvdfile->id] = false;

	offset = MPS_GetSeekPositionFromFrameID(id, 0, NULL);
	if(gvdFileSeekAbs(gvdfile, offset) != 0)
	{
	    MPS_ErrPrintf("ERROR: gvdFileSeekAbs()\n");
	}

	gvdfile->pos = offset;

	return GVD_ERR_SUCCESS;
}

/**
 * 開いているストリームがMP4ファイルで、最初のIDRの前にSPSが無い場合、
 * ストリームの前にSPS+PPS NALのみを返します.
 * @brief GVD-ファイル読み込みインタフェース
 * @param gvdfile [in]GVDファイル構造体
 * @param buf [out]データ出力先
 * @param rdsize [in]読み込みサイズ
 * @param actual_rdsize [out]読み込めたサイズ
 * @return エラーコード
 */
gvdError gvdFileRead(gvdFile gvdfile, void *buf, size_t rdsize, size_t *actual_rdsize)
{
	size_t res = 0;
	uint32_t pos_top, pos_end;
	uint8_t *addr = buf;

	if(rdsize > 0)
	{
	    mpsContext *ctx = MPS_getContext(gvdfile->id);
	    mpsInfoPos *pos = MPS_getInfoPos(gvdfile->id);
	    uint32_t stream_end = ctx->info.stream_size + pos->mdat;
		int32_t remain = stream_end - gvdfile->pos;
		if(stream_end == 0) {
			return GVD_ERR_NOT_SUPPORTED;
		}

		/* ストリーム末尾か？ */
		if(gvdfile->pos >= (int32_t)stream_end)
		{
			if(!gFlagTrail[gvdfile->id])
			{

				/* ストリーム末尾を変更：0x00をMPS_STREAM_TRAILING_BYTE数分挿入 */
				int j;
					uint8_t *mem = buf;
				for(j=0; j<MPS_STREAM_TRAILING_BYTE; j++) {
					mem[j] = 0x00;
				}

				rdsize = MPS_STREAM_TRAILING_BYTE;
				gvdfile->pos += MPS_STREAM_TRAILING_BYTE;
				if(actual_rdsize != NULL) { *actual_rdsize = (size_t)MPS_STREAM_TRAILING_BYTE; }

				/* ストリーム末尾付加フラグをセット */
				gFlagTrail[gvdfile->id] = true;
			}
			else
			{
				if(actual_rdsize != NULL) {
					/* 末尾を超えて要求：致命的エラー */
					*actual_rdsize = (size_t)0;
				}
			}

			return GVD_ERR_SUCCESS;
		}
		else if(remain < (int32_t)rdsize)
		{
			rdsize = stream_end - gvdfile->pos;
		}

	    if(gvdfile->nal_size == 0) /* IDR直前 */
	    {
			/* 先頭8バイトだけリード */
			res = mps_fread(buf, 1, 8, gvdfile->fd);
			fseek((FILE *)gvdfile->fd, -8, SEEK_CUR);

			/* フォーマット判定 */
			if(gFlagAnnexB[gvdfile->id] == -1)
			{
				if(0x00000001 == (addr[0]<<24|addr[1]<<16|addr[2]<<8|addr[3]) ||
				   0x000001 == (addr[0]<<16|addr[1]<<8|addr[2]))
				{
					gFlagAnnexB[gvdfile->id] = 1;
					gvdfile->nal_size = -1;
				}
				else
					gFlagAnnexB[gvdfile->id] = 0;
			}
			else if(gFlagAnnexB[gvdfile->id] == 1)
				gvdfile->nal_size = -1;

			if(7 != (addr[4] & 0x1F)) /* 先頭のNALがSPSでない */
			{
				mpsAVCHeader *AVCHeader = MPS_getAVCHeader(gvdfile->id);
				if(AVCHeader->data == NULL)
					return GVD_ERR_NOT_SUPPORTED;

				/* SPS, PPSをコピー */
#ifdef MPS_USE_STDLIB
				memcpy(buf, AVCHeader->data, AVCHeader->size);
#else
				{
					int32_t i;
					for(i=0; i<(int32_t)AVCHeader->size; i++)
						*((uint8_t *)buf+i) = *(AVCHeader->data+i);
				}
#endif
				if(actual_rdsize != NULL)
					*actual_rdsize = AVCHeader->size;
				gvdfile->nal_size = AVCHeader->size;
				return GVD_ERR_SUCCESS;
			}
	    }

	    res = mps_fread(buf, 1, rdsize, gvdfile->fd);
	    if(res == 0)
	    {
			int32_t err = mps_ferror();
			if(err == E_TMOUT)
				return GVD_ERR_FILE_OPERATION_TIMEDOUT;
			else if(err != E_OK)
				return GVD_ERR_FILE_OPERATION_FAILURE;

			if(!gFlagTrail[gvdfile->id])
			{
				int j;
				for(j=0; j<MPS_STREAM_TRAILING_BYTE; j++) {
					uint8_t *mem = buf;
					mem[j] = 0x00;
				}

				/* ストリーム末尾付加フラグをセット */
				gFlagTrail[gvdfile->id] = true;

				res = MPS_STREAM_TRAILING_BYTE;
			}
	    }

	    pos_top = gvdfile->pos;
	    pos_end = gvdfile->pos + res;
	    gvdfile->pos += (gvdFilePos)res;

	    /* NAL file format to Annex B filter */
		if(gFlagAnnexB[gvdfile->id] == 0)
		{
			/* 次のNALがBUF内に入っている */
			while(gvdfile->nal_next < pos_end)
			{
				/* BUF先頭よりも次のNALが前にある */
				if(pos_top > gvdfile->nal_next)
				{
					MPS_ErrPrintf("[%d] FILTER ERROR: Next NAL out of memory range.\r\n", gvdfile->id);
					break;
				}
				/* 書き換えるスタートコードが入りきっていない */
				else if(pos_end - gvdfile->nal_next > 0
						&& pos_end - gvdfile->nal_next < 4)
				{
					fseek((FILE *)gvdfile->fd, gvdfile->nal_next, SEEK_SET);
					gvdfile->pos = gvdfile->nal_next;
					res -= pos_end - gvdfile->nal_next;
					break;
				}
				else
				{
					uint32_t ofs = gvdfile->nal_next - pos_top; /* buffer先頭からNAL先頭のオフセット */

					/* スタートコード */
					gvdfile->nal_size = addr[ofs+0]<<24|addr[ofs+1]<<16|addr[ofs+2]<<8|addr[ofs+3];
					addr[ofs+0] =
						addr[ofs+1] =
						addr[ofs+2] = 0;
					addr[ofs+3] = 1;

					gvdfile->nal_prev = gvdfile->nal_next; /* NAL絶対位置 */

					gvdfile->nal_next = gvdfile->nal_size + gvdfile->nal_prev + 4;
				}
			}
		}
	}

	if(actual_rdsize != NULL) {
	    *actual_rdsize = (size_t)res;
    }

	return GVD_ERR_SUCCESS;
}

/**
 * @brief GVD-EOFチェックインタフェース
 * @param gvdfile [in]GVDファイル構造体
 * @return true:EOF
 * @return false:それ以外
 */
bool gvdFileIsEOF(gvdFile gvdfile)
{
	/* 次回freadがEOFとなるかどうかを返す */
	return gFlagTrail[gvdfile->id];
}

/**
 * GVDの仕様では、再生するファイルにH.264 raw fileが想定されている為、
 * デコード完了時に自動的にpos=0としてシークを呼び出します。<p>
 * MP4ファイルではデコード完了時に自動的に頭出ししません。
 * MPS_Seek()を使って任意に行ってください。
 * @brief GVD-ファイルシークインタフェース
 * @param gvdfile [in]GVDファイル構造体
 * @param pos [in]シークオフセット
 * @return エラーコード
 */
gvdError gvdFileSeekAbs(gvdFile gvdfile, gvdFilePos pos)
{
	if (gvdfile->error) {
	    return GVD_ERR_FILE_OPERATION_FAILURE;
	}
	mpsContext *ctx = MPS_getContext(gvdfile->id);
#ifdef MPS_USE_STDLIB
	char *cp = strrchr(ctx->filename, '.');
	if(cp != NULL && strncmp(cp, ".mp4", 4) == 0 && pos == 0)
		return GVD_ERR_SUCCESS;
#else
	uint32_t dot;
	for(dot=MPS_MAX_FILENAME_LENGTH; dot>0; dot--)
	{
	    if(ctx->filename[dot-1] == '.') break;
	}
	if((dot == 0 || MPS_MAX_FILENAME_LENGTH > dot+3) &&
	   ctx->filename[dot-1] == '.' &&
	   ctx->filename[dot-0] == 'm' &&
	   ctx->filename[dot+1] == 'p' &&
	   ctx->filename[dot+2] == '4' &&
	   ctx->filename[dot+3] == '\0' &&
	   pos == 0)
	    return GVD_ERR_SUCCESS;
#endif
	if (pos >= gvdfile->size) {
	    pos = gvdfile->size;
	}
	if (fseek((FILE *)gvdfile->fd, (long)pos, SEEK_SET) != 0) {
	    return GVD_ERR_FILE_OPERATION_FAILURE;
	}
	gvdfile->pos = pos;
	gvdfile->nal_next = pos;
	gvdfile->nal_prev = pos;
	gvdfile->nal_size = 0;

	/* ストリーム末尾付加フラグをクリア */
	gFlagTrail[gvdfile->id] = false;

	return GVD_ERR_SUCCESS;
}

/**
 * @brief GVD-ファイルクローズインタフェース
 * @param gvdfile [in]GVDファイル構造体
 * @return エラーコード
 */
gvdError gvdFileClose(gvdFile gvdfile)
{
	if (gvdfile->error) {
	    return GVD_ERR_FILE_OPERATION_FAILURE;
	}
	if (mps_fclose(gvdfile->fd) == -1) {
	    return GVD_ERR_FILE_OPERATION_FAILURE;
	}
	gvdfile->fd = NULL;
	gvdfile->size = 0;
	gvdfile->pos = 0;
	return GVD_ERR_SUCCESS;
}

/**
 * @brief GVD-ファイル構造体終了インタフェース
 * @param gvdfile [in,out]GVDファイル構造体
 * @return エラーコード
 */
gvdError gvdFileFinalize(gvdFile gvdfile)
{
	if (gvdfile->fd != NULL) {
	    return GVD_ERR_INVALID_STATE;
	}

	gvdFileInitialize(gvdfile);
	return GVD_ERR_SUCCESS;
}
