nopaque.Utils = class Utils {
  static escape(text) {
    // https://codereview.stackexchange.com/a/126722
    let lookup = {
      '<': 'lt',
      '>': 'gt',
      '"': 'quot',
      '\'': 'apos',
      '&': 'amp',
      '\r': '#10',
      '\n': '#13'
    };

    return text.toString().replace(/[<>"'\r\n&]/g, (chr) => {
      return '&' + lookup[chr] + ';';
    });
  }

  static unescape(escapedText) {
    let lookup = {
      'lt': '<',
      'gt': '>',
      'quot': '"',
      'apos': "'",
      'amp': '&',
      '#10': '\r',
      '#13': '\n'
    };
    
    return escapedText.replace(/&(#?\w+);/g, (match, entity) => {
      if (lookup.hasOwnProperty(entity)) {
        return lookup[entity];
      }
      
      return match;
    });
  }

  static HTMLToElement(HTMLString) {
    let templateElement = document.createElement('template');
    templateElement.innerHTML = HTMLString.trim();
    return templateElement.content.firstChild;
  }

  static generateElementId(prefix='', suffix='') {
    for (let i = 0; true; i++) {
      if (document.querySelector(`#${prefix}${i}${suffix}`) !== null) {continue;}
      return `${prefix}${i}${suffix}`;
    }
  }

  static isObject(object) {
    return object !== null && typeof object === 'object' && !Array.isArray(object);
  }

  static mergeObjectsDeep(...objects) {
    let mergedObject = {};
    if (objects.length === 0) {
      return mergedObject;
    }
    if (!this.isObject(objects[0])) {throw 'Cannot merge non-object';}
    if (objects.length === 1) {
      return this.mergeObjectsDeep(mergedObject, objects[0]);
    }
    if (!this.isObject(objects[1])) {throw 'Cannot merge non-object';}
    for (let key in objects[0]) {
      if (objects[0].hasOwnProperty(key)) {
        if (objects[1].hasOwnProperty(key)) {
          if (this.isObject(objects[0][key]) && this.isObject(objects[1][key])) {
            mergedObject[key] = this.mergeObjectsDeep(objects[0][key], objects[1][key]);
          } else {
            mergedObject[key] = objects[1][key];
          }
        } else {
          mergedObject[key] = objects[0][key];
        }
      }
    }
    for (let key in objects[1]) {
      if (objects[1].hasOwnProperty(key)) {
        if (!objects[0].hasOwnProperty(key)) {
          mergedObject[key] = objects[1][key];
        }
      }
    }
    if (objects.length === 2) {
      return mergedObject;
    }
    return this.mergeObjectsDeep(mergedObject, ...objects.slice(2));
  }
}