'use strict';
const { Attribute, AttributeFormula } = require('./Attribute');
/**
* @property {Map} attributes
*/
class AttributeFactory {
constructor() {
this.attributes = new Map();
}
/**
* @param {string} name
* @param {number} base
* @param {AttributeFormula} formula
*/
add(name, base, formula = null, metadata = {}) {
if (formula && !(formula instanceof AttributeFormula)) {
throw new TypeError('Formula not instance of AttributeFormula');
}
this.attributes.set(name, {
name,
base,
formula,
metadata,
});
}
/**
* @see Map#has
*/
has(name) {
return this.attributes.has(name);
}
/**
* Get a attribute definition. Use `create` if you want an instance of a attribute
* @param {string} name
* @return {object}
*/
get(name) {
return this.attributes.get(name);
}
/**
* @param {string} name
* @param {number} delta
* @return {Attribute}
*/
create(name, base = null, delta = 0) {
if (!this.has(name)) {
throw new RangeError(`No attribute definition found for [${name}]`);
}
const def = this.attributes.get(name);
return new Attribute(name, base || def.base, delta, def.formula, def.metadata);
}
/**
* Make sure there are no circular dependencies between attributes
* @throws Error
*/
validateAttributes() {
const references = [...this.attributes].reduce((acc, [ attrName, { formula } ]) => {
if (!formula) {
return acc;
}
acc[attrName] = formula.requires;
return acc;
}, {});
for (const attrName in references) {
const check = this._checkReferences(attrName, references);
if (Array.isArray(check)) {
const path = check.concat(attrName).join(' -> ');
throw new Error(`Attribute formula for [${attrName}] has circular dependency [${path}]`);
}
}
}
/**
* @private
* @param {string} attr attribute name to check for circular ref
* @param {Object.<string, Array<string>>} references
* @param {Array<string>} stack
* @return bool
*/
_checkReferences(attr, references, stack = []) {
if (stack.includes(attr)) {
return stack;
}
const requires = references[attr];
if (!requires || !requires.length) {
return true;
}
for (const reqAttr of requires) {
const check = this._checkReferences(reqAttr, references, stack.concat(attr));
if (Array.isArray(check)) {
return check;
}
}
return true;
}
}
module.exports = AttributeFactory;