#include "sample.h"

int allocate_ion_mem(int fd, ion_buffer_t *vb)
{
	int ret = 0;
	int64_t hbmem_flags = HB_MEM_USAGE_CPU_READ_OFTEN | HB_MEM_USAGE_CPU_WRITE_OFTEN |
							HB_MEM_USAGE_PRIV_HEAP_DMA;
	hb_mem_common_buf_t com_buf = {0, };

	if (fd <= 0 || !vb) {
		printf("Invalid ion handle(%d) or buffer handle(%p).\n",
			fd, vb);
		return -1;
	}

	ret = hb_mem_alloc_com_buf(vb->size, hbmem_flags, &com_buf);
	if (ret != 0) {
		printf("Failed to allocate ion memory.(%d)\n",
			ret);
		return -1;
	}

	vb->fd = com_buf.fd;
	vb->handle = 0;
	vb->phys_addr = com_buf.phys_addr;
	vb->virt_addr = (uint64_t)com_buf.virt_addr;

	return ret;
}

int release_ion_mem(int fd, ion_buffer_t *vb)
{
	int32_t ret;

	if (fd <= 0 || !vb) {
		printf("Invalid ion handle(%d) or buffer handle(%p).\n",
			fd, vb);
		return -1;
	}

	ret = hb_mem_free_buf(vb->fd);

	return ret;
}

int check_and_init_test(MediaCodecTestContext *ctx)
{
	int32_t ret = 0;
	char *inputFileName, *outputFileName;

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

	inputFileName = ctx->inputFileName;
	outputFileName = ctx->outputFileName;

	printf("InputFileName = %s\n", inputFileName);
	printf("OutputFileName = %s\n", outputFileName);

	if (inputFileName == NULL || outputFileName == NULL) {
		return -1;
	}

	ctx->inFile = fopen(inputFileName, "rb");
	ctx->outFile = fopen(outputFileName, "wb+");
	if (ctx->inFile == NULL || ctx->outFile == NULL) {
		printf("open file fail. inFile %p, outFile %p\n", ctx->inFile, ctx->outFile);
		return -1;
	}

	// allocate ion buffers
	ret = hb_mem_module_open();
	if (ret == 0) {
		ctx->ionFd = 1;
	} else {
		printf("hb_mem_module_open failed %d.\n", ret);
		return -1;
	}

	if (ctx->context->encoder == TRUE) {
		printf("Thread use %s buffer mode, %d rc mode\n",
			ctx->context->video_enc_params.external_frame_buf ?
			"external" : "internal",
			ctx->context->video_enc_params.rc_params.mode);
		if (ctx->context->video_enc_params.external_frame_buf) {
			ctx->exFb = (ExternalFrameBuffer *) malloc(
				ctx->context->video_enc_params.frame_buf_count * sizeof(ExternalFrameBuffer));
			if (ctx->exFb == NULL) {
				printf("malloc failed.\n");
				return -1;
			}
			for (Uint32 i=0; i<ctx->context->video_enc_params.frame_buf_count; i++) {
				ctx->exFb[i].buf.size = ctx->context->video_enc_params.width
					* ctx->context->video_enc_params.height * 3/2; // only for yuv420;
				ret = allocate_ion_mem(ctx->ionFd, &ctx->exFb[i].buf);
				if (ret != 0) {
					printf("allocate_ion_mem failed %d.\n", ret);
					return ret;
				}
				ctx->exFb[i].valid = 1;
				ctx->exFb[i].src_idx = i;
				if (ctx->readonce != 0) {
					ret = fread((void *)ctx->exFb[i].buf.virt_addr, 1,
						ctx->exFb[i].buf.size, ctx->inFile);
					if (ret <= 0) {
						ret = fseek(ctx->inFile, 0, SEEK_SET);
						if(ret != 0) {
							printf("Failed to rewind input file\n");
							return ret;
						}
					}
				}
			}
		}
	} else {
		printf("Thread use %s buffer mode, %d feed mode.\n",
			ctx->context->video_dec_params.external_bitstream_buf ?
			"external" : "internal",
			ctx->context->video_dec_params.feed_mode);
		if (ctx->context->video_dec_params.external_bitstream_buf) {
			ctx->exBs = (ExternalStreamBuffer *) malloc(
					ctx->context->video_dec_params.bitstream_buf_count * sizeof(ExternalStreamBuffer));
			if (ctx->exBs == NULL) {
				printf("malloc failed.\n");
				return -1;
			}

			for (Uint32 i=0; i<ctx->context->video_dec_params.bitstream_buf_count; i++) {
				ctx->exBs[i].buf.size = ctx->context->video_dec_params.bitstream_buf_size;
				ret = allocate_ion_mem(ctx->ionFd, &ctx->exBs[i].buf);
				if (ret != 0) {
					printf("allocate_ion_mem failed %d.\n", ret);
					return ret;
				}
				ctx->exBs[i].valid = 1;
				ctx->exBs[i].src_idx = i;
				if (ctx->readonce) {
					ctx->exBs[i].onceflags = 0;
				}
			}
		}

	}

	// open decode files
	if (ctx->context->encoder != TRUE) {
		if (ctx->context->video_dec_params.feed_mode == MC_FEEDING_MODE_FRAME_SIZE) {
			ret = avformat_open_input(&ctx->avContext, ctx->inputFileName, 0, 0);
			if (ret < 0) {
				return ret;
			}
			ret = avformat_find_stream_info(ctx->avContext, 0);
			if (ret < 0) {
				return ret;
			}
			ctx->videoIndex = av_find_best_stream(ctx->avContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
			if (ctx->videoIndex < 0) {
				return -1;
			}
			av_init_packet(&ctx->avpacket);
		} else {
			if (ctx->feedingSize == 0) {
				uint32_t KB = 1024;
				int32_t probability10;

				srand((uint32_t)time(NULL));
				ctx->feedingSize = rand() % MAX_FEEDING_SIZE;
				probability10 = (ctx->feedingSize % 100) < 10;
				if (ctx->feedingSize < KB) {
					if (probability10 == FALSE)
						ctx->feedingSize *= 100;
				}
			}
			printf("Feeding size = %d\n", ctx->feedingSize);
		}
		ctx->firstPacket = 1;
	}
	return 0;
}

int check_and_release_test(MediaCodecTestContext *ctx)
{
	if (ctx == NULL) {
		return -1;
	}

	if (ctx->context == NULL || ctx->inFile == NULL ||
		ctx->outFile == NULL) {
		return -1;
	}

	if (ctx->context->encoder != TRUE) {
		if (ctx->context->video_dec_params.feed_mode == MC_FEEDING_MODE_FRAME_SIZE) {
			if (ctx->avContext) {
				avformat_close_input(&ctx->avContext);
			}
		}
	}
	if (ctx->context->encoder == TRUE) {
		if (ctx->context->video_enc_params.external_frame_buf) {
			if (ctx->exFb) {
				for (Uint32 i=0; i<ctx->context->video_enc_params.frame_buf_count; i++) {
					release_ion_mem(ctx->ionFd, &ctx->exFb[i].buf);
				}
				free(ctx->exFb);
			}
		}
	} else {
		if (ctx->context->video_dec_params.external_bitstream_buf) {
			if (ctx->exBs) {
				for (Uint32 i=0; i<ctx->context->video_dec_params.bitstream_buf_count; i++) {
					release_ion_mem(ctx->ionFd, &ctx->exBs[i].buf);
				}
				free(ctx->exBs);
			}
		}
	}

	if (ctx->ionFd)
		hb_mem_module_close();

	if (ctx->outFile)
		fclose(ctx->outFile);

	if (ctx->inFile)
		fclose(ctx->inFile);

	return 0;
}

int get_rc_params(media_codec_context_t *context, mc_rate_control_params_t *rc_params)
{
	int ret = 0;
	ret = hb_mm_mc_get_rate_control_config(context, rc_params);
	if (ret != 0) {
		printf("Failed to get rc params ret=0x%x\n", ret);
		return -1;
	}
	switch (rc_params->mode) {
	case MC_AV_RC_MODE_H264CBR:
		rc_params->h264_cbr_params.intra_period = 30;
		rc_params->h264_cbr_params.intra_qp = 30;
		rc_params->h264_cbr_params.bit_rate = 5000;
		rc_params->h264_cbr_params.frame_rate = 30;
		rc_params->h264_cbr_params.initial_rc_qp = 20;
		rc_params->h264_cbr_params.vbv_buffer_size = 20;
		rc_params->h264_cbr_params.mb_level_rc_enalbe = 1;
		rc_params->h264_cbr_params.min_qp_I = 8;
		rc_params->h264_cbr_params.max_qp_I = 50;
		rc_params->h264_cbr_params.min_qp_P = 8;
		rc_params->h264_cbr_params.max_qp_P = 50;
		rc_params->h264_cbr_params.min_qp_B = 8;
		rc_params->h264_cbr_params.max_qp_B = 50;
		rc_params->h264_cbr_params.hvs_qp_enable = 1;
		rc_params->h264_cbr_params.hvs_qp_scale = 2;
		rc_params->h264_cbr_params.max_delta_qp = 10;
		rc_params->h264_cbr_params.qp_map_enable = 0;
		break;
	case MC_AV_RC_MODE_H264VBR:
		rc_params->h264_vbr_params.intra_qp = 20;
		rc_params->h264_vbr_params.intra_period = 30;
		rc_params->h264_vbr_params.intra_qp = 35;
		break;
	case MC_AV_RC_MODE_H264AVBR:
		rc_params->h264_avbr_params.intra_period = 15;
		rc_params->h264_avbr_params.intra_qp = 25;
		rc_params->h264_avbr_params.bit_rate = 2000;
		rc_params->h264_avbr_params.vbv_buffer_size = 3000;
		rc_params->h264_avbr_params.min_qp_I = 15;
		rc_params->h264_avbr_params.max_qp_I = 50;
		rc_params->h264_avbr_params.min_qp_P = 15;
		rc_params->h264_avbr_params.max_qp_P = 45;
		rc_params->h264_avbr_params.min_qp_B = 15;
		rc_params->h264_avbr_params.max_qp_B = 48;
		rc_params->h264_avbr_params.hvs_qp_enable = 0;
		rc_params->h264_avbr_params.hvs_qp_scale = 2;
		rc_params->h264_avbr_params.max_delta_qp = 5;
		rc_params->h264_avbr_params.qp_map_enable = 0;
		break;
	case MC_AV_RC_MODE_H264FIXQP:
		rc_params->h264_fixqp_params.force_qp_I = 23;
		rc_params->h264_fixqp_params.force_qp_P = 23;
		rc_params->h264_fixqp_params.force_qp_B = 23;
		rc_params->h264_fixqp_params.intra_period = 23;
		break;
	case MC_AV_RC_MODE_H264QPMAP:
		break;
	case MC_AV_RC_MODE_H265CBR:
		rc_params->h265_cbr_params.intra_period = 20;
		rc_params->h265_cbr_params.intra_qp = 30;
		rc_params->h265_cbr_params.bit_rate = 5000;
		rc_params->h265_cbr_params.frame_rate = 30;
		if (context->video_enc_params.width >= 480 ||
			context->video_enc_params.height >= 480) {
			rc_params->h265_cbr_params.initial_rc_qp = 30;
			rc_params->h265_cbr_params.vbv_buffer_size = 3000;
			rc_params->h265_cbr_params.ctu_level_rc_enalbe = 1;
		} else {
			rc_params->h265_cbr_params.initial_rc_qp = 20;
			rc_params->h265_cbr_params.vbv_buffer_size = 20;
			rc_params->h265_cbr_params.ctu_level_rc_enalbe = 1;
		}
		rc_params->h265_cbr_params.min_qp_I = 8;
		rc_params->h265_cbr_params.max_qp_I = 50;
		rc_params->h265_cbr_params.min_qp_P = 8;
		rc_params->h265_cbr_params.max_qp_P = 50;
		rc_params->h265_cbr_params.min_qp_B = 8;
		rc_params->h265_cbr_params.max_qp_B = 50;
		rc_params->h265_cbr_params.hvs_qp_enable = 1;
		rc_params->h265_cbr_params.hvs_qp_scale = 2;
		rc_params->h265_cbr_params.max_delta_qp = 10;
		rc_params->h265_cbr_params.qp_map_enable = 0;
		break;
	case MC_AV_RC_MODE_H265VBR:
		rc_params->h265_vbr_params.intra_qp = 20;
		rc_params->h265_vbr_params.intra_period = 30;
		rc_params->h265_vbr_params.intra_qp = 35;
		break;
	case MC_AV_RC_MODE_H265AVBR:
		rc_params->h265_avbr_params.intra_period = 15;
		rc_params->h265_avbr_params.intra_qp = 25;
		rc_params->h265_avbr_params.bit_rate = 2000;
		rc_params->h265_avbr_params.vbv_buffer_size = 3000;
		rc_params->h265_avbr_params.min_qp_I = 15;
		rc_params->h265_avbr_params.max_qp_I = 50;
		rc_params->h265_avbr_params.min_qp_P = 15;
		rc_params->h265_avbr_params.max_qp_P = 45;
		rc_params->h265_avbr_params.min_qp_B = 15;
		rc_params->h265_avbr_params.max_qp_B = 48;
		rc_params->h265_avbr_params.hvs_qp_enable = 0;
		rc_params->h265_avbr_params.hvs_qp_scale = 2;
		rc_params->h265_avbr_params.max_delta_qp = 5;
		rc_params->h265_avbr_params.qp_map_enable = 0;
		break;
	case MC_AV_RC_MODE_H265FIXQP:
		rc_params->h265_fixqp_params.force_qp_I = 23;
		rc_params->h265_fixqp_params.force_qp_P = 23;
		rc_params->h265_fixqp_params.force_qp_B = 23;
		rc_params->h265_fixqp_params.intra_period = 23;
		break;
	case MC_AV_RC_MODE_H265QPMAP:
		break;
	default:
		ret = HB_MEDIA_ERR_INVALID_PARAMS;
		break;
	}
	return ret;
}