const fs = require('fs');
const path = require('path');

class UTI {
  constructor(name, conforms) {
    Object.defineProperty(this, 'name', {
      value: name
    });

    Object.defineProperty(this, 'conforms', {
      value: conforms
    });
  }

  conformsTo(other) {
    if (this === other || this.conforms.has(other)) {
      return true;
    }

    for (const u of this.conforms) {
      if (u.conformsTo(other)) {
        return true;
      }
    }
    return false;
  }

  toString() {
    return this.name;
  }

  /**
   * Deliver JSON representation of the UTI.
   * Sample result
   * {
   *   "name": "myUTI",
   *   "conformsTo": [ "uti1", "uti2"]
   * }
   * @return json representation of the UTI
   */
  toJSON() {
    return {
      name: this.name,
      conforms: this.conforms
    };
  }
}

export class UTIController {
  constructor() {
    Object.defineProperty(this, 'registry', {
      value: new Map()
    });
    Object.defineProperty(this, 'utiByMimeType', {
      value: new Map()
    });
    Object.defineProperty(this, 'utiByFileNameExtension', {
      value: new Map()
    });
  }

  /**
   * Initialized the uti api.
   * @param options {object} options.definitionFileName is given then additional UTIs will be loaded from the given file name
   * @return {Promise} a promise that is fullfilled when the initialization is done
   */
  async initializeBuildin() {
    return this.loadDefinitionsFromFile(
      path.join(__dirname, '..', 'publicUTI.json')
    );
  }

  /**
   * Load UTIs form a file.
   * @param fileName {string} file containing UTI definitions
   * @return {Promise} a promise that resolves after the UTIs have been registered.
   */
  async loadDefinitionsFromFile(fileName) {
    return new Promise((resolve, reject) => {
      fs.readFile(
        fileName,
        {
          encoding: 'utf-8'
        },
        (error, data) => {
          if (error) {
            reject(error);
            return;
          }
          try {
            resolve(this.loadDefinitions(data));
          } catch (err) {
            reject(err);
          }
        }
      );
    });
  }

  /**
   * Loads additionnal uti defintions from a (json) string
   * @param {String} data
   */
  async loadDefinitions(data) {
    for (const u of JSON.parse(data)) {
      if (u.fileNameExtension !== undefined) {
        this.assignExtensions(
          u.name,
          Array.isArray(u.fileNameExtension)
            ? u.fileNameExtension
            : [u.fileNameExtension]
        );
      }

      if (u.mimeType !== undefined) {
        this.assignMimeTypes(
          u.name,
          Array.isArray(u.mimeType) ? u.mimeType : [u.mimeType]
        );
      }

      const conforms = new Set();

      if (u.conformsTo !== undefined) {
        const ct = Array.isArray(u.conformsTo) ? u.conformsTo : [u.conformsTo];

        ct.forEach(name => {
          const aUTI = this.getUTI(name);
          if (aUTI === undefined) {
            throw new Error(`Referenced UTI not known: ${name}`);
          } else {
            conforms.add(aUTI);
          }
        });
      }

      const nu = new UTI(u.name, conforms);

      this.registry.set(nu.name, nu);
    }
  }

  /**
   * Lookup a given UTI.
   * @param name {string} UTI
   * @return {string} UTI for the given name or undefined if UTI is not present.
   */
  getUTI(name) {
    return this.registry.get(name);
  }

  /**
   * Lookup a UTIs for a mime type.
   * @param mimeType {string} mime type to get UTIs for
   * @return {string}UTI for the given mime type or undefined if no UTI is registerd for the mime type
   */
  getUTIsForMimeType(mimeType) {
    return this.utiByMimeType.get(mimeType);
  }

  /*
   * Lookup a UTI for a file name.
   * First the file name extension is extracted.
   * Then a lookup in the reistered UTIs for file name extions is executed.
   * @param fileName {string} file to detect UTI for
   * @return {string} UTI for the given fileName or undefined if no UTI is registerd for the file names extension
   */
  getUTIsForFileName(fileName) {
    const m = fileName.match(/(\.[a-zA-Z_0-9]+)$/);
    return m ? this.utiByFileNameExtension.get(m[1]) : undefined;
  }

  /**
   * Check whenever two UTI are conformant.
   * If a conforms to b and b conforms to c then a also conforms to c.
   * @param a {string} first UTI
   * @param b {string} second UTI
   * @return {boolean} true if UTI a conforms to UTI b.
   */
  conformsTo(a, b) {
    const ua = this.registry.get(a);
    return ua === undefined ? false : ua.conformsTo(this.registry.get(b));
  }

  assignMimeTypes(name, mimTypes) {
    mimTypes.forEach(type => {
      const u = this.utiByMimeType.get(type);
      if (u === undefined) {
        this.utiByMimeType.set(type, [name]);
      } else {
        u.push(name);
      }
    });
  }

  assignExtensions(name, extensions) {
    extensions.forEach(ext => {
      const e = this.utiByFileNameExtension.get(ext);
      if (e === undefined) {
        this.utiByFileNameExtension.set(ext, [name]);
      } else {
        e.push(name);
      }
    });
  }
}

@@ -48,16 +48,55 @@
 'path');
+%0Aconst %7B promisify %7D = require('util');
 %0A%0Aclass 
@@ -1778,49 +1778,52 @@
 urn 
-new Promise((resolve, reject) =%3E %7B%0A      
+this.loadDefinitions(%0A      await promisify(
 fs.r
@@ -1829,26 +1829,18 @@
 readFile
+)
 (
-%0A        
 fileName
@@ -1844,21 +1844,11 @@
 ame,
-%0A       
  %7B%0A
-  
     
@@ -1879,274 +1879,15 @@
     
-  %7D,%0A        (error, data) =%3E %7B%0A          if (error) %7B%0A            reject(error);%0A            return;%0A          %7D%0A          try %7B%0A            resolve(this.loadDefinitions(data));%0A          %7D catch (err) %7B%0A            reject(err);%0A          %7D%0A        %7D%0A      );%0A    %7D
+%7D)%0A    
 );%0A 

make use of util/promisify