GF_Err rfc_6381_get_codec_vpx(char *szCodec, u32 subtype, GF_VPConfig *vpcc, COLR colr);
GF_Err rfc_6381_get_codec_dolby_vision(char *szCodec, u32 subtype, GF_DOVIDecoderConfigurationRecord *dovi);
GF_Err rfc_6381_get_codec_vvc(char *szCodec, u32 subtype, GF_VVCConfig *vvcc);
GF_Err rfc_6381_get_codec_mpegha(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size, s32 pl);
GF_Err rfc6381_codec_name_default(char *szCodec, u32 subtype, u32 codec_id);


static GF_Err dasher_get_rfc_6381_codec_name(GF_DasherCtx *ctx, GF_DashStream *ds, char *szCodec, Bool force_inband, Bool force_sbr)
{
	u32 subtype=0, subtype_src=0;
	s32 mha_pl=-1;
	const GF_PropertyValue *dcd, *dcd_enh, *dovi, *codec;
	COLR colr;

	memset(&colr, 0, sizeof(colr));

	dcd = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE);
	if (dcd) subtype_src = dcd->value.uint;

	dcd = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DECODER_CONFIG);
	dcd_enh = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT);

	// If colour information is supplied in [the colr] box, and also in the video bitstream, [the] box takes precedence
	{
		const GF_PropertyValue *p1 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_COLR_PRIMARIES),
		                       *p2 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_COLR_TRANSFER),
		                       *p3 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_COLR_MX),
		                       *p4 = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_COLR_RANGE);
		if (p1 && p2 && p3 && p4) {
			colr.override = GF_TRUE;
			colr.colour_primaries = p1->value.uint;
			colr.transfer_characteristics = p2->value.uint;
			colr.matrix_coefficients = p3->value.uint;
			colr.full_range = p4->value.boolean;
		} else if (!p1 && !p2 && !p3 && !p4) {
		} else {
			GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[ISOM Tools] Incomplete upstream-filter 'colr' information when computing RFC6381. Ignoring.\n"));
		}
	}

	if (!force_inband) {
		force_inband = ds->inband_params;
	}
	if (!force_inband) {
		const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE);
		if (p) {
			//input uses inband parameters, force it on output regardless of bitstream switching mode
			switch (p->value.uint) {
			case GF_ISOM_SUBTYPE_AVC3_H264:
			case GF_ISOM_SUBTYPE_AVC4_H264:
			case GF_ISOM_SUBTYPE_LHE1:
			case GF_ISOM_SUBTYPE_HEV1:
			case GF_ISOM_SUBTYPE_VVI1:
			case GF_ISOM_SUBTYPE_DVAV:
			case GF_ISOM_SUBTYPE_DVHE:
				force_inband = GF_TRUE;
				ds->inband_params = 1;
				break;
			}
		}
	}

	codec = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_CODEC);
	if (codec && (codec->type==GF_PROP_STRING) && codec->value.string) {
		const char *codec_str = codec->value.string;
		if (codec_str[0] != '.') {
			snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", codec_str);
			return GF_OK;
		}
		if (!subtype_src)
			subtype_src = gf_codecid_4cc_type(ds->codec_id);
		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s%s", gf_4cc_to_str(subtype_src), codec_str);
		return GF_OK;
	}

	dovi = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_DOLBY_VISION);
	if (dovi) {
		GF_Err e;
		GF_BitStream *bs = gf_bs_new(dovi->value.data.ptr, dovi->value.data.size, GF_BITSTREAM_READ);
		GF_DOVIDecoderConfigurationRecord *dvcc = gf_odf_dovi_cfg_read_bs(bs);
		gf_bs_del(bs);
		if (!dvcc) {
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[ISOM Tools] No config found for Dolby Vision file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
			return GF_BAD_PARAM;
		}

		switch (ds->codec_id) {
		case GF_CODECID_HEVC:
			e = rfc_6381_get_codec_dolby_vision(szCodec, ds->inband_params ? GF_ISOM_SUBTYPE_DVHE : GF_ISOM_SUBTYPE_DVH1, dvcc);
			break;
		case GF_CODECID_AVC:
			e = rfc_6381_get_codec_dolby_vision(szCodec, ds->inband_params ? GF_ISOM_SUBTYPE_DVAV : GF_ISOM_SUBTYPE_DVA1, dvcc);
			break;
		case GF_CODECID_AV1:
			e = rfc_6381_get_codec_dolby_vision(szCodec, GF_ISOM_SUBTYPE_DAV1, dvcc);
			break;
		default:
			e = GF_NOT_SUPPORTED;
		}
		gf_odf_dovi_cfg_del(dvcc);
		return e;
	}

	switch (ds->codec_id) {
	case GF_CODECID_AAC_MPEG4:
	case GF_CODECID_AAC_MPEG2_MP:
	case GF_CODECID_AAC_MPEG2_LCP:
	case GF_CODECID_AAC_MPEG2_SSRP:
	case GF_CODECID_USAC:
		return rfc_6381_get_codec_aac(szCodec, ds->codec_id, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0, force_sbr);

	case GF_CODECID_MPEG4_PART2:
		return rfc_6381_get_codec_m4v(szCodec, ds->codec_id, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0);
		break;
	case GF_CODECID_SVC:
	case GF_CODECID_MVC:
		if (dcd_enh) dcd = dcd_enh;
		subtype = (ds->codec_id==GF_CODECID_SVC) ? GF_ISOM_SUBTYPE_SVC_H264 : GF_ISOM_SUBTYPE_MVC_H264;
	case GF_CODECID_AVC:
		if (!subtype) {
			if (force_inband) {
				subtype = dcd_enh ? GF_ISOM_SUBTYPE_AVC4_H264 : GF_ISOM_SUBTYPE_AVC3_H264;
			} else {
				subtype = dcd_enh ? GF_ISOM_SUBTYPE_AVC2_H264 : GF_ISOM_SUBTYPE_AVC_H264;
			}
		}
		if (dcd) {
			GF_AVCConfig *avcc = gf_odf_avc_cfg_read(dcd->value.data.ptr, dcd->value.data.size);
			if (avcc) {
				GF_Err e = rfc_6381_get_codec_avc(szCodec, subtype, avcc);
				gf_odf_avc_cfg_del(avcc);
				return e;
			}
		}
		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
		GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot find AVC config, using default %s\n", szCodec));
		return GF_OK;

#ifndef GPAC_DISABLE_HEVC
	case GF_CODECID_LHVC:
		subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1;
		//fallthrough
	case GF_CODECID_HEVC_TILES:
		if (!subtype) subtype = GF_ISOM_SUBTYPE_HVT1;
		if (!dcd && ds->dep_id) {
			u32 i, count = gf_list_count(ctx->current_period->streams);
			for (i=0; i<count; i++) {
				GF_DashStream *a_ds = gf_list_get(ctx->current_period->streams, i);
				if (a_ds->id != ds->dep_id) continue;
				dcd = gf_filter_pid_get_property(a_ds->ipid, GF_PROP_PID_DECODER_CONFIG);
				break;
			}
		}
		//fallthrough
	case GF_CODECID_HEVC:
		if (!subtype) {
			if (ds->tile_base) {
				subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2;
			} else if (dcd_enh) {
				if (dcd) {
					subtype = force_inband ? GF_ISOM_SUBTYPE_HEV2 : GF_ISOM_SUBTYPE_HVC2;
				} else {
					subtype = force_inband ? GF_ISOM_SUBTYPE_LHE1 : GF_ISOM_SUBTYPE_LHV1;
				}
			} else {
				subtype = force_inband ? GF_ISOM_SUBTYPE_HEV1 : GF_ISOM_SUBTYPE_HVC1;
			}
		}
		if (dcd || dcd_enh) {
			GF_HEVCConfig *hvcc = dcd ? gf_odf_hevc_cfg_read(dcd->value.data.ptr, dcd->value.data.size, GF_FALSE) : NULL;
			if (hvcc) {
				GF_Err e = rfc_6381_get_codec_hevc(szCodec, subtype, hvcc);
				gf_odf_hevc_cfg_del(hvcc);
				return e;
			}
			GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[Dasher] HEVC config not compliant !\n"));
			return GF_NON_COMPLIANT_BITSTREAM;
		}

		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
		GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot find HEVC config, using default %s\n", szCodec));
		return GF_OK;
#endif

#ifndef GPAC_DISABLE_AV1
	case GF_CODECID_AV1:
		if (!subtype) subtype = GF_ISOM_SUBTYPE_AV01;

		if (dcd) {
			GF_AV1Config *av1c = gf_odf_av1_cfg_read(dcd->value.data.ptr, dcd->value.data.size);
			if (av1c) {
				GF_Err e = rfc_6381_get_codec_av1(szCodec, subtype, av1c, colr);
				gf_odf_av1_cfg_del(av1c);
				return e;
			}
			GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASHER] AV1 config not conformant\n"));
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
		GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot find AV1 config, using default %s\n", szCodec));
		return GF_OK;
#endif /*GPAC_DISABLE_AV1*/


	case GF_CODECID_VP8:
		if (!subtype) subtype = GF_ISOM_SUBTYPE_VP08;
	case GF_CODECID_VP9:
		if (!subtype) subtype = GF_ISOM_SUBTYPE_VP09;

		if (dcd) {
			GF_VPConfig *vpcc = gf_odf_vp_cfg_read(dcd->value.data.ptr, dcd->value.data.size);

			if (vpcc) {
				GF_Err e = rfc_6381_get_codec_vpx(szCodec, subtype, vpcc, colr);
				gf_odf_vp_cfg_del(vpcc);
				return e;
			}
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] No config found for VP file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
		GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot find VPX config, using default %s\n", szCodec));
		return GF_OK;

	case GF_CODECID_MHAS:
		subtype = subtype_src ? subtype_src : GF_ISOM_SUBTYPE_MH3D_MHM1;
		if (!dcd) {
			const GF_PropertyValue *pl = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_PROFILE_LEVEL);
			if (pl) mha_pl = (s32) pl->value.uint;
		}
		//fallthrough
	case GF_CODECID_MPHA:
		if (!subtype)
			subtype = subtype_src ? subtype_src : GF_ISOM_SUBTYPE_MH3D_MHA1;

		return rfc_6381_get_codec_mpegha(szCodec, subtype, dcd ? dcd->value.data.ptr : NULL, dcd ? dcd->value.data.size : 0, mha_pl);

	case GF_CODECID_VVC:
		if (!subtype) {
			subtype = force_inband ? GF_ISOM_SUBTYPE_VVI1 : GF_ISOM_SUBTYPE_VVC1;
		}
		if (dcd) {
			GF_VVCConfig *vvcc = gf_odf_vvc_cfg_read(dcd->value.data.ptr, dcd->value.data.size);

			snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.", gf_4cc_to_str(subtype));
			if (vvcc) {
				GF_Err e = rfc_6381_get_codec_vvc(szCodec, subtype, vvcc);
				gf_odf_vvc_cfg_del(vvcc);
				return e;
			}
			GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[Dasher] No config found for VP file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
			return GF_NON_COMPLIANT_BITSTREAM;
		}
		snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
		GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] Cannot find VVC config, using default %s\n", szCodec));
		return GF_OK;

	default:
		subtype = gf_codecid_4cc_type(ds->codec_id);
		if (!subtype) {
			const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_ISOM_SUBTYPE);
			if (p) subtype = p->value.uint;
		}
		if (!subtype && (ds->codec_id==GF_CODECID_RAW)) {
			if (ds->stream_type==GF_STREAM_VISUAL) {
				const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_PIXFMT);
				if (p) subtype = gf_pixel_fmt_to_qt_type(p->value.uint);
			}
			else if (ds->stream_type==GF_STREAM_AUDIO) {
				const GF_PropertyValue *p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_AUDIO_FORMAT);
				if (p) subtype = gf_audio_fmt_to_isobmf(p->value.uint);
			}
		}
		if (!subtype) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[Dasher] codec parameters not known, cannot set codec string\n" ));
			strcpy(szCodec, "unkn");