Source: core/Composition.js

import { Measure } from "../data/Measure.js";

import { Component } from "./Component.js";
import { Liqueur } from "./Liqueur.js";
import { Alcohol } from "./Alcohol.js";
import { Syrup } from "./Syrup.js";
import { VirtualIngredient } from "./VirtualIngredient.js";
import round from "./round.js";

/**
 * Collection of ingredients
 */
export class Composition {
	components = [];
	/**
	 * Add a Component to Composition
	 *
	 * @param {string} id
	 * @param {Component} component
	 */
	add(id, component) {
		if (this.component(id))
			throw new Error('Duplicate ID');
		this.components.push({ id, component });
	}
	/**
	 * Remove Component from Composition
	 *
	 * @param {string} id
	 */
	remove(id) {
		this.components = this.components.filter((item, index) => {
			return item.id != id;
		});
	}
	/**
	 * Get Component from Composition
	 *
	 * @param {string} id
	 * @returns {(Component|null)}
	 */
	component(id) {
		for (let i in this.components) {
			if (this.components[i].id == id)
				return this.components[i].component;
		}
		return null;
	}
	/**
	 * Scale quantities of all Components by K
	 *
	 * @param {number} k
	 * @returns {void}
	 */
	scale(k) {
		this.components.forEach((item, index) => {
			item.component.scale(k);
		});
	}
	/**
	 * Scale quantities of all Components to total measure
	 *
	 * @param {number} quantity
	 * @param {MeasureVariant} measure
	 * @returns {void}
	 */
	scaleTo(quantity, measure) {
		let total = this.total(measure),
			k = quantity/total;
		this.scale(k);
	}
	/**
	 * Get total quantity of all Components with a specific measure
	 *
	 * @param {MeasureVariant} measure
	 * @returns {void}
	 */
	total(measure) {
		let result = 0;
		this.components.forEach(({ id, component }, index) => {
			if (! component.is(VirtualIngredient))
				result += component.get(measure);
		});
		return result;
	}
	/**
	 * Get reference info (per 1L)
	 *
	 * @returns {CompositionInfo}
	 */
	reference() {
		let k = 1000 / this.total(Measure.ML);
		this.scale(k);
		let result = this.info();
		this.scale(1 / k);
		return result;
	}
	/**
	 * Get info (for real volume)
	 *
	 * @param {number} [precision]
	 * @returns {CompositionInfo}
	 */
	info(precision) {
		let result = {
			volume: this.total(Measure.ML),
			weight: this.total(Measure.G),
			density: 0,
			abs_spirit: 0,
			abv: 0,
			sugar: 0,
			sugar_content: 0,
			kcal: this.total(Measure.KCAL)
		};
		this.components.forEach(({ id, component }, index) => {
			if (component.is(Alcohol))
				result.abs_spirit += component.get(Measure.ML) * component.get(Measure.VV);
			if (component.is(Syrup))
				result.sugar += component.get(Measure.ML) * component.get(Measure.WV);
			if (component.is(Liqueur)) {
				let info = component.ingredient.composition.info();
				result.sugar += info.sugar_content * component.get(Measure.ML);
				result.abs_spirit += (info.abv * 0.01) * component.get(Measure.ML);
			}
		})

		result.abv = result.abs_spirit / result.volume * 100;
		result.sugar_content = result.sugar / result.volume;
		result.density = result.weight / result.volume;
		if (precision) {
			for (let key in result)
				result[key] = round(result[key], precision);
		}
		return result;
	}
}
/**
 * @typedef {Object} CompositionInfo
 * @property {number} volume - total volume, ml
 * @property {number} weight - total weight, g
 * @property {number} density - density, g/ml
 * @property {number} abs_spirit - absolute spirit, ml
 * @property {number} abv - alcohol by volume, %
 * @property {number} sugar - total sugar, g
 */