#include "sample.h"

static int read_input_streams(MediaCodecTestContext *ctx, media_codec_buffer_t *inputBuffer)
{
	int ret = 0, ret2 = 0;
	Uint32 bufIdx = 0, srcIdx = 0;
	Int32 doRewind = FALSE;
	uint8_t *seqHeader = NULL;
	int seqHeaderSize = 0;
	void *bufPtr = NULL;
	int avalBufSize = 0;

	if (ctx == NULL) {
		return -1;
	}
	if (ctx->context == NULL || ctx->inFile == NULL ||
		ctx->outFile == NULL || inputBuffer == NULL) {
		printf("Invalid parameters.\n");
		return -1;
	}

	if (ctx->context->video_dec_params.external_bitstream_buf) {
		// release input buffer and take it as the new input buffer
		for (bufIdx = 0; bufIdx < ctx->context->video_dec_params.bitstream_buf_count; bufIdx++) {
			if (ctx->exBs[bufIdx].valid &&
				ctx->exBs[bufIdx].src_idx == inputBuffer->vstream_buf.src_idx) {
				srcIdx = inputBuffer->vstream_buf.src_idx;
				break;
			}
		}
		if (bufIdx == ctx->context->video_dec_params.bitstream_buf_count) {
			return -1;
		}
		bufPtr = (void *)ctx->exBs[srcIdx].buf.virt_addr;

		if (ctx->context->video_dec_params.feed_mode == MC_FEEDING_MODE_FRAME_SIZE) {
			avalBufSize = ctx->exBs[srcIdx].buf.size;
		} else {
			avalBufSize = (ctx->exBs[srcIdx].buf.size < (int)ctx->feedingSize) ?
				ctx->exBs[srcIdx].buf.size : ctx->feedingSize;
		}
		inputBuffer->vstream_buf.fd = ctx->exBs[srcIdx].buf.fd;
		inputBuffer->vstream_buf.phy_ptr = ctx->exBs[srcIdx].buf.phys_addr;
		inputBuffer->vstream_buf.vir_ptr = (hb_u8 *)ctx->exBs[srcIdx].buf.virt_addr;
		if (ctx->readonce != 0 && ctx->exBs[srcIdx].onceflags >=
			(int32_t)ctx->context->video_dec_params.bitstream_buf_count) {
			inputBuffer->vstream_buf.size = ctx->exBs[srcIdx].readsize;
			return avalBufSize;
		}
	} else {
		bufPtr = (void *)inputBuffer->vstream_buf.vir_ptr;
		if (ctx->context->video_dec_params.feed_mode == MC_FEEDING_MODE_FRAME_SIZE) {
			avalBufSize = inputBuffer->vstream_buf.size;
		} else {
			avalBufSize = (inputBuffer->vstream_buf.size < ctx->feedingSize) ?
				inputBuffer->vstream_buf.size : ctx->feedingSize;
		}
	}

	// MC_FEEDING_MODE_FRAME_SIZE mode
	if (ctx->context->video_dec_params.feed_mode == MC_FEEDING_MODE_FRAME_SIZE) {
		do {
			if (ctx->avpacket.size == 0) {
				ret = av_read_frame(ctx->avContext, &ctx->avpacket);
				if (ret < 0 && doRewind == FALSE) {
					printf("Failed to read input file (error=0x%x)\n", ret);
				}

				if (ret < 0 && doRewind == TRUE) {
					avformat_close_input(&ctx->avContext);
					ret2 = avformat_open_input(&ctx->avContext, ctx->inputFileName, 0, 0);
					if (ret2 < 0) {
						ret = ret2;
						break;
					}
					/*ret = avformat_find_stream_info(ctx->avContext, 0);
					EXPECT_GE(ret, 0);
					if (ret < 0) {
						break;
					}
					ctx->videoIndex = av_find_best_stream(ctx->avContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
					EXPECT_GE(ctx->videoIndex, 0);
					if (ctx->videoIndex < 0) {
						ret = -1;
						break;
					}*/
					av_init_packet(&ctx->avpacket);
				}
			}
		} while (ret < 0 && doRewind == TRUE);

		if (ret < 0) {
			if (ret == AVERROR_EOF || ctx->avContext->pb->eof_reached == TRUE) {
				printf("End of file!\n");
				ret = 0;
			} else {
				printf("Failed to av_read_frame error(0x%08x)\n", ret);
			}
			return ret;
		}

		seqHeaderSize = 0;
		// if (ctx->firstPacket) {
		//	 AVCodecParameters* codec;
		//	 int retSize = 0;
		//	 codec = ctx->avContext->streams[ctx->videoIndex]->codecpar;
		//	 seqHeader = (uint8_t*)malloc(codec->extradata_size + 1024);
		//	 if (seqHeader == NULL) {
		//		 printf("Failed to mallock seqHeader\n");
		//		 ret = -1;
		//		 return ret;
		//	 }
		//	 memset((void*)seqHeader, 0x00, codec->extradata_size + 1024);

		//	 seqHeaderSize = build_dec_seq_header(seqHeader,
		//		 ctx->context->codec_id,
		//		 ctx->avContext->streams[ctx->videoIndex], &retSize);
		//	 if (seqHeaderSize < 0) {
		//		 printf("Failed to build seqHeader\n");
		//		 free(seqHeader);
		//		 ret = -1;
		//		 return ret;
		//	 }

		//	 ctx->firstPacket = 0;
		// }

		if ((ctx->avpacket.size <= avalBufSize) && (seqHeaderSize <= avalBufSize)) {
			int bufSize = 0;
			if (seqHeaderSize) {
				memcpy(bufPtr, seqHeader, seqHeaderSize);
				bufSize = seqHeaderSize;
				/*memcpy((char *)bufPtr+bufSize,ctx->avpacket.data, ctx->avpacket.size);
				bufSize += ctx->avpacket.size;
				av_packet_unref(&ctx->avpacket);
				ctx->avpacket.size = 0;*/
			} else {
				memcpy(bufPtr,ctx->avpacket.data, ctx->avpacket.size);
				bufSize = ctx->avpacket.size;
				av_packet_unref(&ctx->avpacket);
				ctx->avpacket.size = 0;
				if (ctx->readonce != 0 && ctx->context->video_dec_params.external_bitstream_buf != 0) {
					ctx->exBs[srcIdx].readsize = bufSize;
					ctx->exBs[srcIdx].onceflags++;
				}
			}
			inputBuffer->vstream_buf.size = bufSize;
			printf("%d, srcIdx:%d, phys_addr:%lx, virt_addr:%p, size:%d\n",
				__LINE__, srcIdx, inputBuffer->vstream_buf.phy_ptr, inputBuffer->vstream_buf.vir_ptr, inputBuffer->vstream_buf.size);
		} else {
			printf("The stream buffer is too small!\n");
			free(seqHeader);
			return -1;
		}
		if (seqHeader) {
			free(seqHeader);
			seqHeader = NULL;
		}
		return 1;
	}

	// MC_FEEDING_MODE_STREAM_SIZE mode
	do {
		ret = fread(bufPtr, 1, avalBufSize, ctx->inFile);
		if (ret <= 0 && doRewind == FALSE) {
			printf("Failed to read input file (error=0x%x)\n", ret);
		}

		if (ret <= 0 && doRewind == TRUE) {
			if(fseek(ctx->inFile, 0, SEEK_SET)) {
				printf("Failed to rewind input file\n");
				break;
			}
		}
	} while (ret == 0 && doRewind == TRUE);
	inputBuffer->vstream_buf.size = ret > 0 ? ret : 0;

	return ret;
}


static int write_output_frames(MediaCodecTestContext *ctx, media_codec_buffer_t *outputBuffer)
{
	int32_t ret = 0;

	if (ctx == NULL) {
		return -1;
	}

	if (ctx->context == NULL || ctx->inFile == NULL ||
		ctx->outFile == NULL || outputBuffer == NULL) {
		printf("Invalid parameters.\n");
		return -1;
	}
	printf("write stride: %d, wxh: %dx%d, size: %d\n", outputBuffer->vframe_buf.stride,
	outputBuffer->vframe_buf.width, outputBuffer->vframe_buf.height, outputBuffer->vframe_buf.size);
	fwrite(outputBuffer->vframe_buf.vir_ptr[0], outputBuffer->vframe_buf.size, 1, ctx->outFile);

	return ret;
}

void do_sync_decoding(void *arg)
{
	int ret = 0;
	MediaCodecTestContext *ctx = (MediaCodecTestContext *)arg;
	media_codec_context_t *context = NULL;
	media_codec_buffer_t inputBuffer;
	media_codec_buffer_t outputBuffer;
	media_codec_output_buffer_info_t info;

	ret = check_and_init_test(ctx);
	if (ret != 0) {
		printf("check_and_init_test failed(%d).\n", ret);
		return;
	}	context = ctx->context;

	context = ctx->context;
	ret = hb_mm_mc_initialize(context);
	if (ret != 0) {
		printf("hb_mm_mc_initialize failed(%d).\n", ret);
		return;
	}

	ret = hb_mm_mc_configure(context);
	if (ret != 0) {
		printf("hb_mm_mc_configure failed(%d).\n", ret);
		hb_mm_mc_release(context);
		return;
	}

	mc_av_codec_startup_params_t startup_params;
	startup_params.video_enc_startup_params.receive_frame_number = 0;
	ret = hb_mm_mc_start(context, &startup_params);
	if (ret != 0) {
		printf("hb_mm_mc_start failed(%d).\n", ret);
		hb_mm_mc_release(context);
		return;
	}

	do {
		// process input buffers
		ret = hb_mm_mc_dequeue_input_buffer(context, &inputBuffer, 3000);
		if (ret == 0) {
			ret = read_input_streams(ctx, &inputBuffer);
			if (ret <= 0) {
				printf("There is no more input data(ret=%d)!\n", ret);
				inputBuffer.vstream_buf.stream_end = TRUE;
				inputBuffer.vstream_buf.size = 0;
				ctx->lastStream = 1;
			}

			ret = hb_mm_mc_queue_input_buffer(context, &inputBuffer, 100);
			if (ret != 0) {
				break;
			}
		} else {
			if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
				char info[256];
				hb_mm_strerror(ret, info, 256);
				printf("dequeue input buffer fail.(%s)\n", info);
				break;
			}
		}

		// process output buffers
		memset(&outputBuffer, 0x00, sizeof(media_codec_buffer_t));
		memset(&info, 0x00, sizeof(media_codec_output_buffer_info_t));
		ret = hb_mm_mc_dequeue_output_buffer(context, &outputBuffer, &info, 100);
		if (ret == 0) {
			printf("info.video_frame_info.nalu_type %d\n", info.video_frame_info.nalu_type);
			write_output_frames(ctx, &outputBuffer);

			if (ctx->enable_get_userdata) {
				mc_user_data_buffer_t userdata = {0};
				ret = hb_mm_mc_get_user_data(context, &userdata, 0);
				if (!ret) {
					printf("Get userdata %d:\n", userdata.size);
					for (uint32_t i = 0; i < userdata.size; i++) {
						if (i < 16) {
							printf("userdata[i]:%x\n", userdata.virt_addr[i]);
						} else {
							printf("userdata[i]:%c\n", userdata.virt_addr[i]);
						}
					}
					ret = hb_mm_mc_release_user_data(context, &userdata);
				} else {
					ret = 0;
				}
			}

			ret = hb_mm_mc_queue_output_buffer(context, &outputBuffer, 100);
			if (outputBuffer.vframe_buf.frame_end) {
				printf("There is no more output data!\n");
				break;
			}
			if (ret) {
				break;
			}
		} else {
			char info[256];
			hb_mm_strerror(ret, info, 256);
			printf("dequeue output buffer fail.(%s)\n", info);
			if (ret != (int32_t)HB_MEDIA_ERR_WAIT_TIMEOUT) {
				break;
			}
		}
	} while (TRUE);

	ret = hb_mm_mc_stop(context);
	if (ret != 0) {
		printf("hb_mm_mc_stop failed(%d).\n", ret);
	}

	ret = hb_mm_mc_release(context);
	if (ret != 0) {
		printf("hb_mm_mc_release failed(%d).\n", ret);
	}

	check_and_release_test(ctx);
}