/// includes ///
var JsCompiler = require("../js-compiler"),
    fs           = require("fs"),
    path         = require("path"),
    dir          = require("../lib/utils/directory");

/// private variables ///
var enableLog = false;
var enableMinify = false;
var inputIsFolder = false;
var enableSilent = false;
var includeInq = false;
var includeExperimental = false;
var definitions = [];
var input = "";
var output = "";

/// methods ///
function processArguments() {
    var arg;
    for(var i = 2; i < process.argv.length; ++i) {
        arg = process.argv[i];
        if(arg === "-l" || arg === "-log") enableLog = true;
        else if(arg === "-m" || arg === "--minify") enableMinify = true;
        else if(arg === "-f" || arg === "--folder") inputIsFolder = true;
        else if(arg === "-s" || arg === "--silent") enableSilent = true;
        else if(arg === "-i" || arg === "--input") input = process.argv[++i];
        else if(arg === "-o" || arg === "--output") output = process.argv[++i];
        else if(arg === "-e" || arg === "--ensure") includeInq = true;
        else if(arg === "-d" || arg === "--define") definitions = process.argv[++i].split(' ');
        else console.error("[WARNING] Unknown compiler flag: " + arg);
    }

    if(!output) output = input + (inputIsFolder ? "" : ".js");
    return !!input;
}

function walk(dir, ext) {
    var results = [];
    var list = fs.readdirSync(dir);
    list.forEach(function(file) {
        file = dir + '/' + file;
        var stat = fs.statSync(file);
        if (stat && stat.isDirectory()) results = results.concat(walk(file, ext));
        else if(path.extname(file) === ext) results.push(file);
    });
    return results;
}

if(!processArguments()) console.error("[ERROR] You must specify the input using the -i flag.");
else {
    var compiler = new JsCompiler({
        log: enableLog,
        minify: enableMinify,
        definitions: definitions,
        includeInq: includeInq
    });
    compiler.init();

    if(inputIsFolder) {
        var fullInput = path.join(process.cwd(), input);
        var fullOutput = path.join(process.cwd(), output);

        var files = walk(fullInput, ".js");
        !function next(i) {
            if(i < files.length) {
                var file = files[i];

                fs.readFile(file, { encoding: "utf8" }, function (err, content) {
                    if (err) return console.error("[ERROR] " + err);

                    if(!enableSilent) console.log(file);
                    var results = compiler.compile(content);
                    if (results) {
                        var out = path.join(fullOutput, file.substring(fullInput.length));
                        var outDir = path.dirname(out);
                        dir.mkdir(outDir, function() {
                            if (err) return console.error("[ERROR] " + err);
                            fs.writeFile(path.resolve(__dirname, out), results, { encoding: "utf8" }, function (err) {
                                if (err) return console.error("[ERROR] " + err);
                                if (enableLog) console.log("Success. :)");
                                next(i + 1);
                            });
                        });
                    }
                });
            }
            else {
                compiler.stats();
            }
        }(0);
    }
    else {
        fs.readFile(path.resolve(process.cwd(), input), { encoding: "utf8" }, function(err, content) {
            if(err) return console.error("[ERROR] " + err);

            var results = compiler.compile(content);
            if(results) {
                fs.writeFile(path.resolve(process.cwd(), output), results, { encoding: "utf8" }, function(err) {
                    if(err) return console.error("[ERROR] " + err);
                    if(enableLog) console.log("Success. :)");
                });
            }
        });
    }
}


/**
 * Expression Processor for the CellScript Expression Parser.
 */

/// includes ///
var fs   = require("fs"),
    path = require("path");

/// private variables ///
var processors = [];
var processorsNamed = [];
var complexExpressions = [];
var enableLog = false;
var lvl = 0;

/// private methods ///
function tryInit() {
    var temp, i, files;

    if(!processors.length) {
        var processorsPath = path.resolve(__dirname, "processor");
        files = fs.readdirSync(processorsPath);
        for (i = 0; i < files.length; ++i) {
            temp = require(path.resolve(processorsPath, files[i]));
            processors.push(temp);
            processorsNamed[temp.name] = temp;
        }
    }

    if(!complexExpressions.length) {
        var expressionsPath = path.resolve(__dirname, "combination");
        files = fs.readdirSync(expressionsPath);
        for (i = 0; i < files.length; ++i) {
            temp = require(path.resolve(expressionsPath, files[i]));
            complexExpressions[temp.name] = temp.value;
        }
    }
}

function includeParsers(parserDefinitions, log) {
    if(parserDefinitions) {
        for(var parserDefinition in parserDefinitions) {
            if(parserDefinitions.hasOwnProperty(parserDefinition)) {
                var processor = processorsNamed[parserDefinition];
                var parser = parserDefinitions[parserDefinition];

                if(typeof parser.process == "function") {
                    if(processor && !parser.forceOverride) throw "An expression parser with this name is already included: " + parserDefinition;
                    processors.push(parser);
                    processorsNamed[parserDefinition] = parser;

                    if(processor) {
                        processors.splice(processors.indexOf(processor), 1);
                        if(log) console.log("[EXTENSION] Overridden expression parser: " + parserDefinition);
                    }
                    else {
                        if(log) console.log("[EXTENSION] Loaded new expression parser: " + parserDefinition);
                    }
                }
                else {
                    if(!processor || typeof processor.include != "function") throw "Unsupported expression parser definition type: " + parserDefinition;
                    processor.include(parser);
                    if(log) console.log("[EXTENSION] Configured expression parser: " + parserDefinition);
                }
            }
        }
    }
}

function getComplexExpression(input) {
    return complexExpressions[input] || [input];
}

function indent() {
    return new Array(lvl).join(" ");
}

function log() {
    if(enableLog) console.log.apply(this, arguments);
}

function processToken(state, targetedProcessors) {
    if(typeof targetedProcessors === "string") targetedProcessors = getComplexExpression(targetedProcessors);

    var temp, processor, i;
    for (i = 0; i < targetedProcessors.length; ++i) {
        processor = processorsNamed[targetedProcessors[i]];
        if(!processor) return state.error("Missing processor: " + targetedProcessors[i]);
        if (processor.canProcess(state, true)) {
            log(indent(++lvl) + " > " + processor.name);
            temp =  processor.process(state);
            log(indent(lvl--) + " < " + processor.name + " (" + temp + ")");

            return temp;
        }
    }

    return false;
}

/// public interface ///
module.exports = {
    init: tryInit,
    include: includeParsers,
    token: processToken,
    log: function(enable) { enableLog = enable; }
};


/*
 * node-cal
 * nodejs calendar module
 * event class
 *
 * ohnx was here (2015)
 *
 */

'use strict';
var util = require('./util.js');

var exports = module.exports = {};

// CalEvent object
function CalEvent(name, startDate, endDate, description, location, uid) {
    if (startDate instanceof Date) { this.startDate = startDate; } else { return null; }
    if (endDate instanceof Date) { this.endDate = endDate; } else { return null; }
    this.uid = uid == null ? (util.guid() + '@node-cal-event.js') : uid;
    this.summary = name;
    this.location = location;
    this.description = description;
}

function RepeatingCalEvent(event, eventLength, testFunc) {
    if(!util.isValidEvent(event)) return null;
    if(eventLength == null) eventLength = 0;
    if(testFunc == null) return null;
    this.event = event;
    this.eventLength = eventLength;
    this.testFunc = testFunc;
}

var repeatWeekly = function(startDate, date) {
    var timeDiff = Math.abs(date.getTime() - startDate.getTime());
    var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));
    return diffDays % 7 == 0;
};

CalEvent.prototype.toJSON = function() {
    var json;
    json = "{";
    json += "\"uid\" : \"" + util.makeSafe(this.uid) + "\",\n";
    json += "\"summary\" : \"" + util.makeSafe(this.summary) + "\",\n";
    json += "\"location\" : \"" + util.makeSafe(this.location == null ? "" : this.location) + "\",\n";
    json += "\"description\" : \"" + util.makeSafe(this.description == null ? "" : this.description) + "\",\n";
    json += "\"startDate\" : \"" + this.startDate.toISOString() + "\",\n";
    json += "\"endDate\" : \"" + this.endDate.toISOString() + "\"\n";
    json += "}";
    return json;
};

var eventFromJSON = function(json) {
    if(json == null) return null;
    try {
        var keys = JSON.parse(json);
        return new CalEvent
        (
            keys["summary"],
            new Date(keys["startDate"]),
            new Date(keys["endDate"]),
            keys["description"],
            keys["location"],
            keys["uid"]
        );
    } catch (e) {
        return null;
    }
};

exports.CalEvent = CalEvent;
exports.RepeatingCalEvent = RepeatingCalEvent;
exports.repeatWeekly = repeatWeekly;
exports.eventFromJSON = eventFromJSON;


/* jslint esversion: 6 */
const cin = process.stdin;
const cout = process.stdout;
const fnList = {
  prompt: pprompt,
  confirm: pconfirm,
  multiline: pmultiline
};

// Public functions
function ask(questions) {
  "use strict";
  let answers = {};
  let i = 0;

  return new Promise(function(resolve, reject) {
    function askQuestion(item) {
      let key = item.key;
      let msg = item.msg;
      let fn = fnList[item.fn];
      if (!key) {
        throw new Error('A value for `key` must be defined for question '+i);
      }
      if (!msg) {
        throw new Error('A value for `msg` must be defined for question '+i);
      }
      if (!fn) {
        throw new Error('A value for `fn` must be "prompt", "confirm", or "multiline" for question '+i);
      }

      if (fn) {
        fn(msg, key, answers).then(
          function() {
            next();
          }
        );
      }
    }

    function next() {
      if (i < questions.length) {
        var question = questions[i];
        i++;
        askQuestion(question);
      }
      else {
        resolve(answers);
      }
    }

    next();
  });
}

function prompt(msg, multiLine){
  "use strict";
  return new Promise(function(resolve) {
    cout.write(msg);
    cin.setEncoding('utf8');
    if (multiLine) {
      let buf = [];
      cout.write('\n');
      cin.on('data',
        function(val){
          if ('\n' == val || '\r\n' == val) {
            cin.pause();
            cin.removeAllListeners('data');
            resolve(buf.join('\n'));
            buf = null;
          } else {
            buf.push(val.slice(0,-1));
          }
        }
      ).resume();
    }
    else {
      cin.once('data',
        function(val){
          cin.pause();
          resolve(val.slice(0,-1));
        }
      ).resume();
    }
  });
}

function confirm(msg) {
  "use strict";
  return prompt(msg, false).then(
    function(val) {
      return(/^y|yes|ok|true$/i.test(val));
    }
  );
}

function multiline(msg) {
  "use strict";
  return prompt(msg, true);
}

// Private functions
function promiseFn(msg, key, answers, fn) {
  "use strict";
  answers = answers || {};
  return fn(msg).then(
    function(resp) {
      answers[key] = resp;
      return answers;
    }
  );
}

function pprompt(msg, key, answers) {
  return promiseFn(msg, key, answers, prompt);
}

function pconfirm(msg, key, answers) {
  return promiseFn(msg, key, answers, confirm);
}

function pmultiline(msg, key, answers) {
  return promiseFn(msg, key, answers, multiline);
}

module.exports = {
  ask,
  confirm,
  multiline,
  prompt
};


const isReq = require('is-incoming-message')
const isRes = require('is-server-response')
const mapLimit = require('map-limit')
const dezalgo = require('dezalgo')
const assert = require('assert')
const noop = require('noop2')

module.exports = httpMiddleware

// create a middleware stack
// (obj, obj, [fn(obj, obj, fn?)], fn) -> null
function httpMiddleware (req, res, arr, done) {
  done = done || noop

  assert.ok(isReq(req), 'is incoming message')
  assert.ok(isRes(res), 'is server response')
  assert.ok(Array.isArray(arr), 'is array')
  assert.equal(typeof done, 'function', 'is function')

  mapLimit(arr, 1, iterator, done)

  function iterator (fn, next) {
    next = dezalgo(next)
    if (fn.length === 3) return fn(req, res, next)
    fn(req, res)
    next()
  }
}


const { File, FileQueryInfoFlags, FileType } = imports.gi.Gio;
const GLib = imports.gi.GLib;
const toString =
  imports.byteArray.toString(imports.byteArray.fromString("_")) === "_"
    ? imports.byteArray.toString
    : String;

// Define with var, so Gjs native module exports it.
// tslint:disable:no-var-keyword
var Require = class {
  // tslint:enable:no-var-keyword
  constructor() {
    this.File = File;
    this.GLib = GLib;
    this.imports = imports;
    this.window = window;

    const keys = Object.getOwnPropertyNames(Require.prototype);

    /** @type {{ [key: string]: any }} */
    const self = this;

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      const val = self[key];

      if (key !== "constructor" && typeof val === "function") {
        self[key] = val.bind(self);
      }
    }

    /** Regular expression to get current module path from error stack. */
    this.currentModulePath = /^[\s\S]*?\n.*?@(.*?)(?:line\ \d+ > eval)?:[\s\S]*/;

    /**
     * Arrays of required modules, indexed by parent filename.
     * @type {{ [path: string]: string[] }}
     */
    this.dependencies = {};

    /**
     * Object where filenames of the modules that have ever been required are keys.
     * @type {{ [path: string]: true }}
     */
    this.filenames = {};

    /**
     * Creates function from string. Breaks coverage for the module defining it,
     * so defined in `./RequireFun` that is loaded when `require` is available.
     * @type {any}
     */
    this.Fun = undefined;

    /** File system polling interval. */
    this.hmrInterval = 1000;

    /** Delay to make sure file has finished being written. */
    this.hmrTimeout = 1000;

    /**
     * Cache of modules, indexed by filename.
     * @type {{ [path: string]: any }}
     */
    this.modules = {};
  }

  /**
   * Invalidates cache and calls a function when a module file is changed.
   * @param {string} dirname @param {string} path @param {() => void} callback
   */
  accept(dirname, path, callback) {
    const filename = this.resolve(dirname, path);
    /** @type {{ [path: string]: string | undefined }} */ let lastContents = {};
    let isVerifyingChange = false;

    const handleChanged = () => {
      if (isVerifyingChange) {
        return;
      }
      isVerifyingChange = true;

      this.GLib.timeout_add(this.GLib.PRIORITY_DEFAULT, this.hmrTimeout, () => {
        this.flatten(filename).forEach(filenameToRead => {
          if (!isVerifyingChange) {
            return;
          }

          const contents = toString(
            this.GLib.file_get_contents(filenameToRead)[1]
          );

          if (lastContents[filenameToRead] !== contents) {
            lastContents[filenameToRead] = contents;

            this.flatten(filename).forEach(filenameToInvalidate => {
              delete this.modules[filenameToInvalidate];
            });

            isVerifyingChange = false;
            callback();
          }
        });

        isVerifyingChange = false;
        return false;
      });
    };

    this.flatten(filename).forEach(filenameToRead => {
      lastContents[filenameToRead] = toString(
        this.GLib.file_get_contents(filenameToRead)[1]
      );
    });

    this.GLib.timeout_add(this.GLib.PRIORITY_DEFAULT, this.hmrInterval, () => {
      if (isVerifyingChange) {
        return true;
      }

      this.flatten(filename).forEach(filenameToRead => {
        if (isVerifyingChange) {
          return;
        }

        const contents = toString(
          this.GLib.file_get_contents(filenameToRead)[1]
        );

        if (lastContents[filenameToRead] !== contents) {
          handleChanged();
        }
      });

      return true;
    });
  }

  /**
   * Gets pathnames of a cached module and all its dependencies.
   * @param {string} filename
   */
  flatten(filename) {
    let result = [filename];
    let nextResult = null;
    let same = false;

    while (!same) {
      nextResult = result
        .reduce((prev, x) => prev.concat(this.dependencies[x]), result)
        .filter((x, i, a) => a.indexOf(x) === i);

      same = result.length === nextResult.length;
      result = nextResult;
    }

    return result;
  }

  /** Returns environment variables. */
  getEnv() {
    /** @type {string[]} */
    const pairs = this.GLib.get_environ();

    /** @type {{ [name: string]: string }} */
    const env = {};

    for (const pair of pairs) {
      /** @type {any} */
      const match = pair.match(/^([^=]+)=(.*)$/);
      env[match[1]] = match[2];
    }

    return env;
  }

  /**
   * Returns a cached module or creates an empty one. Normalizes the path.
   * @param {string} path
   */
  getOrCreate(path) {
    const gFile = this.File.new_for_path(path);
    const dirname = this.dirname(gFile);
    const filename = gFile.get_path();

    const module =
      this.modules[filename] ||
      (this.modules[filename] = {
        exports: {},
        filename: filename,
        hot: { accept: this.accept.bind(null, dirname) }
      });

    this.filenames[filename] = true;

    if (!this.dependencies[filename]) {
      this.dependencies[filename] = [];
    }

    return module;
  }

  /**
   * Shims __filename, __dirname, exports, module, require, process and
   * console. Just enough to use some CommonJS from npm.
   */
  require() {
    /** @type {any} */
    const window = this.window;

    window.process = { env: this.getEnv() };
    window.console = { error: print, log: print, warn: print };

    Object.defineProperty(window, "__filename", {
      /** Returns the full path to the module that requested it. */
      get: () => {
        const path = String(new Error().stack).replace(
          this.currentModulePath,
          "$1"
        );
        return this.File.new_for_path(path).get_path();
      }
    });

    Object.defineProperty(window, "__dirname", {
      /** Returns the full path to the parent dir of the module that requested it. */
      get: () => {
        const path = String(new Error().stack).replace(
          this.currentModulePath,
          "$1"
        );
        return this.File.new_for_path(path)
          .get_path()
          .replace(/.[^/]+$/, "");
      }
    });

    Object.defineProperty(window, "exports", {
      /**
       * Returns the exports property of the module that requested it. Note: if
       * you refer to exports after reassigning module.exports, this won't behave
       * like CommonJS would.
       */
      get: () => {
        const path = String(new Error().stack).replace(
          this.currentModulePath,
          "$1"
        );
        const module = this.getOrCreate(path);
        return module.exports;
      }
    });

    Object.defineProperty(window, "module", {
      /**
       * Returns the meta object of the module that requested it, so you can
       * replace the default exported object if you really need to.
       */
      get: () => {
        const path = String(new Error().stack).replace(
          this.currentModulePath,
          "$1"
        );
        const module = this.getOrCreate(path);
        return module;
      }
    });

    Object.defineProperty(window, "require", {
      /**
       * Returns the require function bound to filename of the module that
       * requested it.
       */
      get: () => {
        const path = String(new Error().stack).replace(
          this.currentModulePath,
          "$1"
        );
        const gFile = this.File.new_for_path(path);
        const require = this.requireModule.bind(null, this.getOrCreate(path));
        require.cache = this.modules;
        require.resolve = this.resolve.bind(null, this.dirname(gFile));
        return require;
      }
    });

    this.REQUIRE = memoize(this.REQUIRE);
    this.LOAD_AS_FILE = memoize(this.LOAD_AS_FILE);
    this.LOAD_INDEX = memoize(this.LOAD_INDEX);
    this.LOAD_AS_DIRECTORY = memoize(this.LOAD_AS_DIRECTORY);
    this.LOAD_NODE_MODULES = memoize(this.LOAD_NODE_MODULES);
    this.NODE_MODULES_PATHS = memoize(this.NODE_MODULES_PATHS);
    this.IS_FILE = memoize(this.IS_FILE);
    this.JOIN = memoize(this.JOIN);

    this.Fun = require("./RequireFun").RequireFun;
  }

  /** @private @param {string} X */
  IS_FILE(X) {
    const gFile = this.File.new_for_path(X);

    if (!gFile.query_exists(null)) {
      return false;
    }

    const gFileInfo = gFile.query_info(
      "standard::type",
      FileQueryInfoFlags.NONE,
      null
    );

    return gFileInfo.get_file_type() === FileType.REGULAR;
  }

  /** @private @param {string} X */
  LOAD_AS_FILE(X) {
    if (this.IS_FILE(X)) {
      return X;
    }

    if (this.IS_FILE(`${X}.js`)) {
      return `${X}.js`;
    }

    if (this.IS_FILE(`${X}.json`)) {
      return `${X}.json`;
    }
  }

  /** @private @param {string} X */
  LOAD_AS_DIRECTORY(X) {
    if (this.IS_FILE(this.JOIN(X, "package.json"))) {
      const contents = toString(
        this.GLib.file_get_contents(this.JOIN(X, "package.json"))[1]
      );
      const M = this.JOIN(X, JSON.parse(contents).main);
      const result = this.LOAD_AS_FILE(M) || this.LOAD_INDEX(M);

      if (result) {
        return result;
      }
    }

    return this.LOAD_INDEX(X);
  }

  /** @private @param {string} X */
  LOAD_INDEX(X) {
    if (this.IS_FILE(this.JOIN(X, "index.js"))) {
      return this.JOIN(X, "index.js");
    }

    if (this.IS_FILE(this.JOIN(X, "index.json"))) {
      return this.JOIN(X, "index.json");
    }
  }

  /** @private @param {string} X @param {string} START */
  LOAD_NODE_MODULES(X, START) {
    const DIRS = this.NODE_MODULES_PATHS(START);

    for (const DIR of DIRS) {
      const result =
        this.LOAD_AS_FILE(this.JOIN(DIR, X)) ||
        this.LOAD_AS_DIRECTORY(this.JOIN(DIR, X));

      if (result) {
        return result;
      }
    }
  }

  /** @private @param {string} START */
  NODE_MODULES_PATHS(START) {
    const PARTS = START.split("/");
    let I = PARTS.length - 1;
    PARTS[0] = "/";

    /** @type {string[]} */
    const DIRS = [];

    while (I >= 0) {
      if (PARTS[I] !== "node_modules") {
        DIRS.push(
          PARTS.slice(0, I + 1)
            .concat("node_modules")
            .reduce(this.JOIN)
        );
      }

      I = I - 1;
    }

    return DIRS;
  }

  /** @private @param {string} X @param {string} Y */
  JOIN(X, Y) {
    return this.File.new_for_path(X)
      .resolve_relative_path(Y)
      .get_path();
  }

  /**
   * Require(X) from module at path Y.
   * @see https://nodejs.org/api/modules.html#modules_all_together
   * @private
   * @param {string} X
   * @param {string} Y
   */
  REQUIRE(X, Y) {
    if (X[0] === "/") {
      Y = "/"; // filesystem root
    }

    /** @type {string | undefined} */
    let result = undefined;

    if (
      X === "." ||
      X.slice(0, 2) === "./" ||
      X[0] === "/" ||
      X.slice(0, 3) === "../"
    ) {
      result =
        this.LOAD_AS_FILE(this.JOIN(Y, X)) ||
        this.LOAD_AS_DIRECTORY(this.JOIN(Y, X));

      if (result) {
        return result;
      }
    }

    result = this.LOAD_NODE_MODULES(X, Y);

    if (result) {
      return result;
    }

    throw new Error(`Module not found: ${X} in ${Y}`);
  }

  /** @private @param {File} gFile */
  dirname(gFile) {
    const parent = /** @type {File} */ (gFile.get_parent());
    return parent.get_path();
  }

  /**
   * Gets a normalized local pathname. Understands Dot and Dot Dot, or looks into
   * node_modules up to the root. Adds '.js' suffix if omitted.
   * @private @param {string} dirname @param {string} path
   */
  resolve(dirname, path) {
    return this.REQUIRE(path, dirname);
  }

  /**
   * Loads a module by evaluating file contents in a closure. For example, this
   * can be used to require lodash/toString, which Gjs can't import natively. Or
   * to reload a module that has been deleted from cache.
   * @private @param {{ filename: string }} parent @param {string} path
   */
  requireClosure(parent, path) {
    const dirname = this.dirname(this.File.new_for_path(parent.filename));
    const filename = this.resolve(dirname, path);

    if (this.modules[filename]) {
      return this.modules[filename].exports;
    }

    let contents = toString(this.GLib.file_get_contents(filename)[1]);
    const module = this.getOrCreate(filename);

    const require = this.requireModule.bind(null, module);
    require.cache = this.modules;
    require.resolve = this.resolve.bind(null, dirname);

    if (contents.indexOf("//# sourceURL=") === -1) {
      contents += `\n//# sourceURL=${filename}`;
    }
    if (contents[0] === "#") {
      contents = contents.replace(/^.+?\n/, "");
    }

    this.Fun(
      "exports",
      "require",
      "module",
      "__filename",
      "__dirname",
      contents
    )(module.exports, require, module, filename, dirname);

    if (!this.dependencies[parent.filename]) {
      this.dependencies[parent.filename] = [filename];
    } else if (this.dependencies[parent.filename].indexOf(filename) === -1) {
      this.dependencies[parent.filename].push(filename);
    }

    return module.exports;
  }

  /**
   * Loads a module and returns its exports. Caches the module.
   * @private @param {{ filename: string }} parent @param {string} path
   */
  requireModule(parent, path) {
    const dirname = this.dirname(this.File.new_for_path(parent.filename));
    const filename = this.resolve(dirname, path);

    if (this.modules[filename]) {
      return this.modules[filename].exports;
    }

    if (this.filenames[filename]) {
      // The module has been deleted from cache.

      if (this.dependencies[filename]) {
        this.dependencies[filename].splice(0);
      }

      return this.requireClosure(parent, path);
    }

    if (this.GLib.getenv("NODE_ENV") === "production" && this.Fun) {
      // Faster than importing dirs.
      return this.requireClosure(parent, path);
    }

    if (path.slice(-5) === ".json") {
      const contents = toString(this.GLib.file_get_contents(path)[1]);

      return JSON.parse(contents);
    }

    const parts = filename
      .replace(
        this.imports.searchPath[this.imports.searchPath.length - 1] + "/",
        ""
      )
      .replace(/\.js$/, "")
      .split("/");

    if (parts[parts.length - 1] === "toString") {
      return this.requireClosure(parent, path);
    }

    const module = this.getOrCreate(filename);
    let current = this.imports;
    parts.forEach(x => {
      current = current[x];
    });

    if (!this.dependencies[parent.filename]) {
      this.dependencies[parent.filename] = [filename];
    } else if (this.dependencies[parent.filename].indexOf(filename) === -1) {
      this.dependencies[parent.filename].push(filename);
    }

    return module.exports;
  }
};

/**
 * Caches results of unary or binary function.
 * @param {(arg1: any, arg2?: any) => any} fun
 */
function memoize(fun) {
  // Making cache = {} an optional ES6 parameter breaks coverage. Why?

  /** @type {({ [key: string]: any })} */
  const cache = {};

  if (fun.length === 1) {
    return (/** @type {any} */ arg) => {
      if (arg in cache) {
        return cache[arg];
      }

      const result = fun(arg);
      cache[arg] = result;
      return result;
    };
  }

  return (/** @type {any} */ arg1, /** @type {any} */ arg2) => {
    if (cache[arg1] && arg2 in cache[arg1]) {
      return cache[arg1][arg2];
    }

    const result = fun(arg1, arg2);

    if (!cache[arg1]) {
      cache[arg1] = {};
    }

    cache[arg1][arg2] = result;

    return result;
  };
}


/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

/** @module lib/generateDetachTasks */

var _               = require('lodash');
var allParentsIds   = require('./allParentsIds');
var containerStatus = require('./containerStatus');

/**
 * Creates all the task definition to shut down (detach) all instances in origin.
 *
 * @param {planner} planner - the planner is an instance of TaskPlanner
 * @param {object} origin - The origin system definition
 * @param {object} opts - The options
 *
 */
function generateDetachTasks(planner, origin, opts) {

  _.forIn(origin.topology.containers, function(container) {

    var stopSubTask = {
      cmd: 'stop',
      id: container.id,
      parent: container.containedBy
    };

    var removeSubTask = {
      cmd: 'remove',
      id: container.id,
      parent: container.containedBy
    };

    var unlinkSubTask = {
      cmd: 'unlink',
      id: container.id,
      parent: container.containedBy
    };

    var detachOp = {
      preconditions: containerStatus(container, 'running'),
      subTasks: [unlinkSubTask, stopSubTask, removeSubTask]
    };

    var unlinkPreconditions = containerStatus(container, 'running');
    var stopPrecondition = containerStatus(container, 'started');
    var removePrecondition = containerStatus(container, 'added');

    container.contains.forEach(function(contained) {
      var status = containerStatus({
        id: contained
      }, 'started');
      stopPrecondition = _.merge(stopPrecondition, status);

      detachOp.subTasks.splice(1, 0, {
        cmd: 'detach',
        id: contained
      });
    });

    if (opts.mode === 'safe') {
      allParentsIds(origin, container).forEach(function(id) {

        detachOp.subTasks.unshift({
          cmd: 'unlink',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });

        detachOp.subTasks.push({
          cmd: 'link',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });
      });
    }

    planner.addTask({
      cmd: 'detach',
      id: container.id
    }, {
      preconditions: containerStatus(container, 'detached'),
      subTasks: [{ cmd: 'nop' }]
    });

    planner.addTask({
      cmd: 'detach',
      id: container.id
    }, detachOp);

    planner.addTask(unlinkSubTask, {
      preconditions: unlinkPreconditions,
      effects: containerStatus(container, { running: false })
    });

    planner.addTask(unlinkSubTask, {
      preconditions: containerStatus(container, { running: false }),
      subTasks: [{ cmd: 'nop' }]
    });

    planner.addTask(stopSubTask, {
      preconditions: containerStatus(container, 'started'),
      effects: containerStatus(container, 'added')
    });

    planner.addTask(stopSubTask, {
      preconditions: containerStatus(container, {
        running: false,
        started: false
      }),
      subTasks: [{ cmd: 'nop' }]
    });

    planner.addTask(removeSubTask, {
      preconditions: removePrecondition,
      effects: containerStatus(container, 'detached')
    });

    planner.addTask(removeSubTask, {
      preconditions: containerStatus(container, 'detached'),
      subTasks: [{ cmd: 'nop' }]
    });
  });
}

module.exports = generateDetachTasks;


module.exports.keyblade = keyblade

const UndefinedKeyError = module.exports.UndefinedKeyError = require('./UndefinedKeyError')

/**
 * Protects the given object by wrapping it in a Proxy.
 *
 * @param  {object} obj
 * The object to wrap.
 *
 * @param  {(string) => string} opts.message
 * Optional function used to construct the error message.
 * Will get the key passed as the only argument.
 *
 * @param {string[]} ignore
 * Fields to ignore.
 *
 * @param {bool|Function(message: string, key: string)}
 * If specified, and is a function, is invoked before throwing.
 * If specified, and is truthy, `console.error` before throwing.
 *
 * @return {Proxy<object>}
 */
function keyblade (obj, opts) {
  opts = Object.assign({
    message: _defaultMessage,
    logBeforeThrow: true,
    ignore: []
  }, opts)
  opts.ignore = (opts.ignore && Array.isArray(opts.ignore)) ? opts.ignore : []

  return new Proxy(obj, {
    get (target, propKey, receiver) {
      const useGetter = Reflect.has(target, propKey, receiver) || _isReserved(propKey, opts.ignore)
      if (useGetter) {
        return Reflect.get(target, propKey, receiver)
      }

      // Leave symbols alone.
      if (typeof propKey === 'symbol') {
        return Reflect.get(target, propKey, receiver)
      }

      const message = opts.message(propKey)
      if (opts.logBeforeThrow) {
        if (typeof opts.logBeforeThrow === 'function') {
          opts.logBeforeThrow(message, propKey)
        } else {
          console.error(message)
        }
      }

      throw new UndefinedKeyError(message)
    }
  })
}

/**
 * Default message creator.
 *
 * @param  {string} key
 * @return {string}
 */
function _defaultMessage (key) {
  key = String(key)

  return `The key '${key}' does not exist on the object.`
}

const RESERVED_PROPS = [
  'toJSON',
  'toString',
  'inspect',
  '__esModule'
]

/**
 * Determines if the key name is a reserved key.
 *
 * @param  {string}  name
 * @param  {string[]}  additionalFieldsToIgnore
 * @return {Boolean}
 * @api private
 */
function _isReserved (name, additionalFieldsToIgnore) {
  return [].concat(RESERVED_PROPS, additionalFieldsToIgnore).includes(name)
}


/**
*
*	COMPUTE: pcorr
*
*
*	DESCRIPTION:
*		- Computes Pearson product-moment correlation coefficients between one or more numeric arrays.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2014. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2014.
*
*/

'use strict';

// LINEAR CORRELATION //

/**
* FUNCTION: pcorr( arr1[, arr2,...] )
*	Computes Pearson product-moment correlation coefficients between one or more numeric arrays.
*
* @param {...Array} arr - numeric array
* @returns {Array} correlation matrix
*/
function pcorr() {
	var args,
		nArgs,
		len,
		deltas,
		delta,
		means,
		stdevs,
		C,
		cov,
		corr,
		arr,
		N, r, A, B, sum, val, sigma,
		i, j, n;

	args = Array.prototype.slice.call( arguments );
	nArgs = args.length;

	if ( !nArgs ) {
		throw new Error( 'pcorr()::insufficient input arguments. Must provide array arguments.' );
	}
	for ( i = 0; i < nArgs; i++ ) {
		if ( !Array.isArray( args[i] ) ) {
			throw new TypeError( 'pcorr()::invalid input argument. Must provide array arguments.' );
		}
	}
	if ( Array.isArray( args[0][0] ) ) {
		// If the first argument is an array of arrays, calculate the correlation matrix over the nested arrays, disregarding any other arguments...
		args = args[ 0 ];
	}
	nArgs = args.length;
	len = args[ 0 ].length;
	for ( i = 1; i < nArgs; i++ ) {
		if ( args[i].length !== len ) {
			throw new Error( 'pcorr()::invalid input argument. All arrays must have equal length.' );
		}
	}
	// [0] Initialization...
	deltas = new Array( nArgs );
	means = new Array( nArgs );
	stdevs = new Array( nArgs );
	C = new Array( nArgs );
	cov = new Array( nArgs );
	corr = new Array( nArgs );
	for ( i = 0; i < nArgs; i++ ) {
		means[ i ] = args[ i ][ 0 ];
		arr = new Array( nArgs );
		for ( j = 0; j < nArgs; j++ ) {
			arr[ j ] = 0;
		}
		C[ i ] = arr;
		cov[ i ] = arr.slice(); // copy!
		corr[ i ] = arr.slice(); // copy!
	}
	if ( len < 2 ) {
		return corr;
	}
	// [1] Compute the covariance...
	for ( n = 1; n < len; n++ ) {

		N = n + 1;
		r = n / N;

		// [a] Extract the values and compute the deltas...
		for ( i = 0; i < nArgs; i++ ) {
			deltas[ i ] = args[ i ][ n ] - means[ i ];
		}

		// [b] Update the covariance between one array and every other array...
		for ( i = 0; i < nArgs; i++ ) {
			arr = C[ i ];
			delta = deltas[ i ];
			for ( j = i; j < nArgs; j++ ) {
				A = arr[ j ];
				B = r * delta * deltas[ j ];
				sum = A + B;
				// Exploit the fact that the covariance matrix is symmetric...
				if ( i !== j ) {
					C[ j ][ i ] = sum;
				}
				arr[ j ] = sum;
			} // end FOR j
		} // end FOR i

		// [c] Update the means...
		for ( i = 0; i < nArgs; i++ ) {
			means[ i ] += deltas[ i ] / N;
		}
	} // end FOR n

	// [2] Normalize the co-moments...
	n = N - 1;
	for ( i = 0; i < nArgs; i++ ) {
		arr = C[ i ];
		for ( j = i; j < nArgs; j++ ) {
			val = arr[ j ] / n;
			cov[ i ][ j ] = val;
			if ( i !== j ) {
				cov[ j ][ i ] = val;
			}
		}
	}

	// [3] Compute the standard deviations...
	for ( i = 0; i < nArgs; i++ ) {
		// Diagonal elements of covariance matrix...
		stdevs[ i ] = Math.sqrt( cov[i][i] );
	}

	// [4] Set the diagonal elements to 1:
	for ( i = 0; i < nArgs; i++ ) {
		corr[ i ][ i ] = 1;
	}

	// [5] Compute the correlation coefficients...
	for ( i = 0; i < nArgs; i++ ) {
		arr = cov[ i ];
		sigma = stdevs[ i ];
		for ( j = i+1; j < nArgs; j++ ) {
			val = arr[ j ] / ( sigma*stdevs[j] );
			// Address floating point errors introduced by taking the sqrt and enforce strict [-1,1] bounds...
			if ( val > 1 ) {
				val = 1;
			} else if ( val < -1 ) {
				val = -1;
			}
			corr[ i ][ j ] = val;
			corr[ j ][ i ] = val;
		}
	}
	return corr;
} // end FUNCTION pcorr()

// EXPORTS //

module.exports = pcorr;


var debug = require('debug')('carcass:fileRegister');

var fs = require('fs');
var path = require('path');
var mixable = require('mixable-object');

// Copied from bluebird.
function isPrimitive(val) {
  return val == null || val === true || val === false ||
    typeof val === 'string' || typeof val === 'number';
}

/**
 * Get or build a sub register for an object.
 */
function subRegister(obj, name) {
  var res;
  res = isPrimitive(obj[name]) ? {} : obj[name];
  return obj[name] = mixable(res).mixin(proto, 'register', 'extend');
}

/**
 * Try to handle with require().
 */
function registerMod(leaf, dir, name) {
  var modPath;
  try {
    modPath = require.resolve(dir);
  } catch (_error) {}
  if (modPath == null) {
    return false;
  }
  // Define a getter with the base name.
  name = path.basename(name, path.extname(name));
  Object.defineProperty(leaf, name, {
    configurable: true,
    enumerable: true,
    get: function () {
      if (require.cache[modPath] == null) {
        debug('loading %s.', modPath);
      }
      return require(modPath);
    }
  });
  return true;
}

/**
 * Try to handle as a directory.
 */
function registerDir(leaf, dir, name) {
  var files;
  try {
    files = fs.readdirSync(dir);
  } catch (_error) {}
  if (files == null) {
    return false;
  }
  if (name != null) {
    leaf = subRegister(leaf, name);
  }
  for (var i = 0, len = files.length; i < len; i++) {
    name = files[i];
    leaf.register(dir, name);
  }
  return true;
}

/**
 * Register.
 *
 * Map file structure to an object.
 *
 * @param {String} root the path to the file or directory
 * @param {String} ...names the names are not only path to the files but also path
 *   to the attributes
 *
 * @return {this}
 */
function register(root) {
  var leaf = this; // jscs:ignore safeContextKeyword
  // TODO: default base path?
  var dir = path.resolve(root);
  // Try as a directory if no name given.
  if (arguments.length <= 1) {
    registerDir(this, dir);
    return this;
  }
  // The names are not only path to the files but also path to the attributes.
  for (var i = 1, len = arguments.length - 1; i < len; i++) {
    var sub = arguments[i];
    leaf = subRegister(leaf, sub);
    dir = path.resolve(dir, sub);
  }
  // Only the last name is registered (others are used as the path; see above).
  var name = arguments[i++];
  dir = path.resolve(dir, name);
  // Handle with require if possible.
  if (registerMod(leaf, dir, name)) {
    return this;
  }
  // Handle as a directory if possible.
  registerDir(leaf, dir, name);
  return this;
}

// Keep enumerable as they're supposed to be used in real prototypes.
module.exports = {
  register: register,
  // Powered with merge() but for historic reasons named extend().
  extend: mixable.merge
};

// To be used with the sub registers, so put them innumerable.
var proto = Object.defineProperties({}, {
  register: {
    configurable: true,
    enumerable: false,
    value: register
  },
  extend: {
    configurable: true,
    enumerable: false,
    value: mixable.merge
  }
});


/**
 * The [socket.io](http://socket.io/) server which
 * allows any server method (currently limited to sample only)
 * to be consumed as a true stream by the client.
 *
 * This is the socket.io equivalent of `routes/*.js`
 * in express terms.
 */
var io = module.exports = require('socket.io')();
var ss = require('socket.io-stream');

var series = require('async').series;
var _ = require('lodash');
var partial = _.partial;
var assign = _.assign;
var clone = _.clone;
var miss = require('mississippi');
var prettyHrtime = require('pretty-hrtime');

var config = require('mongoscope-config');
var createSampleStream = require('mongodb-collection-sample');
var EJSON = require('mongodb-extended-json');
var unpackNamespaceParam = require('./params/ns');
var typedParams = require('./middleware/typed-params');
var sampleOptions = require('./middleware/sample-options');
var collectionRequired = require('./middleware/collection-required');
var Token = require('./models/token');
var debug = require('debug')('mongodb-scope-server:io');

/**
 * The socket.io version of `lib/moddleware/token-required.js`
 * which will handle validating and decoding authentication
 * tokens the client sends as socket.io messages instead
 * of HTTP headers.
 */
io.on('connection', require('socketio-jwt').authorize({
  secret: config.get('token:secret').toString('utf-8')
}));

/**
 * A small helper which allows us to just use all of our
 * existing `./middleware/*.js` and `./params/*.js`
 * targeted at express with socket.io.  This is in
 * an effort to reuse as much code as possible and
 * lower the probability of "oops" bugs, as in
 * "Oops! I changed it in middleware/ns.js and tested
 * it, but I forgot got to update it in io.js :(".
 *
 * @see https://medium.com/@osklopez/sharing-express-session-data-with-socket-io-190b4237f60e
 *
 * @param {socket.io.Socket} socket - http://socket.io/docs/server-api/#socket
 * @param {Object} req - Request data sent by the client.
 * @param {Function} done
 */
function applyMiddleware(socket, req, done) {
  // A stub for the `res` argument express usually provides.
  var res = {};

  // Map the socket.io request into a `req` like stub as well.
  var params = clone(req);
  assign(params, {
    session_id: socket.decoded_token.session_id,
    deployment_id: socket.decoded_token.deployment_id
  });

  req = {
    params: params,
    query: {},
    body: {}
  };

  // Yay! Now we can just use all of our existing middleware!
  series({
    'Load token data': partial(Token.load, socket.decoded_token, req),
    'Add typed param getters': partial(typedParams, req, res),
    'Parse the namespace param if presented': function(next) {
      unpackNamespaceParam(req, res, next, req.params.ns);
    },
    'The collection must already exist': partial(collectionRequired, req, res),
    'Parse sample options': partial(sampleOptions, req, res)
  }, function(err) {
    if (err) {
      return done(err);
    }
    debug('middleware applied successfully', req);
    done(null, req);
  });
}

function streamer(socket, next) {
  return function(res, params) {
    debug('client sent params `%j`', params);
    applyMiddleware(socket, params, function(err, req) {
      if (err) {
        debug('request did not make it through middleware', err);
        return res.emit('error', err);
      }
      next(req, res);
    });
  };
}

function getCollectionSample(req, res) {
  var d = require('debug')('mongodb-scope-server:io:collection:sample');

  d('parsed options `%j`', req.params.sample_options);

  var db = req.mongo.db(req.params.database_name);
  var collectionName = req.params.collection_name;
  var opts = req.params.sample_options;

  /**
   * We'll serialize using mongodb-extended-json so each
   * document arrives to the client with detailed bson types
   * intact.
   *
   * * @see https://jira.mongodb.org/browse/INT-506
   */
  var serialize = miss.through({objectMode: true}, function(chunk, enc, cb) {
    cb(null, EJSON.serialize(chunk));
  });

  var start = process.hrtime();
  var src = createSampleStream(db, collectionName, opts);

  miss.pipe(src, serialize, res, function(err) {
    var end = process.hrtime(start);
    if (err) {
      d('Error while sampling: ', err);
      return;
    }
    d('Successfully sampled in', prettyHrtime(end, {
      verbose: true
    }));
  });
}

/**
 * Finally, setup the listeners to handle
 * incoming stream requests from the client,
 * applying our middleware to handle authentication and
 * finally connecting the client stream to a readable
 * stream around the node.js driver.
 *
 * @see [mongodb-scope-client#Client.prototype.createReadStream](http://git.io/vGxcW)
 * @see https://www.npmjs.com/package/socket.io-stream
 */
io.on('connection', function(socket) {
  ss(socket).on('collection:sample', streamer(socket, getCollectionSample));
});


/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

/** @module lib/generateConfigureTasks */

var _               = require('lodash');
var allParentsIds   = require('./allParentsIds');
var containerStatus = require('./containerStatus');

/**
 * Creates all the task definition to configure dest based on origin and the opts.
 *
 * @param {planner} planner - the planner is an instance of TaskPlanner
 * @param {object} origin - The origin system definition
 * @param {object} dest - The destination system definition
 * @param {object} opts - The options
 *
 */
function generateConfigureTasks(planner, origin, dest, opts) {

  _.forIn(dest.topology.containers, function(container) {

    var addSubTask = {
      cmd: 'add',
      id: container.id,
      parent: container.containedBy
    };
    var startSubTask = {
      cmd: 'start',
      id: container.id,
      parent: container.containedBy
    };
    var linkSubTask = {
      cmd: 'link',
      id: container.id,
      parent: container.containedBy
    };
    var configureOp = {
      preconditions: containerStatus(container, 'detached'),
      subTasks: [addSubTask, startSubTask, linkSubTask]
    };
    var configureNop = {
      preconditions: containerStatus(container, 'started'),
      subTasks: [{ cmd: 'nop' }]
    };
    var addPreconditions = containerStatus(container, 'detached');
    var linkPreconditions = containerStatus(container, {
      started: true,
      running: false
    });
    var oldContainer = origin.topology.containers[container.id];

    if (oldContainer) {
      // let's detach all removed containers
      oldContainer.contains.filter(function(contained) {
        return container.contains.indexOf(contained) === -1 && !dest.topology.containers[contained];
      }).map(function(contained) {
        // the current contained is NOT included in the dest status
        // so we detach it
        return {
          cmd: 'detach',
          id: contained
        };
      }).reduce(function(list, op) {
        list.push(op);
        return list;
      }, configureNop.subTasks);
    }

    // the children container must be configured before linking
    container.contains.forEach(function(contained) {
      var oldContained = origin.topology.containers[contained];

      linkPreconditions = _.merge(linkPreconditions, containerStatus({
        id: contained,
        containedBy: container.id
      }, 'running'));
      // we need to add those before the link

      if (oldContained && oldContained.containedBy !== container.id) {
        configureOp.subTasks.splice(configureOp.subTasks.length - 1, 0, {
          cmd: 'detach',
          id: contained
        });

        configureNop.subTasks.push({
          cmd: 'detach',
          id: contained
        });
      }

      configureOp.subTasks.splice(configureOp.subTasks.length - 1, 0, {
        cmd: 'configure',
        id: contained
      });

      configureNop.subTasks.push({
        cmd: 'configure',
        id: contained
      });
    });

    if (opts.mode === 'safe' && oldContainer && oldContainer.containedBy !== container.containedBy) {
      // we should unlink the parent before doing anything
      // and link back after
      allParentsIds(origin, container).forEach(function(id) {

        var op = configureOp;
        var nop = configureNop;

        op.subTasks.unshift({
          cmd: 'unlink',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });

        nop.subTasks.unshift({
          cmd: 'unlink',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });

        op.subTasks.push({
          cmd: 'link',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });

        nop.subTasks.push({
          cmd: 'link',
          id: id,
          parent: origin.topology.containers[id].containedBy
        });
      });
    }

    if (opts.mode === 'safe' && configureNop.subTasks.length > 1 && container.containedBy !== container.id) {
      configureNop.subTasks.unshift({
        cmd: 'unlink',
        id: container.id,
        parent: container.containedBy
      });

      configureNop.subTasks.push({
        cmd: 'link',
        id: container.id,
        parent: container.containedBy
      });
    }

    // if a container is already running, there is nothing to do
    planner.addTask({
      cmd: 'configure',
      id: container.id
    }, configureNop);

    // real configure task
    planner.addTask({
      cmd: 'configure',
      id: container.id
    }, configureOp);

    planner.addTask(addSubTask, {
      preconditions: addPreconditions,
      effects: containerStatus(container, 'added')
    });

    planner.addTask(startSubTask, {
      preconditions: containerStatus(container, 'added'),
      effects: containerStatus(container, 'started')
    });

    planner.addTask(linkSubTask, {
      preconditions: containerStatus(container, 'running'),
      subTasks: [{ cmd: 'nop' }]
    });

    planner.addTask(linkSubTask, {
      preconditions: linkPreconditions,
      effects: containerStatus(container, 'running')
    });
  });
}

module.exports = generateConfigureTasks;


/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

/** @module lib/allParentsIds */

/**
 * Returns all parents of the passed container, minus the root.
 *
 * @param {object} context - the system definition in which to perform the search
 * @param {object} container - the container to search
 */
function allParentsIds(context, container, parents) {
  var isLeaf = parents === undefined;

  parents = parents || [];

  // doing this before pushing skips the root
  if (!container.containedBy || container.containedBy === container.id) {
    return parents;
  }

  if (!isLeaf) {
    parents.push(container.id);
  }

  // let's order them by tree order
  return allParentsIds(context, context.topology.containers[container.containedBy], parents);
}

module.exports = allParentsIds;


/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

/** @module lib/containerStatus */

var _ = require('lodash');

/**
 * Creates a container status to be used as a precondition
 *
 * Valid values for the status parameters are:
 * - a string, such as 'detached', 'added', 'started', 'running'
 * - an object, such as { added: true, started: true, running: false }
 *
 * @param {object} original - the container
 * @param {object|string} status - the status to set.
 * @param {object} parent - the parent container's id.
 */
function containerStatus(original, status, parent) {

  var state = {
    topology: {
      containers: {}
    }
  };

  var container = {
    id: original.id
  };

  if (parent === null) {
    // nothing to do
  } else if (parent !== undefined) {
    container.containedBy = parent;
  } else {
    container.containedBy = original.containedBy;
  }

  if (typeof status === 'string') {

    switch(status) {
      case 'detached':
        container.added   = false;
        container.started = false;
        container.running = false;
        break;
      case 'added':
        container.added   = true;
        break;
      case 'started':
        container.started = true;
        break;
      case 'running':
        container.running = true;
        break;

      default:
        throw new Error('unknown state');
    }

  } else {
    _.forIn(status, function(value, key) {
      container[key] = value;
    });
  }

  if (container.added === false) {
    delete container.containedBy;
  }

  state.topology.containers[container.id] = container;

  return state;
}

module.exports = containerStatus;


'use strict';

var EventEmitter = require('events').EventEmitter;
var zmq          = require('zmq');
var mdns         = require('mdns2');
var mout         = require('mout');
var uuid         = require('uuid');
var freeport     = require('./lib/freeport');
var async        = require('async');
var inherits     = require('util').inherits;

// ------------------------------ CONSTRUCTOR ----------------------------------

var One = function (opt) {
    EventEmitter.call(this);

    opt = opt || {};

    // the id of the service that the node will provide
    this._service = opt.service || 'unnamedService';

    // cluster which the node belongs to
    this._cluster = opt.cluster || 'defaultCluster';

    // id of the node
    this._id      = opt.id || uuid.v4();

    // port in which the node will be publishing messages
    this._pubPort = opt.port; // if port is not defined, a free one is used

    // interface in which the node will bind
    this._address = opt.address || '0.0.0.0';

    // status flags
    this._inCluster   = false;
    this._advertising = false;

    // used to store information about the other nodes in the cluster
    this._clusterTopology = {};

    // pub and sub sockets
    this._pub = null;
    this._sub = null;

    // mdns advertiser/browser
    this._ad      = null;
    this._browser = null;

    // information that is used to advertise the service
    this._adInfo  = null;
};

inherits(One, EventEmitter);

// ----------------------------- PUBLIC METHODS --------------------------------

One.prototype.getId = function () {
    return this._id;
};

One.prototype.getCluster = function () {
    return this._cluster;
};

One.prototype.getClusterTopology = function () {
    return this._clusterTopology;
};

One.prototype.inCluster = function () {
    return this._inCluster;
};

One.prototype.advertising = function () {
    return this._advertising;
};

One.prototype.join = function (callback) {
    this._pub = zmq.socket('pub');
    this._sub = zmq.socket('sub');

    // listen to messages on sub
    this._sub.on('message', this._handleMessage.bind(this));

    async.waterfall([
        // find pub port, if none is defined
        function (next) {
            // if port is not defined, find one
            if (!this._pubPort) {
                freeport(function (err, port) {
                    if (err) {
                        return next(err);
                    }

                    this._pubPort = port;

                    next();
                }.bind(this));
            } else {
                next();
            }
        }.bind(this),

        // bind pub port
        function (next) {
            this._pub.bind(this._getBind(this._address, this._pubPort), next);
        }.bind(this),

        // start discovery, so that cluster can find other members of the
        // cluster
        function (next) {
            this._startDiscovery(next);
        }.bind(this)

    ], function (err) {
        if (err) {
            return this._error(err, callback);
        }

        // successfuly joined
        this._inCluster = true;

        // callback + emit join
        this.emit('join', this._cluster);
        callback && process.nextTick(callback.bind(null, null, this._cluster));

    }.bind(this));

    return this;
};

One.prototype.leave = function (callback) {
    var that = this;

    async.waterfal([
        function (next) {
            that._stopDiscovery(next);
        },
        function (next) {
            if (that._advertising) {
                that.stopAdvertise(next);
            } else {
                next();
            }
        },
        function (next) {
            that._sub.removeAllListeners();
            that._sub.close();
            that._pub.close();

            that._sub = null;
            that._pub = null;

            that._inCluster = false;

            next();
        }
    ], function (err) {
        if (err) {
            return that._error(err, callback);
        }

        that._clusterTopology = {};

        // callback + emit
        that._emitter.emit('leave', that._cluster);
        callback && process.nextTick(callback.bind(null, null, that._cluster));
    });

    return that;
};

One.prototype.advertise = function (details, callback) {
    // fix params in case user does not provide details
    if (typeof(details) === 'function') {
        callback = details;
        details  = null;
    }
    details = details || {};

    // banner will be used to announce the service
    var banner = {
        name: this._id,
        txtRecord: {
            cluster: this._cluster
        }
    };

    // mix the details with the banner, so that they also get advertised
    mout.object.mixIn(banner.txtRecord, details);

    // advertise service
    this._ad = mdns.createAdvertisement(mdns.tcp(this._service), this._pubPort, banner, function (err) {
        if (err) {
            return this._error(err, callback);
        }

        this._advertising = true;

        // callback + emit result
        this._adInfo = {
            service: this._service,
            port:    this._pubPort,
            banner:  banner
        };
        this.emit('advertise_start', this._adInfo);
        callback && process.nextTick(callback.bind(null, null, this._adInfo));

    }.bind(this));

    this._ad.start();

    return this;
};

One.prototype.stopAdvertise = function (callback) {
    this._ad.stop();

    this._ad          = null;
    this._advertising = false;

    this.emit('advertise_stop', this._adInfo);
    callback && process.nextTick(callback.bind(null, null, this._adInfo));

    return this;
};

One.prototype.subscribe = function (channel, callback) {
    if (!this._inCluster) {
        return this._error(new Error('Can\'t subscribe while not in cluster'), callback);
    }

    this._assertChanValid(channel, callback);

    this._sub.subscribe(channel + ':'); // ":" added for separating chan from msg

    this.emit('subscribe', channel);
    callback && process.nextTick(callback.bind(null, null, channel));

    return this;
};

One.prototype.unsubscribe = function (channel, callback) {
    if (!this._inCluster) {
        return this._error(new Error('Can\'t unsubscribe while not in cluster'), callback);
    }

    this._sub.unsubscribe(channel + ':'); // ":" added for separating chan from msg

    this.emit('unsubscribe', channel);
    callback && process.nextTick(callback.bind(null, null, channel));

    return this;
};

One.prototype.publish = function (channel, payload) {
    if (!this._inCluster) {
        return this._error(new Error('Can\'t publish while not in cluster'));
    }

    this._assertChanValid(channel);

    this.emit('publish', channel, payload);

    this._pub.send(channel + ':' + payload);

    return this;
};

// ----------------------------- PROTECTED METHODS -----------------------------

One.prototype._startDiscovery = function (callback) {
    this._browser = mdns.createBrowser(mdns.tcp(this._service), {
        resolverSequence: [
            mdns.rst.DNSServiceResolve(),
            mdns.rst.getaddrinfo({
                families: [4]
            }),
            mdns.rst.makeAddressesUnique()
        ]
    });

    this._browser.on('serviceUp', this._handleNodeUp.bind(this));
    this._browser.on('serviceDown', this._handleNodeDown.bind(this));

    this._browser.start();

    callback();

    return this;
};

One.prototype._stopDiscovery = function (callback) {
    this._browser.stop();

    this._browser.removeListener('serviceUp', this._handleNodeUp);
    this._browser.removeListener('serviceDown', this._handleNodeDown);

    this._browser = null;

    callback();

    return this;
};

One.prototype._handleNodeUp = function (banner) {
    // if node already in cluster or belongs to other cluster, ignore
    if (!mout.lang.isObject(this._clusterTopology[banner.name]) &&
        banner.txtRecord.cluster === this._cluster) {

        // add node to this node's perception of the cluster
        var info = {
            id:        banner.name,
            timestamp: (new Date()).toJSON(),
            address:   banner.addresses[0],
            port:      banner.port
        };

        info.details = mout.lang.deepClone(banner.txtRecord);
        delete info.details.cluster;

        this._clusterTopology[banner.name] = info;

        // connect to its pub socket
        this._sub.connect(this._getBind(info.address, info.port));

        this.emit('node_up', info);
    }
};

One.prototype._handleNodeDown = function (banner) {
    // if node was in this cluster's perception of the cluster, remove it
    if (mout.lang.isObject(this._clusterTopology[banner.name])) {
        var info = mout.lang.deepClone(this._clusterTopology[banner.name]);
        delete this._clusterTopology[banner.name];

        this.emit('node_down', info);
    }
};

One.prototype._handleMessage = function (data) {
    data        = data.toString();
    var sepPos  = data.indexOf(':'),
        chan    = data.substr(0, sepPos),
        payload = data.substr(sepPos + 1)
    ;

    this.emit('message', chan, payload);
};

One.prototype._getBind = function (addr, port) {
    return 'tcp://' + addr + ':' + port;
};

One.prototype._assertChanValid = function (channel, callback) {
    if (channel.indexOf(':') > -1) {
        this._error(new Error('Can\'t use commas in channel names'), callback);
    }
};

One.prototype._error = function (err, callback) {
    // note that the error event is only thrown if a callback was not provided
    if (typeof(callback) === 'function') {
        return callback(err);
    }

    this.emit('error', err);
};

// -----------------------------------------------------------------------------

module.exports = One;


const { lstat, readdir } = require('fs');
let makePromise = require('makepromise'); if (makePromise && makePromise.__esModule) makePromise = makePromise.default;
const { join } = require('path');

/**
 * Update information about directory's content with lstat.
 * @param {string} dirPath Path to the root directory
 * @param {!Array<string>} dirContent
 * @returns {Promise<Array<_readDirStructure.File>>} An array with file objects.
 */
async function lstatFiles(dirPath, dirContent) {
  const readFiles = dirContent.map(async (relativePath) => {
    const path = join(dirPath, relativePath)
    const ls = await makePromise(lstat, path)
    return {
      lstat: ls,
      path,
      relativePath,
    }
  })
  const res = await Promise.all(readFiles)
  return res
}

/**
 * Check if lstat result is a directory
 * @param {_readDirStructure.File} lstatRes
 * @param {!fs.Stats} lstatRes.lstat The stats of the item.
 * @param {string} lstatRes.path The full path of the item.
 * @param {string} lstatRes.relativePath The name of the item.
 * @returns {boolean} true if is a directory
 */
const isDirectory = lstatRes => lstatRes.lstat.isDirectory()
/**
 * Check if lstat result is not a directory
 * @param {_readDirStructure.File} lstatRes
 * @param {!fs.Stats} lstatRes.lstat The stats of the item.
 * @param {string} lstatRes.path The full path of the item.
 * @param {string} lstatRes.relativePath The name of the item.
 * @returns {boolean} true if is not a directory
 */
const isNotDirectory = lstatRes => !lstatRes.lstat.isDirectory()

const getType = (lstatRes) => {
  if (lstatRes.lstat.isDirectory()) {
    return 'Directory'
  }
  if (lstatRes.lstat.isFile()) {
    return 'File'
  }
  if (lstatRes.lstat.isSymbolicLink()) {
    return 'SymbolicLink'
  }
}

/**
 * Read a directory, and return its structure as an object. Only `Files`, `Directories` and `Symlinks` are included!
 * @param {string} dirPath Path to the directory.
 * @returns {Promise<_readDirStructure.DirectoryStructure>} An object reflecting the directory structure.
 * @example
```js
const res = await readDirStructure('dir')
// result:
{
  type: 'Directory',
  content: {
    'data.txt': {
      type: 'File'
    },
    subdir: {
      type: 'Directory',
      content: {
        'data-ln.txt': {
          type: 'SymbolicLink'
        },
      }
    }
  }
}
```
 */
               async function readDirStructure(dirPath) {
  if (!dirPath) {
    throw new Error('Please specify a path to the directory')
  }
  const ls = await makePromise(lstat, dirPath)
  if (!ls.isDirectory()) {
    const err = new Error('Path is not a directory')
    err.code = 'ENOTDIR'
    throw err
  }
  const dir = /** @type {!Array<string>} */ (await makePromise(readdir, dirPath))
  const lsr = await lstatFiles(dirPath, dir)

  const directories = lsr.filter(isDirectory) // reduce at once
  const notDirectories = lsr.filter(isNotDirectory)

  const files = notDirectories.reduce((acc, current) => {
    const type = getType(current)
    return {
      ...acc,
      [current.relativePath]: {
        type,
      },
    }
  }, {})

  const dirs = await directories.reduce(async (acc, { path, relativePath }) => {
    const res = await acc
    const structure = await readDirStructure(path)
    return {
      ...res,
      [relativePath]: structure,
    }
  }, {})

  const content = {
    ...files,
    ...dirs,
  }
  return {
    content,
    type: 'Directory',
  }
}

/**
 * After running the `readDirStructure`, this function can be used to flatten the `content` output and return the list of all files (not including symlinks).
 * @param {!_readDirStructure.Content} content The recursive content of the directory.
 * @param {string} path The path to the directory.
 */
       const getFiles = (content, path) => {
  let files = []
  let dirs = []
  Object.keys(content).forEach((key) => {
    const { type } = content[key]
    if (type == 'File') files.push(join(path, key))
    else if (type == 'Directory') dirs.push(key)
  })
  const dirFiles = dirs.reduce((acc, dir) => {
    const { content: c } =
      /** @type {!_readDirStructure.Content} */ (content[dir])
    const f = getFiles(c, join(path, dir))
    return [...acc, ...f]
  }, [])
  return [...files, ...dirFiles]
}

/* typal types/index.xml */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {_readDirStructure.File} File
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {Object} _readDirStructure.File
 * @prop {!fs.Stats} lstat The stats of the item.
 * @prop {string} path The full path of the item.
 * @prop {string} relativePath The name of the item.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {_readDirStructure.Content} Content The recursive content of the directory.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {Object<string, !_readDirStructure.DirectoryStructure>} _readDirStructure.Content The recursive content of the directory.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {_readDirStructure.DirectoryStructure} DirectoryStructure A directory structure representation.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {Object} _readDirStructure.DirectoryStructure A directory structure representation.
 * @prop {string} [type] The type of the item.
 * @prop {!_readDirStructure.Content} [content] The recursive content if the item is a directory.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {import('fs').Stats} fs.Stats
 */

module.exports = readDirStructure
module.exports.getFiles = getFiles


var path = require('path');

var esprima = require('esprima');
var _ = require('lodash');

module.exports = function(sails){
    return {

        defaults: function(config){
            return {
                paths: {
                    policyFactories: path.resolve(config.appPath, 'api/policyFactories')
                }
            };
        },

        configure: function(){

            _.extend(sails.config.paths, {
                policyFactories: path.resolve(sails.config.appPath, sails.config.paths.policyFactories),
            });

            sails.config.policies = this.parsePolicies(sails.config.policies);
        },

        parsePolicies: parsePolicies,
        parseEsprima: parseEsprima,
    };
}

/*
    input [Any]
*/
function parsePolicies(input){
    var type = typeof input;

    if(type === 'object'){
        for(var i in input){
            input[i] = this.parsePolicies(input[i]);
        }

        return input;
    }

    if(type === 'string'){
        var parsedString = esprima.parse(input).body[0].expression;
        return this.parseEsprima(parsedString);
    }

    return input;
}

/*
    input [Esprima Object]
    fromFactory [Boolean] set to true the result will be interpreted by a factory
*/
function parseEsprima(input, fromFactory){

    var type = input.type;

    if(type === esprimaType.value){
        return input.value;
    }

    if(type === esprimaType.policy){

        var policyName = input.name;

        if(fromFactory){
            return require(sails.config.paths.policies + '/' + policyName);
        }

        return policyName;
    }

    if(type === esprimaType.factory){

        var factoryName = input.callee.name;

        try {
            var factory = require(sails.config.paths.policyFactories + '/' + factoryName);
        } catch (e) {
            return require(sails.config.paths.policies + '/' + factoryName);
        }

        var args = input.arguments.map(function(arg){
            return this.parseEsprima(arg, true);
        }, this);

        return factory.apply(this, args);
    }

    throw new Error('esprima type unhandled: ' + type);
}

var esprimaType = {
    value: 'Literal',
    policy: 'Identifier',
    factory: 'CallExpression',
}


/**
 * Function converts attribute name "data-some-value" to "someValue".
 *
 * @param {string} name
 * @returns {string}
 */
'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});
exports.attributeNameToProperty = attributeNameToProperty;
exports.propertyNameToAttribute = propertyNameToAttribute;
/** @function
* @param name */
function attributeNameToProperty(name) {
  var result = name.replace(/^data-/i, '');
  return result.replace(/-(\w)/g, function (match, letter) {
    return letter.toUpperCase();
  });
}

/**
 * Function converts dataset property name "someValue" to "data-some-value".
 *
 * @param {string} name
 * @returns {string}
 */
/** @function
* @param name */
function propertyNameToAttribute(name) {
  var result = name.replace(/([A-Z])/g, function (match, letter) {
    return '-' + letter.toLowerCase();
  });
  return 'data-' + result;
}
//# sourceMappingURL=escapeAttributeName.js.map


/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

'use strict';

/** @module lib/generateCommands */

var _ = require('lodash');

/**
 * Generate the configure and detach commands for all root elements.
 *
 * @param {object} origin - The origin system definition
 * @param {object} dest - The destination system definition
 */
function generateCommands(origin, dest) {
  var destCmds = _.chain(dest.topology.containers)
    .values()
    .filter(function(container) {
      return container.containedBy === container.id || !container.containedBy;
    })
    .map(function(container) {
      return {
        cmd: 'configure',
        id: container.id
      };
    })
    .value();

  var originCmds = _.chain(origin.topology.containers)
    .values()
    .map(function(container) {
      if (!dest.topology.containers[container.id]) {
        return {
          cmd: 'detach',
          id: container.id
        };
      }

      return null;
    }).filter(function(container) {
      return container !== null;
    })
    .value();

  return destCmds.concat(originCmds);
}

module.exports = generateCommands;


import { assert, object, array, any } from 'joi';
import { InternalLogger, Logger, Store, Bootstrapable } from '@eventific/core';
import chalk from 'chalk';
import { get } from 'node-emoji';

var queryManagerOptionsSchema = object().keys({
    aggregates: array().min(1).required(),
    providers: array().optional(),
    store: any().required(),
    viewHandlers: array().min(1).required()
});

var __extends = (undefined && undefined.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (undefined && undefined.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [0, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
// tslint:disable-next-line
var pascalCase = require('pascal-case');
function QueryManager(options) {
    assert(options, queryManagerOptionsSchema);
    return function (Class) {
        return _a = /** @class */ (function (_super) {
                __extends(class_1, _super);
                /* istanbul ignore next */
                function class_1() {
                    var args = [];
                    for (var _i = 0; _i < arguments.length; _i++) {
                        args[_i] = arguments[_i];
                    }
                    var _this = _super.apply(this, args[0].injector.args(Class)) || this;
                    var params = args[0];
                    _this._injector = params.injector;
                    _this._store = params.store;
                    _this._aggregates = params.aggregates;
                    _this._viewHandlers = params.viewHandlers;
                    _this._logger = _this._injector.get(Logger);
                    return _this;
                }
                class_1._Instantiate = function (parentInjector) {
                    var injector = parentInjector.newChildInjector();
                    var store = options.store._CreateStore(injector);
                    for (var _i = 0, _a = (options.providers || []); _i < _a.length; _i++) {
                        var prov = _a[_i];
                        injector.set(prov);
                    }
                    injector.set({ provide: Store, useConstant: store });
                    injector.set({ provide: Logger, useConstant: new InternalLogger(chalk.green(pascalCase('QueryManager'))) });
                    return new this({
                        aggregates: options.aggregates.map(function (a) { return a._InstantiateAggregate(injector); }),
                        injector: injector,
                        store: store,
                        viewHandlers: options.viewHandlers.map(function (v) { return v._InstantiateViewHandler(injector); })
                    });
                };
                class_1.prototype._start = function () {
                    return __awaiter(this, void 0, void 0, function () {
                        var _this = this;
                        var _i, _a, handler, _loop_1, _b, _c, agg;
                        return __generator(this, function (_d) {
                            switch (_d.label) {
                                case 0:
                                    if (!this.onInit) return [3 /*break*/, 2];
                                    return [4 /*yield*/, this.onInit({ injector: this._injector })];
                                case 1:
                                    _d.sent();
                                    _d.label = 2;
                                case 2: return [4 /*yield*/, this._store.start()];
                                case 3:
                                    _d.sent();
                                    _i = 0, _a = this._viewHandlers;
                                    _d.label = 4;
                                case 4:
                                    if (!(_i < _a.length)) return [3 /*break*/, 7];
                                    handler = _a[_i];
                                    return [4 /*yield*/, handler.start()];
                                case 5:
                                    _d.sent();
                                    _d.label = 6;
                                case 6:
                                    _i++;
                                    return [3 /*break*/, 4];
                                case 7:
                                    _loop_1 = function (agg) {
                                        return __generator(this, function (_a) {
                                            switch (_a.label) {
                                                case 0:
                                                    agg.changes$.subscribe(function (event) { return __awaiter(_this, void 0, void 0, function () {
                                                        var stateResult, _i, _a, handler;
                                                        return __generator(this, function (_b) {
                                                            switch (_b.label) {
                                                                case 0: return [4 /*yield*/, agg.getState(event.aggregateId)];
                                                                case 1:
                                                                    stateResult = _b.sent();
                                                                    _i = 0, _a = this._viewHandlers;
                                                                    _b.label = 2;
                                                                case 2:
                                                                    if (!(_i < _a.length)) return [3 /*break*/, 5];
                                                                    handler = _a[_i];
                                                                    return [4 /*yield*/, handler.buildAndPersistView(event.aggregateId, stateResult.state, stateResult.version)];
                                                                case 3:
                                                                    _b.sent();
                                                                    _b.label = 4;
                                                                case 4:
                                                                    _i++;
                                                                    return [3 /*break*/, 2];
                                                                case 5: return [2 /*return*/];
                                                            }
                                                        });
                                                    }); });
                                                    return [4 /*yield*/, agg._start()];
                                                case 1:
                                                    _a.sent();
                                                    return [2 /*return*/];
                                            }
                                        });
                                    };
                                    _b = 0, _c = this._aggregates;
                                    _d.label = 8;
                                case 8:
                                    if (!(_b < _c.length)) return [3 /*break*/, 11];
                                    agg = _c[_b];
                                    return [5 /*yield**/, _loop_1(agg)];
                                case 9:
                                    _d.sent();
                                    _d.label = 10;
                                case 10:
                                    _b++;
                                    return [3 /*break*/, 8];
                                case 11:
                                    this._logger.info("All setup and ready " + get('sparkles'));
                                    return [2 /*return*/];
                            }
                        });
                    });
                };
                return class_1;
            }(Class)), _a.Type = 'QueryManager', _a;
        var _a;
    };
}

var __extends$1 = (undefined && undefined.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var IQueryManager = /** @class */ (function (_super) {
    __extends$1(IQueryManager, _super);
    function IQueryManager() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    return IQueryManager;
}(Bootstrapable));

var __extends$2 = (undefined && undefined.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
// tslint:disable-next-line
var pascalCase$1 = require('pascal-case');
function ViewHandler(options) {
    assert(options, object());
    return function (Class) {
        return _a = /** @class */ (function (_super) {
                __extends$2(class_1, _super);
                function class_1() {
                    var args = [];
                    for (var _i = 0; _i < arguments.length; _i++) {
                        args[_i] = arguments[_i];
                    }
                    var _this = _super.apply(this, args[0].args(Class)) || this;
                    _this.name = options.name;
                    return _this;
                }
                class_1._InstantiateViewHandler = function (parentInjector) {
                    var injector = parentInjector.newChildInjector();
                    injector.set({
                        provide: Logger,
                        useConstant: new InternalLogger(chalk.blue(pascalCase$1(options.name) + "CommandHandler"))
                    });
                    return new this(injector);
                };
                return class_1;
            }(Class)), _a.Name = options.name, _a;
        var _a;
    };
}

var IViewHandler = /** @class */ (function () {
    function IViewHandler() {
    }
    return IViewHandler;
}());

export { QueryManager, IQueryManager, ViewHandler, IViewHandler };


/**
 * Adds extra getters to `req` that are type aware.
 */
var EJSON = require('mongodb-extended-json');
var _ = require('lodash');
var isString = _.isString;

// Because req.param() was deprecated.
function _param(req, key, _default) {
  var src = req.params[key];
  if (src === undefined) {
    src = req.body[key];
  }
  if (src === undefined) {
    src = req.query[key];
  }
  if (src === undefined) {
    src = _default;
  }
  return src;
}

module.exports = function(req, res, next) {
  req.body = req.body || {};
  req.json = function(key, _default) {
    _default = _default || 'null';
    var value = _param(req, key, _default);
    if (isString(value)) {
      return EJSON.parse(value);
    }
    // socket.io has already done JSON.parse
    // so just deflate any values into proper
    // BSON instances.
    return EJSON.deserialize(value);
  };

  req.int = function(key, _default) {
    _default = _default || 0;
    return parseInt(_param(req, key, _default), 10);
  };

  req.boolean = function(key, _default) {
    return req.int(key, _default) === 1;
  };
  next();
};


/*!
 * limon <https://github.com/limonjs/limon>
 *
 * Copyright (c) 2016 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk)
 * Released under the MIT license.
 */

'use strict'

var lazy = require('./utils')

/**
 * > Initialize `Limon` with `input` and `options`.
 * Both are completely optional. You can pass plugins
 * and tokens to `options`.
 *
 * **Example**
 *
 * ```js
 * var Limon = require('limon').Limon
 * var lexer = new Limon('foo bar')
 *
 * // or pass only options
 * var limon = new Limon({ foo: 'bar' })
 * var tokens = limon.tokenize('baz qux')
 * ```
 *
 * @param {String} `input` String value to tokenize, or if object it is assumed `options`.
 * @param {Object} `options` Optional options, use it to pass plugins or tokens.
 * @api public
 */

function Limon (input, options) {
  if (!(this instanceof Limon)) {
    return new Limon(input, options)
  }

  lazy.use(this, {
    fn: function (app, opts) {
      app.options = lazy.utils.extend(app.options, opts)
    }
  })

  this.defaults(input, options)
  this.use(lazy.plugin.prevNext())
}

Limon.prototype.defaults = function defaults (input, options) {
  if (lazy.utils.isBuffer(input)) {
    input = input.toString('utf8')
  }
  if (lazy.utils.isObject(input)) {
    options = input
    input = null
  }
  this.options = lazy.utils.extend({}, this.options, options)
  this.tokens = lazy.utils.isArray(this.tokens) ? this.tokens : []
  this.input = input || this.input
  this.input = typeof this.input === 'string' ? this.input : null
}

Limon.prototype.tokenize = function tokenize (input, options) {
  this.defaults(input, options)

  if (!this.input || this.input.length === 0) {
    throw new TypeError('limon.tokenize: expect `input` be non-empty string or buffer')
  }

  var len = this.input.length
  var i = 0

  while (i < len) {
    this.run(this.input[i], i, this.input)
    i++
  }
  return this.tokens
}

/**
 * Expose `Limon` instance
 *
 * @type {Object}
 * @api private
 */

module.exports = new Limon()

/**
 * Expose `Limon` constructor
 *
 * @type {Function}
 * @api private
 */

module.exports.Limon = Limon