base/logic.js

const base = require("./base");
const locales = require("../locales");
require("./ripe");
const ripe = base.ripe;
const LOCALES_BASE = locales.LOCALES_BASE;

/**
 * Adds a new bundle object to the currently set of registered
 * bundles for the the given locale.
 *
 * This registration operation is global and will affect any user
 * of the RIPE SDK package on the current VM.
 *
 * @param {Object} bundle The locale strings bundle that is going to be
 * globally registered.
 * @param {String} locale The ISO 639-1 based locale identifier in the
 * underscore based form to be used in registration.
 */
ripe.Ripe.prototype.addBundle = function(bundle, locale = null) {
    locale = locale === null ? this.locale : locale;
    const _bundle = LOCALES_BASE[locale] || {};
    LOCALES_BASE[locale] = _bundle;
    for (const [key, value] of Object.entries(bundle)) {
        _bundle[key] = value;
    }
};

/**
 * Removes the given set of locale strings from the globally defined
 * locale registry.
 *
 * @param {Object} bundle The locale strings bundle that is going to be
 * globally removed.
 * @param {String} locale The ISO 639-1 based locale identifier in the
 * underscore based form to be used in removal.
 */
ripe.Ripe.prototype.removeBundle = function(bundle, locale = null) {
    locale = locale === null ? this.locale : locale;
    if (LOCALES_BASE[locale] === undefined) return;
    const _bundle = LOCALES_BASE[locale];
    for (const [key, value] of Object.entries(bundle)) {
        _bundle[key] = value;
    }
};

/**
 * Retrieves the complete set of locale bundles currently registered
 * in the global Ripe instance.
 *
 * @returns {Object} An object that maps a certain locale string to
 * an object mapping locale keys to translations.
 */
ripe.Ripe.prototype.getBundles = function() {
    return LOCALES_BASE;
};

/**
 * Retrieves the locale bundle according to the provided locale.
 *
 * @param {String} locale The ISO 639-1 compliant string value that
 * describes the locale to obtain the global bundle.
 * @returns {Object} An object that for the provided locale maps a
 * certain locale string to the translation.
 */
ripe.Ripe.prototype.getBundle = function(locale) {
    return LOCALES_BASE[locale] || {};
};

/**
 * Runs a local based localization, meaning that the data source
 * for the locale strings should be already loaded in memory.
 *
 * This means that the process should be immediate and not async.
 *
 * @param {String} value The base value string that is going to be
 * used for the localization process.
 * @param {String} fallback The fallback string (default) to be used
 * in case no localization is possible.
 * @returns {String} The final localized string or the fallback in
 * case localization is not possible.
 */
ripe.Ripe.prototype.localeLocal = function(
    value,
    fallback = null,
    locale = null,
    fallbackLocale = "en_us"
) {
    fallback = fallback === null ? value : fallback;
    locale = locale === null ? this.locale : locale;
    if (locale && LOCALES_BASE[locale] !== undefined && LOCALES_BASE[locale][value] !== undefined) {
        return LOCALES_BASE[locale][value];
    }
    if (
        fallbackLocale &&
        LOCALES_BASE[fallbackLocale] !== undefined &&
        LOCALES_BASE[fallbackLocale][value] !== undefined
    ) {
        return LOCALES_BASE[fallbackLocale][value];
    }
    return fallback;
};

/**
 * Parses the provided normalized engraving string according to the
 * standard `<name>:<type>` format, using the loaded configurations.
 *
 * The provided string may not be normalized but for that situation
 * typical guessing heuristics are going to be applied.
 *
 * @param {String} engraving The engraving string to be parsed.
 * @param {Array} properties If provided overrides the default loaded
 * config initials strategy for the retrieval of properties definition.
 * @returns {Object} Returns an object with values and valuesM.
 * valuesM is a map with (key = type, value = name) entries for each
 * property defined in the engraving, where "type" is the type of
 * the property (e.g. style) and "name" is the value for that property
 * (e.g. gold) as defined in the engraving parameter. values is a
 * list of (type, name) that respects the order of the properties.
 */
ripe.Ripe.prototype.parseEngraving = function(engraving, properties = null) {
    const { propertyNamesM, propertyTypes } = this.getProperties(properties);

    let values = [];
    const valuesM = {};
    const parts = engraving.length ? engraving.split(".") : [];

    for (let index = 0; index < parts.length; index++) {
        const part = parts[index];
        const slice = part.split(":", 2);
        const name = slice[0];
        let type = propertyTypes.length > index ? propertyTypes[index] : null;
        type = propertyNamesM[name] || type;
        type = slice.length === 2 ? slice[1] : type;
        values.push({ name: name, type: type });
        valuesM[type] = name;
    }

    values = values.sort((a, b) => {
        const typeAIndex = propertyTypes.includes(a.type)
            ? propertyTypes.indexOf(a.type)
            : propertyTypes.length;

        const typeBIndex = propertyTypes.includes(b.type)
            ? propertyTypes.indexOf(b.type)
            : propertyTypes.length;

        return typeAIndex - typeBIndex;
    });

    return {
        values: values,
        valuesM: valuesM
    };
};

/**
 * Gets the loaded config initials properties and returns an object
 * with all the properties types grouped by name and a sorted array of
 * all property types.
 *
 * @param {Array} properties If provided overrides the default loaded
 * config initials strategy for the retrieval of properties definition.
 * @returns {Object} Returns an object with propertyNamesM and
 * propertyTypes. propertyNamesM is a map with (key = name, value = type)
 * entries for each property, where "type" is the type of the property
 * (e.g. style) and "name" is the value for that property (e.g. gold).
 * propertyTypes is an ordered list of types.
 */
ripe.Ripe.prototype.getProperties = function(properties = null) {
    // gathers the complete set of properties for the initials
    // definitions, to be used in the unpack
    properties = properties || this.loadedConfig.initials.properties;

    let propertyTypes = [];
    const propertyNamesM = {};

    for (const property of properties) {
        const type = property.type;
        const name = property.name;
        propertyNamesM[name] = type;
        propertyTypes.push(type);
    }

    propertyTypes = Array.from(new Set(propertyTypes)).sort(
        (a, b) => propertyTypes.indexOf(a) - propertyTypes.indexOf(b)
    );

    return {
        propertyNamesM: propertyNamesM,
        propertyTypes: propertyTypes
    };
};

/**
 * Normalizes the provided engraving (material) value according to the
 * expected standard, producing a sequence of dictionaries containing
 * the resulting property values.
 *
 * @param {String} engraving The engraving to be normalized.
 * @param {Array} properties If provided overrides the default loaded
 * config initials strategy for the retrieval of properties definition.
 * @returns {string} The normalized engraving value.
 */
ripe.Ripe.prototype.normalizeEngraving = function(engraving, properties = null) {
    const { values } = this.parseEngraving(engraving, properties);
    return values.map(v => `${v.name}:${v.type}`).join(".");
};

/**
 * Determines if the model currently loaded (in case there's one)
 * has the provided frame available in spec.
 *
 * Notice that this call does not assure that a render is possible
 * it only determines that according to model's spec it should be
 * possible to render such a frame.
 *
 * @param {String} frame The name of the frame to determine "renderability"
 * according to the {face}-{index} format.
 * @returns {Boolean} If it's possible to render such frame for the
 * currently loaded model.
 */
ripe.Ripe.prototype.hasFrame = function(frame) {
    if (!this.loadedConfig) return true;
    if (!frame) return true;

    const frameP = frame.split("-", 2);
    let [face, index] = frameP.length > 1 ? frameP : [frameP, "0"];
    index = parseInt(index);

    if (!this.loadedConfig.faces_m) return false;
    if (!this.loadedConfig.faces_m[face]) return false;
    if (this.loadedConfig.faces_m[face].frames - 1 < index) return false;

    return true;
};

/**
 * Determines if the model currently loaded (in case there's one) has the
 * provided video name available in spec.
 *
 * Notice that this call does not assure that a video showing is possible
 * it only determines if according to the model's spec it should be
 * possible to get such a video.
 *
 * @param {String} frame The name of the video to determine if it is
 * possible to show that video.
 * @returns {Boolean} If it's possible to show the video for the
 * currently loaded model.
 */
ripe.Ripe.prototype.hasVideo = function(name) {
    if (!this.loadedConfig) return true;
    if (!name) return true;

    if (!this.loadedConfig.videos_m) return false;
    if (!this.loadedConfig.videos_m[name]) return false;

    return true;
};

/**
 * Localizes the given string value taking into account the current
 * brand and model context so that proper prefixes are taking into
 * account for proper build locale usage.
 *
 * @param {String} value The base string value to be used in the localization.
 * @param {Object} owner The localization owner, which should implement the
 * proper localization provider functions. If not provided the default implementation
 * which used the local base values is used instead.
 * @param {Object} options Set of options to control the localization, such as:
 *  - 'brand' - The brand of the model.
 *  - 'model' - The name of the model.
 *  - 'locale' - The ISO-15897 standard locale definition to be used in the
 * localization process.
 *  - 'defaultValue' - The default string value (if any) to be returned in case it's
 * not possible to localize the provided value.
 *  - 'fallback' - If the fallback process should be used, meaning that if the
 * requested locale is not available for the requested model (not part of the
 * list of available locales) the base locale is going to be used instead
 * (fallback process).
 * @returns {String} The final localized string, that takes into account the
 * current model context.
 */
ripe.Ripe.prototype.localeModel = function(
    value,
    owner = null,
    { brand = null, model = null, locale = null, defaultValue = null, fallback = true } = {}
) {
    owner = owner || {
        getLocale: () => this.locale,
        getSupportedLocales: () => Object.keys(LOCALES_BASE),
        hasLocale: (value, locale) => LOCALES_BASE[locale] && Boolean(LOCALES_BASE[locale][value]),
        toLocale: (value, defaultValue, locale, fallback) =>
            this.localeLocal(value, defaultValue, locale, fallback)
    };
    brand = brand === null ? this.brand : brand;
    model = model === null ? this.model : model;
    locale = locale === null ? this.locale : locale;
    return ripe.toLocale(value, brand, model, owner, {
        locale: locale,
        defaultValue: defaultValue,
        fallback: fallback
    });
};

/**
 * Localizes the given part string value.
 *
 * @param {String} color The base string value to be used in the localization.
 * @param {Object} owner The localization owner, which should implement the
 * proper localization provider functions. If not provided the default implementation
 * which used the local base values is used instead.
 * @param {Object} options Set of options to control the localization, such as:
 *  - 'brand' - The brand of the model.
 *  - 'model' - The name of the model.
 *  - 'locale' - The ISO-15897 standard locale definition to be used in the
 * localization process.
 *  - 'defaultValue' - The default string value (if any) to be returned in case it's
 * not possible to localize the provided value.
 *  - 'prefixes' - The list of prefixes that are taking into account for proper build
 * locale usage.
 *  - 'suffixes' - The list of suffixes that are taking into account for proper build
 * locale usage.
 * @returns {String} The final localized string, that takes into account the
 * current model context.
 */
ripe.Ripe.prototype.localePart = function(
    part,
    owner = null,
    {
        brand = null,
        model = null,
        locale = null,
        defaultValue = null,
        prefixes = [],
        suffixes = []
    } = {}
) {
    let value = [];
    value = value.concat(prefixes);
    part && value.push(`parts.${part}`);
    value = value.concat(suffixes);
    return this.localeModel(value, owner, {
        brand: brand,
        model: model,
        locale: locale,
        defaultValue: defaultValue
    });
};

/**
 * Localizes the given material string value.
 *
 * @param {String} color The base string value to be used in the localization.
 * @param {Object} owner The localization owner, which should implement the
 * proper localization provider functions. If not provided the default implementation
 * which used the local base values is used instead.
 * @param {Object} options Set of options to control the localization, such as:
 *  - 'brand' - The brand of the model.
 *  - 'model' - The name of the model.
 *  - 'part' - The name of the part of the model.
 *  - 'locale' - The ISO-15897 standard locale definition to be used in the
 * localization process.
 *  - 'defaultValue' - The default string value (if any) to be returned in case it's
 * not possible to localize the provided value.
 *  - 'prefixes' - The list of prefixes that are taking into account for proper build
 * locale usage.
 *  - 'suffixes' - The list of suffixes that are taking into account for proper build
 * locale usage.
 * @returns {String} The final localized string, that takes into account the
 * current model context.
 */
ripe.Ripe.prototype.localeMaterial = function(
    material,
    owner = null,
    {
        brand = null,
        model = null,
        part = null,
        locale = null,
        defaultValue = null,
        prefixes = [],
        suffixes = []
    } = {}
) {
    let value = [];
    value = value.concat(prefixes);
    part && material && value.push(`materials.${part}.${material}`);
    material && value.push(`materials.${material}`);
    value = value.concat(suffixes);
    return this.localeModel(value, owner, {
        brand: brand,
        model: model,
        locale: locale,
        defaultValue: defaultValue
    });
};

/**
 * Localizes the given color string value.
 *
 * @param {String} color The base string value to be used in the localization.
 * @param {Object} owner The localization owner, which should implement the
 * proper localization provider functions. If not provided the default implementation
 * which used the local base values is used instead.
 * @param {Object} options Set of options to control the localization, such as:
 *  - 'brand' - The brand of the model.
 *  - 'model' - The name of the model.
 *  - 'part' - The name of the part of the model.
 *  - 'material' - The name of the material of the model.
 *  - 'locale' - The ISO-15897 standard locale definition to be used in the
 * localization process.
 *  - 'defaultValue' - The default string value (if any) to be returned in case it's
 * not possible to localize the provided value.
 *  - 'prefixes' - The list of prefixes that are taking into account for proper build
 * locale usage.
 *  - 'suffixes' - The list of suffixes that are taking into account for proper build
 * locale usage.
 * @returns {String} The final localized string, that takes into account the
 * current model context.
 */
ripe.Ripe.prototype.localeColor = function(
    color,
    owner = null,
    {
        brand = null,
        model = null,
        part = null,
        material = null,
        locale = null,
        defaultValue = null,
        prefixes = [],
        suffixes = []
    } = {}
) {
    let value = [];
    value = value.concat(prefixes);
    part && material && color && value.push(`colors.${part}.${material}.${color}`);
    part && color && value.push(`colors.${part}.${color}`);
    material && color && value.push(`colors.${material}.${color}`);
    color && value.push(`colors.${color}`);
    value = value.concat(suffixes);
    return this.localeModel(value, owner, {
        brand: brand,
        model: model,
        locale: locale,
        defaultValue: defaultValue
    });
};

/**
 * Localizes the given property string value.
 *
 * @param {String} color The base string value to be used in the localization.
 * @param {Object} owner The localization owner, which should implement the
 * proper localization provider functions. If not provided the default implementation
 * which used the local base values is used instead.
 * @param {Object} options Set of options to control the localization, such as:
 *  - 'brand' - The brand of the model.
 *  - 'model' - The name of the model.
 *  - 'type' - The type of the property.
 *  - 'locale' - The ISO-15897 standard locale definition to be used in the
 * localization process.
 *  - 'defaultValue' - The default string value (if any) to be returned in case it's
 * not possible to localize the provided value.
 *  - 'prefixes' - The list of prefixes that are taking into account for proper build
 * locale usage.
 *  - 'suffixes' - The list of suffixes that are taking into account for proper build
 * locale usage.
 * @returns {String} The final localized string, that takes into account the
 * current model context.
 */
ripe.Ripe.prototype.localeProperty = function(
    name,
    owner = null,
    {
        brand = null,
        model = null,
        type = null,
        locale = null,
        defaultValue = null,
        prefixes = [],
        suffixes = []
    } = {}
) {
    let value = [];
    value = value.concat(prefixes);
    name && value.push(`properties.${name}`);
    type && name && value.push(`properties.${type}.${name}`);
    value = value.concat(suffixes);
    return this.localeModel(value, owner, {
        brand: brand,
        model: model,
        locale: locale,
        defaultValue: defaultValue
    });
};