text: // Copyright (c) 2021, João Paulo Carreiro
  //
  // Permission to use, copy, modify, and/or distribute this software 
  // for any purpose with or without fee is hereby granted, provided 
  // that the above copyright notice and this permission notice appear 
  // in all copies.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
  // WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 
  // WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 
  // THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 
  // CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
  // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 
  // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
  // WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
  const prelude = require('./../prelude');
  
  module.exports = ((usrConfig) => {
  
      //
      // Default configuration object.
      //
  
      const _dftConfig = {
          key: 'name',
          includeKey: true,
          getProperty: null,
          multiple: false,
          validate: null
      }
  
      //
      // Runtime configuration object.
      // Merge default config with user supplied value.
      //
  
      const _config = prelude.extend(/* deep */ true, {}, _dftConfig, usrConfig)
  
      //
      // Internal mapping datatype.
      //
  
      const _map = new Map()
  
      /**
       * Add a new item to the map/hash. The item is required 
       * to have a property that works as the item key.
       * @param {*} item The item to add.
       * @returns {*} The key that was just added.
       */
      const add = (item) => {
  
          //
          // Validate input.
          //
  
          if (_config.validate) {
              _config.validate(item)
          }
  
          //
          // Extract the key and value from item.
          //
  
          let { [_config.key]: key, ...notKey } = item
  
          //
          // check what should be included in the 
          // value part of the item.
          //
  
          let val = _config.includeKey ? item : notKey
  
          //
          // Add mapping to native object.
          //
  
          if (_config.multiple) {
  
              //
              // Multiple values for a key.
              // Check if the array is already there
              // If so add the value to that array.
              //
  
              if (!_map.has(key)) {
                  _map.set(key, [])
              }
  
              //
              // Add it to the existing array.
              //
  
              _map.get(key).push(val)
          }
          else {
  
              //
              // Single value, set it.
              //
  
              _map.set(item[_config.key], val)
          }
  
          //
          // Return added key to caller.
          //
  
          return key
      }
  
      const load = (lst) => prelude.toArray(lst).map(add).length
  
      const remove = (key, filter) => _config.multiple && filter ? add(key, get(key).filter(e => !filter(e))) : _map.delete(key)
  
      const clear = _map.clear
  
      const get = (key) => {
          const vals = prelude.toArray(_map.get(key)).map(v => _config.getProperty ? v[_config.getProperty] : v)
          return _config.multiple ? vals : vals[0]
      }
  
      const value = (key) => _map.get(key)
  
      const all = () => Array.from(_map.values())
  
      const has = (key) => _map.has(key)
  
      const howMany = (key) => _config.multiple ? _map.get(key).length : (_map.has(key) ? 1 : 0)
  
      //
      // Protocol
      //
  
      return {
          add,
          load,
          remove,
          clear,
          get,
          value,
          all,
          has,
          howMany
      }
  })