import BaseComponent, {
	cleanRenderPreResult,
	register,
} from './BaseComponent.mjs';
import {mergeSets} from '../../../core/ArrayUtilities.mjs';

function nullableMax(a = null, b = null) {
	if(a === null) {
		return b;
	}
	if(b === null) {
		return a;
	}
	return Math.max(a, b);
}

function mergeResults(a, b) {
	mergeSets(a.agentIDs, b.agentIDs);
	return {
		agentIDs: a.agentIDs,
		asynchronousY: nullableMax(a.asynchronousY, b.asynchronousY),
		topShift: Math.max(a.topShift, b.topShift),
		y: nullableMax(a.y, b.y),
	};
}

export default class Parallel extends BaseComponent {
	invokeChildren(stage, env, methodName) {
		return stage.stages.map((subStage) => {
			const component = env.components.get(subStage.type);
			return component[methodName](subStage, env);
		});
	}

	prepareMeasurements(stage, env) {
		this.invokeChildren(stage, env, 'prepareMeasurements');
	}

	separationPre(stage, env) {
		this.invokeChildren(stage, env, 'separationPre');
	}

	separation(stage, env) {
		this.invokeChildren(stage, env, 'separation');
	}

	renderPre(stage, env) {
		const baseResults = {
			agentIDs: [],
			asynchronousY: null,
			topShift: 0,
		};

		return this.invokeChildren(stage, env, 'renderPre')
			.map((r) => cleanRenderPreResult(r))
			.reduce(mergeResults, baseResults);
	}

	render(stage, env) {
		const originalMakeRegion = env.makeRegion;
		let bottomY = 0;
		stage.stages.forEach((subStage) => {
			env.makeRegion = (options = {}) => (
				originalMakeRegion(Object.assign({
					stageOverride: subStage,
				}, options))
			);

			const component = env.components.get(subStage.type);
			const baseY = component.render(subStage, env) || 0;
			bottomY = Math.max(bottomY, baseY);
		});
		env.makeRegion = originalMakeRegion;
		return bottomY;
	}

	renderHidden(stage, env) {
		this.invokeChildren(stage, env, 'renderHidden');
	}

	shouldHide(stage, env) {
		const baseResults = {
			nest: 0,
			self: false,
		};
		return this.invokeChildren(stage, env, 'shouldHide')
			.reduce((result, {self = false, nest = 0} = {}) => ({
				nest: result.nest + nest,
				self: result.self || Boolean(self),
			}), baseResults);
	}
}

register('parallel', new Parallel());
