Source: wk/util/concretize.js

/**
 * @author    Wolfgang Kowarschick <kowa@hs-augsburg.de>
 * @copyright 2017-2018
 * @license   CC-BY-NC-SA-4.0
 */

/**
 * @deprecated use <code>{@link module:wk/config/concretize}</code> instead
 */
/**
 * @module wk/util/concretize
 */

import stringToHex from '/wk_pixi/util/stringToHex';

/** @private */
const C_EMPTY = Object.freeze({});

/**
 * @function
 * @static
 *
 * @description
 * Computes a concrete value, if a template value is passed.
 * Otherwise the original value is returned.
 *
 * Templates are:
 * <ul>
 *   <li>
 *     An array with at least two elements
 *     whose first element is <code>"@some"</code>.
 *     For such an array some of its other elements
 *     is returned randomly.
 *   </li>
 *   <li>
 *     An object that contains two special attributes
 *     named <code>"@min"</code> and <code>"@max"</code>.
 *     The values of those attributes have to be numbers.
 *     Returned is a random value within the interval
 *     defined by <code>"@min"</code> and <code>"@max"</code>.
 *
 *     There are further attributes that affect the computation
 *     of the random value:
 *
 *     <ul>
 *       <li><code>"@integer": true/false</code>:
 *         If true, an integer value is computed within the interval
 *         [@min, @max]. If false, a float value is computed
 *         within the interval [@min, @max[.
 *       </li>
 *       <li><code>"@positive": value</code>:
 *         The value has to be a number greater or equal 0 and less or equal 1.
 *         The randomly computed number is with a probability of
 *         <code>value</code> a member of [@min, @max] or [@min, @max[ resp.
 *         and with a probability of <code>1-value</code>
 *         a member of [-@max, -@min] or [-@max, -@min[.
 *       </li>
 *       <li><code>"@relative": "name" (a string value)</code>:
 *         The computed value is multiplied with the environment value
 *         <code>p_environment[name]</code>, if that value exists.
 *       </li>
 *     </ul>
 *   </li>
 *   <li><code>{"@stringToHeX": "#AFFE00"}</code>:
 *      A six byte color key is converted into the corresponding
 *      integer code of that color. This is needed by PixiJS, as that library
 *      can only deal with integer color codes instead of string color codes.
 *      Hex values, such as 0xAFFE00 which are accepted by PixiJS,
 *      on the other sides, cannot be stored in JSON-Files....
 *   </li>
 *   <li><code>"@@NAME"</code>:
 *      Two @-symbols at the beginning of a key name (including
 *      <code>"@@some"</code>) are replace by one. So, those keys are
 *      not concretized by the first invocation of this function
 *      but by the second. If the key name starts with more then
 *      two @-symbols, <code>concretize</code> must applied correspondingly often to
 *      concretize the value associated.
 *   </li>
 * </ul>
 *
 * @param {Object} p_config
 *   An object which may contain a @-values that have to be replaced
 *   by concrete values.
 * @param {Object} p_environment
 *   An object containing base values which can be referred by @-values.
 *   If such a value is a function, it is called to compute the base
 *   value. The current element in the array and its position are passed
 *   to that function.
 * @param {Array.<String>} p_specials
 *   If not null, only those specials are expanded that are member of the
 *   array <code>p_specials</code>.
 * @param {?number} p_level
 *   The number of levels that shall be concretized recursively.
 * @param {?object} p_info = null
 *   (used internally only; see <code>p_environment</code>)
 * @return {*}
 *   The computed value if one has been computed,
 *   <code>p_config</code> otherwise.
 */
function concretize({config:      p_config,
                     environment: p_environment = C_EMPTY,
                     specials:    p_specials    = null,
                     level:       p_level       = null,
                     info:        p_info        = C_EMPTY
                   })
{ const
    f_is_special = p_name => p_specials == null || p_specials.includes(p_name);

  let l_level = Number.isFinite(p_level) ? p_level-1 : p_level;

  if (Number.isFinite(p_level) && p_level <= 0)
  { return p_config; }

  if (typeof p_config === 'string' || p_config instanceof String)
  { return(p_config.charAt(0) === '@' && f_is_special(p_config))
            ? (p_config.charAt(1) !== '@')
                ? (typeof p_environment[p_config] === 'function'
                    ? p_environment[p_config](p_info)
                    : p_environment[p_config]
                  )
                : p_config.substr(1)
            : p_config;
  }
  
  if (p_config instanceof Array)
  { let l_first =  p_config[0];
  
    if (   (typeof l_first === 'string' || l_first instanceof String)
        && (l_first.charAt(1) === '@' && l_first.charAt(0) === '@')
        && f_is_special(l_first)
       )
    { return [l_first.substr(1), ...[...p_config].shift()];}
    
    if (l_first === '@some' && f_is_special('@some'))
    { return concretize
             ({config:      p_config[Math.floor(Math.random()*(p_config.length-1))+1],
               environment: p_environment,
               specials:    p_specials,
               level:       l_level,
               info:        p_info
             });
    }
    
    const c_result = [];
    for (let i = 0, n = p_config.length; i < n; i++)
    { let
        l_element = p_config[i],
        l_number  = 1;

      if (   f_is_special('@number')
          && l_element instanceof Object
          && Number.isFinite(l_element['@number'])
         )
      { l_number  = l_element['@number'];
        l_element = {...l_element};
        delete l_element['@number'];
      }

      for (let j = 0; j < l_number; j++)
      { c_result.push(concretize({config:      l_element,
                                  environment: p_environment,
                                  specials:    p_specials,
                                  level:       l_level,
                                  info:        { element:    l_element,
                                                 number:     l_number,
                                                 pos:        j,
                                                 posSource:  i,
                                                 posResult:  i*l_number+j
                                               }
                                })
                     );
      }
    }
    return c_result;
  }

  if (p_config instanceof Object)
  { if (p_config['@stringToHex'] != null && f_is_special('@stringToHex'))
    { const
        c_stringToHex = concretize({config:      p_config['@stringToHex'],
                                    environment: p_environment,
                                    specials:    p_specials,
                                    level:       l_level,
                                    info:        p_info
                                  });

      if (   typeof c_stringToHex === 'string' || c_stringToHex instanceof String
          || (Number.isFinite(c_stringToHex) && c_stringToHex >= 0)
         )
        return stringToHex(c_stringToHex);
    }

    if (f_is_special('@min') || f_is_special('@max'))
    { const
        c_min = concretize({config:      p_config['@min'],
                            environment: p_environment,
                            specials:    p_specials,
                            level:       l_level,
                            info:        p_info
                          }),
        c_max = concretize({config:      p_config['@max'],
                            environment: p_environment,
                            specials:    p_specials,
                            level:       l_level,
                            info:        p_info
                          });

      if (Number.isFinite(c_min) && Number.isFinite(c_max))
      { const
          c_is_int = (p_config['@integer'] === true),
          c_is_relative = p_config['@relative'],
          c_positive = p_config['@positive'];
        let
          l_result;

        if (c_is_int)
          l_result = Math.floor(c_min + Math.random() * (c_max + 1 - c_min));
        else
          l_result = c_min + Math.random() * (c_max - c_min);

        if (Number.isFinite(c_positive))
          l_result *= (Math.random() >= c_positive) ? 1 : -1;

        if (   c_is_relative
            && p_environment !== null
            && p_environment[c_is_relative] !== null
           )
        { return l_result * (p_environment[c_is_relative]); }
        else
        {  return l_result; }
      }
    }

    const c_result = {};

    for (let l_key of Object.keys(p_config))
      if ( l_key.charAt(0) !== '@' ||
           (l_key.charAt(1) === '@' && f_is_special(l_key))
         )
        c_result[l_key.charAt(1) === '@' ? l_key.substring(1) : l_key]
          = concretize({config:      p_config[l_key],
                        environment: p_environment,
                        specials:    p_specials,
                        level:       l_level,
                        info:        p_info
                      });
    return c_result;
  }

  return p_config;
}

export {concretize};
export default concretize;