class Utils {
  static escape(text) {
    // https://codereview.stackexchange.com/a/126722
    var table = {
      '<': 'lt',
      '>': 'gt',
      '"': 'quot',
      '\'': 'apos',
      '&': 'amp',
      '\r': '#10',
      '\n': '#13'
    };
    
    return text.toString().replace(/[<>"'\r\n&]/g, (chr) => {
      return '&' + table[chr] + ';';
    });
  };

  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 (!Utils.isObject(objects[0])) {throw 'Cannot merge non-object';}
    if (objects.length === 1) {
      return Utils.mergeObjectsDeep(mergedObject, objects[0]);
    }
    if (!Utils.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 (Utils.isObject(objects[0][key]) && Utils.isObject(objects[1][key])) {
            mergedObject[key] = Utils.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 Utils.mergeObjectsDeep(mergedObject, ...objects.slice(2));
  }

  static deleteProfileAvatarRequest(userId) {
    return new Promise((resolve, reject) => {
      let modalElement = Utils.HTMLToElement(
        `
          <div class="modal">
            <div class="modal-content">
              <h4>Confirm Avatar deletion</h4>
              <p>Do you really want to delete your Avatar?</p>
            </div>
            <div class="modal-footer">
              <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
              <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
            </div>
          </div>
        `
      );
      document.querySelector('#modals').appendChild(modalElement);
      let modal = M.Modal.init(
        modalElement,
        {
          dismissible: false,
          onCloseEnd: () => {
            modal.destroy();
            modalElement.remove();
          }
        }
      );

      let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
      confirmElement.addEventListener('click', (event) => {
        fetch(`/users/${userId}/avatar`, {method: 'DELETE', headers: {Accept: 'application/json'}})
          .then(
            (response) => {
              if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
              if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
              app.flash(`Avatar marked for deletion`);
              resolve(response);
            },
            (response) => {
              app.flash('Something went wrong', 'error');
              reject(response);
            }
          );
      });
      modal.open();
    });
  }

  // static deleteJobRequest(userId, jobId) {
  //   return new Promise((resolve, reject) => {
  //     let job;
  //     try {
  //       job = app.data.users[userId].jobs[jobId];
  //     } catch (error) {
  //       job = {};
  //     }

  //     let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
  //     confirmElement.addEventListener('click', (event) => {
  //       let jobTitle = job?.title;
  //       fetch(`/jobs/${jobId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
  //         .then(
  //           (response) => {
  //             if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
  //             if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
  //             app.flash(`Job "${jobTitle}" marked for deletion`, 'job');
  //             resolve(response);
  //           },
  //           (response) => {
  //             app.flash('Something went wrong', 'error');
  //             reject(response);
  //           }
  //         );
  //     });
  //     modal.open();
  //   });
  // }

  // static getJobLogRequest(userId, jobId) {
  //   return new Promise((resolve, reject) => {
  //     fetch(`/jobs/${jobId}/log`, {method: 'GET', headers: {Accept: 'application/json, text/plain'}})
  //       .then(
  //         (response) => {
  //           if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
  //           if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
  //           return response.text();
  //         },
  //         (response) => {
  //           app.flash('Something went wrong', 'error');
  //           reject(response);
  //         }
  //       )
  //       .then(
  //         (text) => {
  //           let modalElement = Utils.HTMLToElement(
  //             `
  //               <div class="modal">
  //                 <div class="modal-content">
  //                   <h4>Job logs</h4>
  //                   <pre><code>${text}</code></pre>
  //                 </div>
  //                 <div class="modal-footer">
  //                   <a class="btn modal-close waves-effect waves-light">Close</a>
  //                 </div>
  //               </div>
  //             `
  //           );
  //           document.querySelector('#modals').appendChild(modalElement);
  //           let modal = M.Modal.init(
  //             modalElement,
  //             {
  //               onCloseEnd: () => {
  //                 modal.destroy();
  //                 modalElement.remove();
  //               }
  //             }
  //           );
  //           modal.open();
  //           resolve(text);
  //         }
  //       );
  //   });
  // }

  // static restartJobRequest(userId, jobId) {
  //   return new Promise((resolve, reject) => {
  //     let job;
  //     try {
  //       job = app.data.users[userId].jobs[jobId];
  //     } catch (error) {
  //       job = {};
  //     }

  //     let modalElement = Utils.HTMLToElement(
  //       `
  //         <div class="modal">
  //           <div class="modal-content">
  //             <h4>Confirm Job restart</h4>
  //             <p>Do you really want to restart the Job <b>${job?.title}</b>? All Job Results will be permanently deleted.</p>
  //           </div>
  //           <div class="modal-footer">
  //             <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
  //             <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Restart</a>
  //           </div>
  //         </div>
  //       `
  //     );
  //     document.querySelector('#modals').appendChild(modalElement);
  //     let modal = M.Modal.init(
  //       modalElement,
  //       {
  //         dismissible: false,
  //         onCloseEnd: () => {
  //           modal.destroy();
  //           modalElement.remove();
  //         }
  //       }
  //     );

  //     let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
  //     confirmElement.addEventListener('click', (event) => {
  //       let jobTitle = job?.title;
  //       fetch(`/jobs/${jobId}/restart`, {method: 'POST', headers: {Accept: 'application/json'}})
  //         .then(
  //           (response) => {
  //             if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
  //             if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
  //             if (response.status === 409) {app.flash('Conflict', 'error'); reject(response);}
  //             app.flash(`Job "${jobTitle}" restarted.`, 'job');
  //             resolve(response);
  //           },
  //           (response) => {
  //             app.flash('Something went wrong', 'error');
  //             reject(response);
  //           }
  //         );
  //     });
  //     modal.open();
  //   });
  // }

  static deleteUserRequest(userId) {
    return new Promise((resolve, reject) => {
      let user;
      try {
        user = app.data.users[userId];
      } catch (error) {
        user = {};
      }

      let modalElement = Utils.HTMLToElement(
        `
          <div class="modal">
            <div class="modal-content">
              <h4>Confirm User deletion</h4>
              <p>Do you really want to delete the User <b>${user?.username}</b>? All files will be permanently deleted!</p>
            </div>
            <div class="modal-footer">
              <a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
              <a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
            </div>
          </div>
        `
      );
      document.querySelector('#modals').appendChild(modalElement);
      let modal = M.Modal.init(
        modalElement,
        {
          dismissible: false,
          onCloseEnd: () => {
            modal.destroy();
            modalElement.remove();
          }
        }
      );

      let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
      confirmElement.addEventListener('click', (event) => {
        let userName = user?.username;
        fetch(`/users/${userId}`, {method: 'DELETE', headers: {Accept: 'application/json'}})
          .then(
            (response) => {
              if (response.status === 403) {app.flash('Forbidden', 'error'); reject(response);}
              if (response.status === 404) {app.flash('Not Found', 'error'); reject(response);}
              app.flash(`User "${userName}" marked for deletion`);
              resolve(response);
            },
            (response) => {
              app.flash('Something went wrong', 'error');
              reject(response);
            }
          );
      });
      modal.open();
    });
  }
}