import * as Coders from '../../misc/coders/Encoders';
import * as M from '../../utils/metadata';

/**
 * createSourcesFromStreams creates a sources array with
 * one fake empty input and the given streams.
 * @param {array} streams
 * @returns
 */
export function createSourcesFromStreams(streams) {
	return [
		{
			type: 'network',
			inputs: [
				{
					address: '',
					options: [],
				},
			],
			streams: streams,
		},
	];
}

/**
 * createInputsOutputs creates the inputs and outputs based
 * on the sources, profiles, and outputs of the service.
 * the sources is constructed from the streams of the ingest.
 * the profiles are the encoding settings. the outputs from
 * the service only contain the output format options.
 *
 * @param {*} sources
 * @param {*} profiles encoding settings
 * @param {*} outputs service outputs (format options and address)
 * @returns
 */
export function createInputsOutputs(sources, profiles, outputs) {
	const [inpts, outpts] = M.createInputsOutputs(sources, profiles);

	const out = [];

	// iterate through all service outputs and copy its values
	// to a new object. if encoding is enabled, it will be done
	// for each output. this is not optimal. the "tee" output
	// should be considered.
	// https://ffmpeg.org/ffmpeg-formats.html#tee
	// https://stackoverflow.com/questions/67609510/ffmpeg-tee-muxer-failing-with-error-tag-avc1-incompatible-with-output-codec-id
	// and also consider the fifo and onfail option
	// https://ffmpeg.org/ffmpeg-formats.html#fifo
	for (let i = 0; i < outputs.length; i++) {
		let o = {
			address: outputs[i].address,
			options: [...outputs[i].options],
		};

		o.options = [...outpts[0].options, ...o.options];

		out.push(o);
	}

	return [inpts, out];
}
/**
 * validateRequirements validates the requirements object the each
 * service provides. Missing fields will be added.
 *
 * @param {*} requires requirement object
 * @returns validated requirement object
 */
export function validateRequirements(requires) {
	if (!requires) {
		requires = {};
	}

	requires = {
		protocols: [],
		formats: [],
		devices: [],
		codecs: {},
		...requires,
	};

	requires.codecs = {
		audio: [],
		video: [],
		...requires.codecs,
	};

	return requires;
}

/**
 *
 * @param {*} requires
 * @param {*} skills
 * @returns {boolean}
 */
export function checkServiceRequirements(requires, skills) {
	if (!skills) {
		return false;
	}

	requires = validateRequirements(requires);

	if (requires.protocols.length !== 0) {
		let hasOne = false;
		for (let protocol of requires.protocols) {
			if (skills.protocols.output.includes(protocol) === true) {
				hasOne = true;
				break;
			}
		}

		if (hasOne === false) {
			return false;
		}
	}

	if (requires.formats.length !== 0) {
		let hasOne = false;
		for (let format of requires.formats) {
			if (skills.formats.muxers.includes(format) === true) {
				hasOne = true;
				break;
			}
		}

		if (hasOne === false) {
			return false;
		}
	}

	if (requires.devices.length !== 0) {
		let hasOne = false;
		for (let device of requires.devices) {
			if (device in skills.sinks) {
				hasOne = true;
				break;
			}
		}

		if (hasOne === false) {
			return false;
		}
	}

	if (requires.codecs.audio.length !== 0) {
		let hasOne = false;
		for (let codec of requires.codecs.audio) {
			if (codec in skills.codecs.audio) {
				hasOne = true;
				break;
			}
		}

		if (hasOne === false) {
			return false;
		}
	}

	if (requires.codecs.video.length !== 0) {
		let hasOne = false;
		for (let codec of requires.codecs.video) {
			if (codec in skills.codecs.video) {
				hasOne = true;
				break;
			}
		}

		if (hasOne === false) {
			return false;
		}
	}

	return true;
}

/**
 *
 * @param {*} requires
 * @param {*} skills
 * @returns
 */
export function conflateServiceSkills(requires, skills) {
	if (!skills) {
		return null;
	}

	requires = validateRequirements(requires);

	const serviceSkills = {
		protocols: [],
		formats: [],
		devices: {},
		codecs: {
			audio: ['copy'],
			video: ['copy'],
		},
	};

	for (let protocol of requires.protocols) {
		if (skills.protocols.output.includes(protocol) === true) {
			serviceSkills.protocols.push(protocol);
		}
	}

	for (let format of requires.formats) {
		if (skills.formats.muxers.includes(format) === true) {
			serviceSkills.formats.push(format);
		}
	}

	for (let device of requires.devices) {
		if (device in skills.sinks) {
			serviceSkills.devices[device] = [...skills.sinks[device]];
		}
	}

	for (let codec of requires.codecs.audio) {
		if (codec in skills.codecs.audio) {
			serviceSkills.codecs.audio.push(codec);
		}
	}

	for (let codec of requires.codecs.video) {
		if (codec in skills.codecs.video) {
			serviceSkills.codecs.video.push(codec);
		}
	}

	return serviceSkills;
}

/**
 * Preselects an encoder to a profile.
 * @param {*} profile A profile
 * @param {*} type Either 'audio' or 'video'
 * @param {*} streams List of available streams
 * @param {*} codecs List of target codecs
 * @param {*} encoders list of available (to ffmpeg) encoders
 * @returns {boolean} Whether the provided profile is valid
 */
export function preselectProfile(profile, type, streams, codecs, encoders) {
	/**
	 * Checks if the given profile makes sense, i.e. matches to the available
	 * streams and codecs.
	 * @param {*} profile A profile
	 * @param {*} type Either 'audio' or 'video'
	 * @param {*} streams List of available streams
	 * @param {*} codecs List of target codecs
	 * @returns {boolean} Whether the provided profile is valid
	 */
	const isPlausible = (profile, type, streams, codecs) => {
		if (profile.stream < 0) {
			return false;
		}

		if (profile.stream >= streams.length) {
			return false;
		}

		if (streams[profile.stream].type !== type) {
			return false;
		}

		if (!codecs.includes(streams[profile.stream].codec)) {
			if (profile.encoder.coder === 'copy') {
				return false;
			}
		} else {
			if (profile.encoder.coder === 'copy') {
				return true;
			}
		}

		let coder = null;
		if (type === 'audio') {
			coder = Coders.Audio.Get(profile.encoder.coder);
		} else if (type === 'video') {
			coder = Coders.Video.Get(profile.encoder.coder);
		}

		if (coder === null) {
			return false;
		}

		if (!codecs.includes(coder.codec)) {
			return false;
		}

		return true;
	};

	if (isPlausible(profile, type, streams, codecs)) {
		return profile;
	}

	profile.source = 0;
	profile.stream = -1;
	profile.encoder.coder = 'none';

	for (let i = 0; i < streams.length; i++) {
		if (streams[i].type !== type) {
			continue;
		}

		// Select the first stream with matching type.
		profile.stream = i;

		if (!codecs.includes(streams[i].codec)) {
			// The codec doesn't match. Select the first available coder for one of the target codecs.
			for (let codec of codecs) {
				if (codec === 'copy') {
					continue;
				}

				let coder = null;

				if (type === 'audio') {
					coder = Coders.Audio.GetCoderForCodec(codec, encoders);
				} else if (type === 'video') {
					coder = Coders.Video.GetCoderForCodec(codec, encoders);
				}

				if (coder === null) {
					profile.encoder.coder = 'none';
				} else {
					const defaults = coder.defaults();
					profile.encoder.coder = coder.coder;
					profile.encoder.settings = defaults.settings;
					profile.encoder.mapping = defaults.mapping;
					break;
				}
			}
		} else {
			// The codec matches. Select the copy coder.
			profile.encoder.coder = 'copy';

			let coder = type === 'audio' ? Coders.Audio.Get('copy') : Coders.Video.Get('copy');

			const defaults = coder.defaults();

			profile.encoder.settings = defaults.settings;
			profile.encoder.mapping = defaults.mapping;
		}

		break;
	}

	return profile;
}
