const fs = require('fs');
require('util.promisify/shim')(); // NOTE: shim for native node 8.0 uril.promisify
const util = require('util');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
const YAWN = require('yawn-yaml/cjs');
const path = require('path');
const log = require('../logger').config;
const chokidar = require('chokidar');
const glob = require('glob');
const yamlOrJson = require('js-yaml');
const eventBus = require('../eventBus');
const schemas = require('../schemas');

class Config {
  constructor () {
    this.gatewayConfig = null;
    this.systemConfig = null;
    this.gatewayConfigPath = null;
    this.systemConfigPath = null;

    this.models = {};

    this.watchers = {
      system: null,
      gateway: null
    };

    this.gatewayValidator = schemas.register('config', 'gateway.config', require('./gateway.config.json'));
    this.systemValidator = schemas.register('config', 'system.config', require('./system.config.json'));
  }

  loadSystemConfig () {
    let systemConfigPath = this.systemConfigPath || path.join(process.env.EG_CONFIG_DIR, 'system.config.yml');
    try {
      this.systemConfig = yamlOrJson.load(fs.readFileSync(systemConfigPath));
      this.systemConfigPath = systemConfigPath;
    } catch (err) {
      try {
        systemConfigPath = path.join(process.env.EG_CONFIG_DIR, 'system.config.json');

        this.systemConfig = yamlOrJson.load(fs.readFileSync(systemConfigPath));
        this.systemConfigPath = systemConfigPath;
      } catch (err) {
        log.error(`failed to (re)load system config: ${err}`);
        throw (err);
      }
    }

    const { isValid, error } = this.systemValidator(this.systemConfig);

    if (!isValid) {
      throw new Error(error);
    }

    log.debug('systemConfigPath: ', systemConfigPath);
  }

  loadGatewayConfig () {
    let gatewayConfigPath = this.gatewayConfigPath || path.join(process.env.EG_CONFIG_DIR, 'gateway.config.yml');

    try {
      this.gatewayConfig = yamlOrJson.load(fs.readFileSync(gatewayConfigPath));
      this.gatewayConfigPath = gatewayConfigPath;
    } catch (err) {
      log.warn(err);
      try {
        gatewayConfigPath = path.join(process.env.EG_CONFIG_DIR, 'gateway.config.json');

        this.gatewayConfig = yamlOrJson.load(fs.readFileSync(gatewayConfigPath));
        this.gatewayConfigPath = gatewayConfigPath;
      } catch (err) {
        log.error(`failed to (re)load gateway config: ${err}`);
        throw (err);
      }
    }

    const { isValid, error } = this.gatewayValidator(this.gatewayConfig);

    if (!isValid) {
      throw new Error(error);
    }

    log.debug('gatewayConfigPath: ', gatewayConfigPath);
  }

  loadModels () {
    glob.sync(path.resolve(process.env.EG_CONFIG_DIR, 'models', '*.json')).forEach(module => {
      const name = path.basename(module, '.json');
      this.models[name] = require(module);
      schemas.register('model', name, this.models[name]);
      log.info(`Registered schema for ${name} model.`);
    });
  }

  watch () {
    const watchEvents = ['add', 'change'];

    const watchOptions = {
      awaitWriteFinish: true,
      ignoreInitial: true
    };

    const systemWatcher = chokidar.watch(this.systemConfigPath, watchOptions);
    const gatewayWatcher = chokidar.watch(this.gatewayConfigPath, watchOptions);

    watchEvents.forEach(watchEvent => {
      systemWatcher.on(watchEvent, name => {
        log.info(`${watchEvent} event on ${name} file. Reloading system config ` +
          `file from ${this.systemConfigPath}`);
        this.loadSystemConfig();
        eventBus.emit('hot-reload', { type: 'system', config: this });
      });

      gatewayWatcher.on(watchEvent, name => {
        log.info(`${watchEvent} event on ${name} file. Reloading gateway config ` +
          `file from ${this.gatewayConfigPath}`);
        this.loadGatewayConfig();
        eventBus.emit('hot-reload', { type: 'gateway', config: this });
      });
    });

    this.watchers.system = systemWatcher;
    this.watchers.gateway = gatewayWatcher;

    systemWatcher.on('ready', () => {
      systemWatcher.isReady = true;
    });

    gatewayWatcher.on('ready', () => {
      gatewayWatcher.isReady = true;
    });
  }

  updateGatewayConfig (modifier) {
    return this._updateConfigFile(this.gatewayConfigPath, modifier);
  }
  updateSystemConfig (modifier) {
    return this._updateConfigFile(this.systemConfigPath, modifier);
  }

  // this function can preserve comments and formatting.
  // due to fragile nature of YAWN it may not work properly in that mode.
  // safe way is to use it in rewrite file mode
  _updateConfigFile (path, modifier) {
    return readFile(path, 'utf8').then(data => {
      let text;
      const isJSON = path.toLowerCase().endsWith('.json');
      if (this.systemConfig && this.systemConfig.preserveStructureOnUpdates && !isJSON) {
        const yawn = new YAWN(data);
        yawn.json = modifier(yawn.json);
        text = yawn.yaml;
      } else { // js-yaml can handle JSON as well as YAML if no need for structure\comments save
        const json = yamlOrJson.load(data);
        const result = modifier(json);
        text = yamlOrJson.dump(result);
      }
      return writeFile(path, text);
    });
  }
}

module.exports = Config;

@@ -1016,32 +1016,54 @@
 stemConfig () %7B%0A
+    let systemConfig;%0A
     let systemCo
@@ -1153,24 +1153,25 @@
 nfig.yml');%0A
+%0A
     try %7B%0A  
@@ -1170,29 +1170,24 @@
 try %7B%0A      
-this.
 systemConfig
@@ -1245,56 +1245,8 @@
 ));%0A
-      this.systemConfigPath = systemConfigPath;%0A
     
@@ -1361,37 +1361,32 @@
 son');%0A%0A        
-this.
 systemConfig = y
@@ -1440,58 +1440,8 @@
 ));%0A
-        this.systemConfigPath = systemConfigPath;%0A
     
@@ -1609,21 +1609,16 @@
 lidator(
-this.
 systemCo
@@ -1674,32 +1674,116 @@
 (error);%0A    %7D%0A%0A
+    this.systemConfig = systemConfig;%0A    this.systemConfigPath = systemConfigPath;%0A
     log.debug('s
@@ -1956,32 +1956,55 @@
 ay.config.yml');
+%0A    let gatewayConfig;
 %0A%0A    try %7B%0A    
@@ -2001,29 +2001,24 @@
 try %7B%0A      
-this.
 gatewayConfi
@@ -2078,58 +2078,8 @@
 ));%0A
-      this.gatewayConfigPath = gatewayConfigPath;%0A
     
@@ -2217,37 +2217,32 @@
 son');%0A%0A        
-this.
 gatewayConfig = 
@@ -2298,60 +2298,8 @@
 ));%0A
-        this.gatewayConfigPath = gatewayConfigPath;%0A
     
@@ -2469,21 +2469,16 @@
 lidator(
-this.
 gatewayC
@@ -2539,24 +2539,112 @@
 or);%0A    %7D%0A%0A
+    this.gatewayConfig = gatewayConfig;%0A    this.gatewayConfigPath = gatewayConfigPath;%0A
     log.debu

Make sure to pass validation before replace the config