/*
 * grunt-dist
 *
 *
 * Copyright (c) 2015 jaywcjlove
 * Licensed under the MIT license.
 */
'use strict';

var fs = require('fs');
var clc = require('cli-color');
var UglifyJS = require("uglify-js");
var root = __dirname + '/'

require('shelljs/make');

//路径切换到项目根目录
// root = exec('pwd').output.replace(/\s+$/,'/')

//压缩
function compress(file,file_nim,file_gz){
    // var zlib = require('zlib')
    // var gzip = zlib.createGzip();
    // var fs = require('fs');
    // var inp = fs.createReadStream('dist/JSLite.js');
    // var out = fs.createWriteStream('build/JSLite.js.gz');

    // inp.pipe(gzip).pipe(out);
}

function build(tofile,path){
    var dist,url = fileext(path)
    // 生成dist
    mkdir('-p', tofile);

    root = process.cwd()+'/';
    //在dist中生成原生文件
    dist = cat(path).replace(/\n{3,}/g, "\n\n");
    dist.to(tofile + '/' + url);
    report_size(tofile + '/' + url);

    // 文件压缩代码压缩
    // 使用插件 [UglifyJS 2](https://github.com/mishoo/UglifyJS2)
    //在dist中生成 map 和 min 文件
    var result = UglifyJS.minify([root+tofile + '/' + url],{
        outSourceMap: fileext('map',path),
        sourceRoot:'http://jslite.io'
    });

    result.code.to(tofile + '/' + fileext('min',path));
    report_size(tofile + '/' + fileext('min',path));
    result.map.to(tofile + '/' + fileext('map',path));
    report_size(tofile + '/' + fileext('map',path));

    // compress(tofile + '/' + url,tofile + '/' + fileext('min',path),tofile + '/' + fileext('gz',path))
}

module.exports = function(grunt) {

    // Please see the Grunt documentation for more information regarding task
    // creation: http://gruntjs.com/creating-tasks

    grunt.registerMultiTask('dist', 'The best Grunt plugin ever.', function() {
        var files = this.filesSrc;
        // Merge task-specific and/or target-specific options with these defaults.
        var options = this.options({
            punctuation: '.',
            separator: ', '
        });

        // Iterate over all specified file groups.
        this.files.forEach(function(f) {
            // Concat specified files.
            var src = f.src.filter(function(filepath) {
                // console.log("filepath:2:",grunt.file.exists(filepath));
                // Warn on and remove invalid source files (if nonull was set).
                if (!grunt.file.exists(filepath)) {
                    grunt.log.warn('Source file "' + filepath + '" not found.');
                    return false;
                } else {
                    return true;
                }
            }).map(function(filepath) {
                build(f.dest,filepath)
                // dist.to(f.dest);
                // console.log("filepath:3:",filepath,f.dest);
                // Read file source.
                // return grunt.file.read(filepath);
            }).join(grunt.util.normalizelf(options.separator))

            // console.log("message:2",src);
            // console.log("message:3",grunt.util.normalizelf(options.separator));

            // // Handle options.
            // src += options.punctuation;

            // // Write the destination file.
            // grunt.file.write(f.dest, src);

            // // Print a success message.
            // grunt.log.writeln('File "' + f.dest + '" created.');
        });
    });

};

//返回文件名字 + 后缀
function fileext(type,path){
    if(!path) path = type,type = false;

    var file_arr = path.match(/([^:\\/]*?)(?:\.([^ :\\/.]*))$/)
    var fileext = file_arr[2];
    var name = file_arr[1];
    var new_name = ''

    switch(type){
        case "min":new_name = name + '.min.' + fileext;break;
        case "map": new_name = name + '.min.map';break;
        case "gz": new_name = name + '.min.gz';break;
        default: new_name = file_arr[0];
    }
    return new_name;
}

//换算
function format_number(size, precision) {
    var decimal, factor;
    if (precision == null) precision = 1;
    factor = Math.pow(10, precision);
    decimal = Math.round(size * factor) % factor;
    return parseInt(size) + "." + decimal;
};

//统计文件字节数
function fsize(file) {
    return fs.statSync(file).size;
};

//输出大小
function report_size(file) {
    return echo(" › " + clc.xterm(161)(file)  + ": " + (format_number(fsize(file) / 1024)) + " KiB");
};


/*
 * 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';

var async = require('async');
var buildAnalyze = require('./analyze');
var _ = require('lodash');
var buildSys = require('./container');

module.exports = function(options, loadConfig, logger, _sr, _containers, _services) {
  var ERR_NOCDEF = 'unable to find container definition';
  var ERR_NOSYSID = 'unable to find system';
  var ERR_NOREV = 'unable to find revision';
  var ERR_NOTARGET = 'unable to find the target environment';

  var _builder;
  var _deployer;
  var _compiler;
  var _synchrotron;
  var _monkey;
  var _monitorInterval;
  var _monitorRunning = false;
  var _proxy;
  var analyzeAndDeployQueue;
  var analyze = buildAnalyze(loadConfig, logger, _services);

  /**
   * list all of the available systems
   */
  var listSystems = function(cb) {
    logger.info('list systems');
    var systems = _sr.listSystems();
    cb(null, systems);
  };

  /**
   * get the full head system definition (latest revision)
   */
  var getHeadSystem = function(identifier, target, cb) {
    logger.info('get head system: ' + identifier);
    var systemId = _sr.findSystem(identifier);
    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }
    _sr.getHead(systemId, target, cb);
  };

  /**
   * get the full deployed system definition
   */
  var getDeployedSystem = function(identifier, target, cb) {
    var systemId = _sr.findSystem(identifier);
    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }
    fetchTarget(systemId, target, function(err, target) {
      if (err) { return cb(err); }
      logger.info({ systemId: systemId, target: target }, 'get deployed system');
      _sr.getDeployedRevision(systemId, target, cb);
    });
  };

  /**
   * create a new  blank system
   */
  var createSystem = function(user, name, namespace, cwd, cb) {
    logger.info('create system name: ' + name + ', namespace: ' + namespace + ', cwd: ' + cwd);
    _sr.createSystem(user, namespace, name, cwd, cb);
  };

  /**
   * link a system from local fs
   */
  var linkSystem = function(user, path, cwd, cb) {
    logger.info('link system: ' + path + ', ' + cwd);
    _sr.linkSystem(user, path, cwd, cb);
  };

  /*
   * unlink a system from local fs
   */
  var unlinkSystem = function(user, identifier, cb) {
    var systemId = _sr.findSystem(identifier);
    if (!systemId) { return cb(new Error(ERR_NOSYSID)); }
    logger.info('unlink system: ' + systemId);
    _sr.unlinkSystem(user, systemId, cb);
  };

  /**
   * list all of the available containers in a system
   */
  var listContainers = function(identifier, revisionId, out, cb) {
    logger.info('list containers: ' + identifier);
    var systemId = _sr.findSystem(identifier);
    var containers = {};
    if (!systemId) { return cb(new Error(ERR_NOSYSID)); }
    _builder.loadTargets(systemId, revisionId, function(err, targets) {
      if (err) { return cb(err); }
      _.each(targets, function(target) {
        _.each(target.containerDefinitions, function(cdef) {
          containers[cdef.id] = cdef;
        });
      });
      cb(null, _.values(containers));
    });
  };

  /**
   * build a container
   */
  var buildContainer = function(user, identifier, containerIdentifier, revision, target, out, cb) {
    var containerDef;
    var systemId;

    systemId = _sr.findSystem(identifier);
    if (!systemId) {
      logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID));
    }
    var systemRoot = _sr.repoPath(systemId);
    out.initProgress(9, '--> finding container');

    fetchTarget(systemId, target, revision, function(err, target) {
      if (err) { return cb(err); }
        _builder.loadMatchingTargets(systemId, revision, target, function(err, targets) {
          if (err) { return cb(err); }
          _builder.findContainer(systemId, revision, targets, containerIdentifier, function(err, containerDefId, targets) {
            if (err) { out.stdout(err); logger.error(err); return cb(err); }
            if (!containerDefId) { out.stdout(ERR_NOCDEF); logger.error(ERR_NOCDEF); return cb(ERR_NOCDEF); }
            async.eachSeries(_.values(targets), function(json, cb) {
              var root = buildSys(json);
              containerDef = root.containerDefByDefId(containerDefId);
              json.repoPath = systemRoot;

              if (!containerDef.specific || !containerDef.specific.repositoryUrl) {
                return _builder.build(user, systemId, targets, json, containerDef, target, out, cb);
              }

              _synchrotron.synch(json, containerDef, out, function(err) {
                if (err) { out.stdout(err); logger.error(err); return cb(err); }
                _builder.build(user, systemId, targets, json, containerDef, target, out, cb);
              });
            }, cb);
        });
      });
    });
  };

  /**
   * build all containers belonging to a system, in series
   */
  var buildAllContainers = function(user, systemName, revision, target, out, cb) {
    var systemId = _sr.findSystem(systemName);

    if (!systemId) {
      logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID));
    }

    logger.info({ systemId: systemId, revision: revision }, 'building all containers');
    fetchTarget(systemId, target, revision, function(err, target) {
      if (err) { return cb(err); }
      _builder.loadMatchingTargets(systemId, revision, target, function(err, targets) {
        if (err) { return cb(err); }

        out.stdout('--> building all containers for ' + targets[Object.keys(targets)[0]].name + ' revision ' + revision + ' target ' + target);

        var containers = _.chain(targets)
          .filter(function(value, key) {
            return target === 'alltargets' || key === target;
          })
          .map(function(target) {
            return _.map(target.containerDefinitions, function(cdef) {
              return {
                id: cdef.id,
                target: target.topology.name,
                type: cdef.type
              };
            });
          })
          .flatten()
          .reduce(function(acc, cont) {
            var notPresent = !_.find(acc, function(found) {
              return found.id === cont.id && found.type === cont.type;
            });

            if (notPresent) {
              acc.push(cont);
            }

            return acc;
          }, [])
          .value();

        async.eachSeries(containers, function (cont, next) {
          buildContainer(user, systemId, cont.id, revision, cont.target, out, function(err) {
            if (err) { out.stderr(err); }
            // so that buildall fails if one build fail
            next(err);
          });
        }, cb);
      });
    });
  };

  /**
   * Supports target abbreviation
   */
  var fetchTarget = function(systemId, target, revision, cb) {
    if (target === 'alltargets') {
      cb(null, target);
    }
    else {
      _sr.getDeployedRevisionId(systemId, target, function(err, deployedRevId) {
        if (typeof revision === 'function') {
          cb = revision;
          if (!err) {
            revision = deployedRevId;
          }
          else {
            revision = 'latest';
          }
        }
        _builder.loadTargets(systemId, revision, function(err, targets) {
          if (err) { return cb(err); }
          var candidates = Object.keys(targets).filter(function(candidate) {
          return candidate.indexOf(target) >= 0;
          });

          if (candidates.length === 0 || candidates.length > 1) {
            logger.error(ERR_NOTARGET);
            return cb(new Error(ERR_NOTARGET));
          } else {
            target = candidates[0];
          }
          cb(null, target);
        });
      });
    }
  };

  var createAnalyzeAndDeployTask = function (user, systemId, revisionId, target, mode, out, cb) {
    return analyzeAndDeployQueue.push({
      user: user,
      systemId: systemId,
      revisionId: revisionId,
      target: target,
      mode: mode,
      out: out
    }, cb);
  };

  var mergeSpecificBlock = function(analyzed, target) {
    _.each(target.topology.containers, function(container) {
      var matched = analyzed.topology.containers[container.id];
      if (matched) {
        _.merge(matched.specific, container.specific);
        _.merge(container.specific, matched.specific);
        if (matched.nativeId) {
          container.nativeId = matched.nativeId;
        }
      }
    });
  };

  var analyzeAndDeploy = function (user, systemId, revisionId, target, mode, out, cb) {
    analyzeSystem(user, systemId, target, out, function(err, analyzed) {
      if (err) { return cb(err); }

      _sr.getRevision(systemId, revisionId, target, function(err, systemRevision) {
        if (err) { return cb(err); }

        var systemRoot = _sr.repoPath(systemId);
        analyzed.repoPath = systemRoot;
        systemRevision.repoPath = systemRoot;
        systemRevision.topology.name = target;

        _deployer.deploy(user, systemId, revisionId, analyzed, systemRevision, _sr, mode, out, function(err) {
          if (err) { return cb(err); }

          loadConfig(systemRevision, function(err, cfg) {
            if (err) { return cb(err); }

            if (mode !== 'preview' && cfg.enableProxy && _.find(cfg.enableProxy, function(p) { return p === target; })) {
              out.stdout('Proxy enabled, reanalyzing system');

              // need to re-merge as per deployer before proxy HUP - move the merge to a function that can be accessed
              analyzeSystem(user, systemId, target, out, function(err, analyzed) {
                if (err) { return cb(err); }
                mergeSpecificBlock(analyzed, systemRevision);

                out.stdout('HUP all proxies');
                _proxy.hupAllProxies(mode, target, systemRevision, analyzed, out, function(err) {
                  cb(err);
                });
              });
            }
            else {
              cb();
            }
          });
        });
      });
    });
  };

  var handleAnalyzeAndDeployTask = function (task, cb) {
    logger.info(task, 'starting new deploy');
    analyzeAndDeploy(
      task.user,
      task.systemId,
      task.revisionId,
      task.target,
      task.mode,
      task.out,
      function(err) {
        logger.info(task, 'deploy finished');
        cb(err);
      });
  };

  /**
   * deploy the specified revision to the nominated target
   * handle if file is missing
   */
  var deployRevision = function(user, identifier, revisionIdentifier, target, mode, out, cb) {
    var systemId = _sr.findSystem(identifier);

    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }

    fetchTarget(systemId, target, revisionIdentifier, function(err, target) {
      if (err) { return cb(err); }

      _sr.findRevision(systemId, revisionIdentifier, function(err, revisionId) {
        if (err) { out.stdout(ERR_NOREV); logger.error(ERR_NOREV); return cb(ERR_NOREV); }

        logger.info({
          systemId: systemId,
          revisionId: revisionId,
          environment: target
        }, 'deploy revision');

        if (!mode) { mode = 'live'; }
        if (!revisionId) {
          return cb(new Error('revisionId is needed to deploy'));
        }
        return createAnalyzeAndDeployTask(user, systemId, revisionId, target, mode, out, cb);
      });
    });
  };

  /**
   * preview a system deploy
   */
  var previewRevision = function(user, identifier, revisionIdentifier, target, out, cb) {
    logger.info('preview revision: ' + identifier + ', ' + revisionIdentifier + ' ' + target);
    deployRevision(user, identifier, revisionIdentifier, target, 'preview', out, function(err) {
      cb(err, {plan: out.getPlan(), ops: out.operations()});
    });
  };

  /**
   * get the revision history for a system
   */
  var listRevisions = function(identifier, cb) {
    logger.info('list revisions: ' + identifier);
    if (!identifier) {
      return cb(new Error('no identifier'));
    }
    var systemId = _sr.findSystem(identifier);

    if (!systemId) {
      return cb(new Error('system not found'));
    }

    _sr.listRevisions(systemId, function(err, revisions) {
      cb(err, _.first(revisions, 20));
      //cb(err, revisions);
    });
  };

  /**
   * get a specific revision
   */
  var getRevision = function(identifier, revisionIdentifier, target, cb) {
    logger.info('get revision: ' + identifier + ', ' + revisionIdentifier);
    var systemId = _sr.findSystem(identifier);
    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }
    fetchTarget(systemId, target, revisionIdentifier, function(err, target) {
      if (err) { return cb(err); }
      _sr.findRevision(systemId, revisionIdentifier, function(err, revisionId) {
        if (err) { return cb(err); }
        _sr.getRevision(systemId, revisionId, target, cb);
      });
    });
  };

  var markRevision = function(user, identifier, revisionIdentifier, cb) {
    logger.info('mark revision: ' + identifier + ', ' + revisionIdentifier);
    var systemId = _sr.findSystem(identifier);
    _sr.findRevision(systemId, revisionIdentifier, function(err, revisionId) {
      _sr.markDeployedRevision(user, systemId, revisionId, cb);
    });
  };

  /**
   * get timeline
   */
  var timeline = function(identifier, cb) {
    var systemId = _sr.findSystem(identifier);
    if (!systemId) {
      logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID));
    }

    _sr.getTimeline(systemId, function(err, timeline) {
      cb(err, { entries: timeline });
    });
  };

  var getDeployedOrHeadSystem = function(systemId, target, cb) {
    getDeployedSystem(systemId, target, function(err, system) {
      if (!system) {
        getHeadSystem(systemId, target, function(err, head) {
          cb(err, head);
        });
      }
      else {
        cb(err, system);
      }
    });
  };

  var analyzeSystem = function(user, identifier, target, out, cb) {
    logger.info({ system: identifier }, 'analyze system');
    var systemId = _sr.findSystem(identifier);

    fetchTarget(systemId, target, function(err, target) {
      if (err) { return cb(err); }
      getDeployedOrHeadSystem(systemId, target, function(err, system) {
        if (err) { return cb(err); }
        logger.info('running analysis against deployed system');
        var systemRoot = _sr.repoPath(systemId);
        system.repoPath = systemRoot;

        analyze(system, function(err, result) {
          logger.info('analysis complete');
          if (err) {
            logger.error(err);
            return cb(err);
          }

          result.repoPath = systemRoot;
          cb(err, result, system);
        });
      });
    });
  };

  var getDeployedOrHeadRevisionId = function(systemId, target, cb) {
    _sr.getDeployedRevisionId(systemId, target, function(err, revId) {
      if (!revId) {
        _sr.getHeadRevisionId(systemId, function(err, headId) {
          cb(err, headId);
        });
      }
      else {
        cb(err, revId);
      }
    });
  };

  var checkSystem = function(user, identifier, target, out, cb) {
    logger.info('check system: ' + identifier);
    var systemId = _sr.findSystem(identifier);

    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }

    getDeployedOrHeadRevisionId(systemId, target, function(err, deployedId) {
      if (err) { return cb(err); }

      createAnalyzeAndDeployTask(user, systemId, deployedId, target, 'preview', out, function (err) {
        cb(err, {plan: out.getPlan(), ops: out.operations()});
      });
    });
  };

  var fixSystem = function(user, identifier, target, out, cb) {
    logger.info('fix system: ' + identifier);
    var systemId = _sr.findSystem(identifier);

    getDeployedOrHeadRevisionId(systemId, target, function(err, revisionId) {
      if (err) {
        return cb(err);
      }
      createAnalyzeAndDeployTask(user, systemId, revisionId, target, 'live', out, cb);
    });
  };

  var removeAllSwContainers = function(system) {
    _.each(system.topology.containers, function(c) {
      if (c.type && (c.type === 'docker' || c.type === 'process')) {
        system.topology.containers[c.containedBy].contains = []; // assuming just sw containers here...
        delete system.topology.containers[c.id];
      }
    });
    return system;
  };

  var stopSystem = function stopSystem(user, identifier, target, out, cb) {
    var systemId = _sr.findSystem(identifier);

    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }

    analyzeSystem(user, systemId, target, out, function(err, analyzed) {
      if (err) { return cb(err); }
      getDeployedOrHeadRevisionId(systemId, target, function(err, revisionId) {
        if (err) { return cb(err); }
        getDeployedOrHeadSystem(systemId, target, function(err, systemRevision) {
          if (err) { return cb(err); }
          removeAllSwContainers(systemRevision);

          var systemRoot = _sr.repoPath(systemId);
          analyzed.repoPath = systemRoot;
          systemRevision.repoPath = systemRoot;
          systemRevision.topology.name = target;

          removeAllSwContainers(systemRevision);

          _deployer.deploy(user, systemId, revisionId, analyzed, systemRevision, _sr, 'live', out, cb);
        });
      });
    });
  };

  var infoSystem = function(user, identifier, target, out, cb) {
    var result = [];
    analyzeSystem(user, identifier, target, out, function(err, analyzed) {
      if (err) { return cb(err); }
      _.each(analyzed.topology.containers, function(c) {
        var info = '';
        var type = '';
        if (c.type === 'aws-ami') {
          if (c.specific) {
            info = (c.specific.instanceId || '') + ',';
            info += (c.specific.publicIpAddress || '') + ',';
            info += c.specific.privateIpAddress || '';
          }
        }
        if (c.type) {
          type = c.type;
        }
        else {
          var cdef = _.find(analyzed.containerDefinitions, function(cd) { return cd.id === c.containerDefinitionId; });
          if (cdef) {
            type = cdef.type;
          }
        }
        result.push({name: c.id.split('-')[0], type: type, parent: c.containedBy.split('-')[0], info: info});
      });
      cb(err, result);
    });
  };

  /**
   * compile the system into the various targets and commit them to the repository
   */
  var compileSystem = function(user, identifier, comment, out, cb) {
    logger.info('compile system: ' + identifier);
    var systemId = _sr.findSystem(identifier);
    var system;

    if (!systemId) { logger.error(ERR_NOSYSID); return cb(new Error(ERR_NOSYSID)); }

    var repoPath = _sr.repoPath(systemId);
    _compiler.compile(systemId, repoPath, out, function(err, systems) {
      if (err) { return cb(err); }
      async.eachSeries(_.keys(systems), function(key, next) {
          system = systems[key];
          _sr.writeFile(system.id, key + '.json', JSON.stringify(system, null, 2), next);
        },
        function(err) {
          cb(err);
        });
    });
  };

  /**
   * commit the system
   */
  var commitSystem = function(user, identifier, comment, out, cb) {
    logger.info('commit system: ' + identifier);
    var systemId = _sr.findSystem(identifier);

    _sr.commitRevision(user, systemId, comment, function(err, revisionId) {
      _sr.getDeployedTargets(systemId, function(err, targets) {
        if (targets) {
          async.eachSeries(targets, function(target, next) {
            if (target.commit === 'edits') {
              _sr.markDeployedRevision(user, systemId, revisionId, target.env, function() {
                next();
              });
            }
            else {
              next();
            }
          }, function() {
            cb(err, revisionId);
          });
        }
        else {
          cb(err, revisionId);
        }
      });
    });
  };

  var monkeyStart = function(user, identifier, target, out, cb) {
    _monkey.start(function() { return _monitorRunning; }, analyzeSystem, user, identifier, target, out, function() {
      cb(null);
    });
  };

  var monkeyStop = function(user, identifier, target, out, cb) {
    _monkey.stop(function() {
      cb(null);
    });
  };

  var monitorStart = function(user, identifier, target, out, cb) {
    if (_monitorInterval) { return cb(); }

    _monitorInterval = setInterval(function() {
      if (_monkey.spanking()) {
        logger.info('check aborted monkey is spanking');
        return;
      }
      _monitorRunning = true;
      checkSystem(user, identifier, target, out, function(err, result) {
        if (err) {
          logger.error('error during system monitor: ' + err);
        }
        else {
          if (result.plan.length > 0) {
            logger.warn('deviation detected applying remedial fixes');
            fixSystem(user, identifier, target, out, function(err) {
              _monitorRunning = false;
              if (err) {
                logger.error('error applying remedial fixes: ' + err);
              }
              else {
                logger.info('remedial fixes applied');
              }
            });
          }
          else {
            _monitorRunning = false;
          }
        }
      });
    }, 10000);
    cb(null, 'monitor started');
    logger.info('monitor started');
  };

  var monitorStop = function (user, identifier, target, out, cb) {
    if (_monitorInterval) {
      clearInterval(_monitorInterval);
      _monitorInterval = undefined;
    }
    cb();
  };

  var construct = function() {
    _synchrotron = require('./synchrotron')(loadConfig);
    _deployer = require('./deployer')(logger, _containers);
    _compiler = require('./compiler')(_synchrotron, loadConfig, logger, options);
    _builder = require('./builder')(logger, _containers, _sr, _compiler);
    _monkey = require('./monkey')(options, 15000, logger);
    _proxy = require('./proxy')(loadConfig, _containers, logger);
    analyzeAndDeployQueue = async.queue(handleAnalyzeAndDeployTask, 1);
  };

  construct();
  return {
    listSystems: listSystems,
    createSystem: createSystem,
    getDeployedSystem: getDeployedSystem,
    linkSystem: linkSystem,
    unlinkSystem: unlinkSystem,
    fixSystem: fixSystem,
    compileSystem: compileSystem,
    commitSystem: commitSystem,
    stopSystem: stopSystem,
    infoSystem: infoSystem,

    listContainers: listContainers,
    buildContainer: buildContainer,
    buildAllContainers: buildAllContainers,
    deployRevision: deployRevision,
    previewRevision : previewRevision,

    listRevisions: listRevisions,
    getRevision: getRevision,
    markRevision: markRevision,

    timeline: timeline,

    analyzeSystem: analyzeSystem,
    checkSystem: checkSystem,

    monkeyStart: monkeyStart,
    monkeyStop: monkeyStop,
    monitorStart: monitorStart,
    monitorStop: monitorStop
  };
};


/*
 * grunt-coffeeify
 * https://github.com/jackcviers/grunt-coffeeify
 *
 * Copyright (c) 2013 Jack Viers
 * Licensed under the MIT license.
 */

'use strict';
var path = require('path');
var browserify = require('browserify');
var coffeeify = require('coffeeify');
var when = require('when');

module.exports = function(grunt) {

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerMultiTask('coffeeify', 'Your task description goes here.', function() {
    // asynchronous task
    var done = this.async(),
    errorCount = this.errorCount,
    coffeeifyInstance = function(src, destination){
      grunt.verbose.writeln("Adding entries to be coffeeified/browserified: " +src);
      var browserifyInstance = browserify(src);
      return {dest: destination, instance: browserifyInstance};
    };

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options(
      {
        // include coffeeify by default
        // since it doesn't hurt plain
        // js
        transforms: [coffeeify],
        insertGlobals: false,
        detectGlobals: true,
        ignoreMissing: false,
        debug: false
      }
    );

    grunt.verbose.writeln('Options :' + JSON.stringify(options));

    // Iterate over all specified file groups.
    when.all(this.files.map(function(fileObject) {
      var newFileObject = {}, keys, length;
      // create a mutable copy of the fileObject
      for(keys = Object.keys(fileObject), length = keys.length; length; --length) {
        newFileObject[keys[length -1]] = fileObject[keys[length-1]];
      }
      // filter out any non-existing paths

      newFileObject.src = newFileObject.src.filter(function(filepath) {
        if(!grunt.file.exists(filepath)) {
          grunt.verbose.writeln("Non-extant filepath: "+ filepath);

          return false;
        }

        return true;
      // make the paths absolute so that browserify is happy
      }).map(function(filepath){
        return path.resolve(filepath);
      });

      // return the modified fileObject
      return newFileObject;
    // map over the fileobjects and browserify them
    }).map(function(fileObject) {
      // create an independent browserify instance, bind the
      // src paths to it, and create an object containing
      // the instance and the final destination
      return coffeeifyInstance(fileObject.src, fileObject.dest);
    // keep the destination in context for all src filepaths
    // use promises to resolve callbacks correctly,
    // returning a list of promises that we can use.
    }).map(function(browserifyInstance){
      var deferred, promise, transforms, requires;

      grunt.verbose.writeln('Preparing to coffeeify');
      deferred = when.defer();
      transforms = options.transforms || [];
      requires = options.requires || [];
      // for each transform in the transforms option,
      // apply it to the browserify instance.
      transforms.forEach(function(transform){
        browserifyInstance.instance.transform(transform);
      });
      // map over the requires,
      // first checking to see if the file is
      // a filepath or a module, then add
      // them to the browserify instance
      requires.forEach(function(moduleOrFilepath){
        var moduleAsFilePaths = grunt.file.expand([moduleOrFilepath]),
        moduleIsFilePath = moduleAsFilePaths.length === 0 ? false : true;
        grunt.verbose.writeln("moduleOrFilepath: " + moduleAsFilePaths);
        if(moduleIsFilePath) {
          moduleAsFilePaths.forEach(function(filepath) {
            browserifyInstance.instance.require(filepath);
          });
        } else {
          browserifyInstance.instance.require(moduleOrFilepath);
        }
      });
      // bundle the sources and prepend/appends
      browserifyInstance.instance.bundle(options, function(error, contents){
        var finalFileContents = '';
        // We broke the build on this one.
        // Resolve the deferred and exit early.
        if(error){
          deferred.reject(error);
          return;
        }

        // If there are contents to prepend,
        // add them to the contents to be
        // be written.
        if(options.prepend){
          finalFileContents += options.prepend + "\n";
        }
        // Add the contents of the browserify
        // process to the contents to be
        // written.
        finalFileContents += contents;
        // If there are contents to append,
        // add them to the contents to be
        // written.
        if(options.append){
          finalFileContents += "\n" + options.append;
        }
        // Write the contents to disk.
        grunt.file.write(browserifyInstance.dest, finalFileContents);

        grunt.verbose.oklns("Coffeeified: " + browserifyInstance.dest);

        // resolve the deferred for the current fileObject.
        deferred.resolve(browserifyInstance.dest);
      });

      // return the promise so when.all can handle
      // the deferred resolutions with then.
      return deferred.promise;
    })).then(
      // All of the promises succeeded, report the success.
      function reportSources(fileLocationsReport) {
        return {count: fileLocationsReport.length, locations:fileLocationsReport.join("\n")};
      },
      // There was at least one failure. Fail the build.
      function failBuild(error) {
        errorCount += 1;
        grunt.fail.warn(error);
      }
    ).then(
      // Finalize the task.
      function finalizeBuild(sourceReport){
        // Something went wrong. Fail the build.
        if(errorCount > 0) {
          grunt.fail.warn("Coffeeification failed.");
        // The build succeeded. Call done to
        // finish the grunt task.
        } else {
          done("Coffeified " + sourceReport.count + ": " + sourceReport.locations);
        }
      },
      // Shouldn't ever reach here, but jic.
      function failBuild(error){
        grunt.fail.warn(error);
      }
    );

  });
};


"use strict";
/***
 *                          __     _  __       __
 *       ____ ___   ____   / /_   (_)/ /___   / /_   ___   _____ ____
 *      / __ `__ \ / __ \ / __ \ / // // _ \ / __ \ / _ \ / ___// __ \
 *     / / / / / // /_/ // /_/ // // //  __// / / //  __// /   / /_/ /
 *    /_/ /_/ /_/ \____//_.___//_//_/ \___//_/ /_/ \___//_/    \____/
 *
 *                  mobile solutions for everyday heroes
 *
 * @file
 * Fix resolve issues by replacing functionality
 *
 * @module
 * @aplus/node/resolver/resolve-fix
 *
 * @author
 * Brenton House <brenton.house@gmail.com>
 *
 * @copyright
 * Copyright (c) 2016 by Superhero Studios Incorporated.  All Rights Reserved.
 *
 * @license
 * Licensed under the terms of the MIT License (MIT)
 * Please see the license.md included with this distribution for details.
 *
 */

var _ = require("lodash");
var wrench = require("wrench");
var path = require("path");
var fs = require("fs-extra");
var minimatch = require('minimatch');

/**
 *
 *
 * @param {string} rootpath - Directory path to start searching for files
 * @param {Object} registry - Object used to store files/modules found
 * @param {string[]} registry.files - Files found in search
 * @param {string[]} registry.directories - Directories found in search
 * @param {Object[]} registry.core - Core modules to use in app
 * @param {Object[]} registry.fallback - Modules to use in case of reference to missing module
 * @param {string[]} [params.includes] - Array of glob patterns to match files to be included in search
 * @param {Object} [logger=console] - Alloy logger object
 */
module.exports = function(rootpath, registry, includes, logger) {

	_.defaults(registry, {
		files: [],
		directories: [],
		core: [],
		fallback: [],
	});
	logger = logger || console;

	includes = includes || ["**/*.js", "**/*.json"];

	/**
	 * Inject necessary code into the file app.js
	 */
	function injectCode() {

		var fullpath = path.join(rootpath, "app.js");
		var source = fs.readFileSync(fullpath, 'utf8');
		var test = /\/\/ALLOY-RESOLVER/.test(source);
		logger.trace("CODE INJECTED ALREADY: " + test);
		if(!test) {
			source = source.replace(/(var\s+Alloy[^;]+;)/g, "$1\n//ALLOY-RESOLVER\nvar process=require('/process');\nAlloy.resolve=new (require('/resolver'))().resolve;\n");
			fs.writeFileSync(fullpath, source);
		}
	}

	/**
	 * Fix module resolution in all found javascript files
	 */
	function fixFiles() {
		logger.trace("inside fixFiles()");
		_.each(registry.files, function(file) {
			var fullpath = path.join(rootpath, file);
			var basepath = path.posix.dirname(file);
			var basefile = path.posix.resolve(file);
			var source = fs.readFileSync(fullpath, 'utf8');
			logger.trace("fixing file: " + fullpath);
			var requireRegex = /(require)\s*\(((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/g;
			var staticRequireRegex = /(require)(?:\(\s*['"])([^'"]+)(?:['"]\s*\))/g;

			source = source.replace(requireRegex, function($1, $2, $3) {
				var requestedModule = $2;
				if(staticRequireRegex.test($1)) {
					var staticRequireSource = $1;
					staticRequireSource = staticRequireSource.replace(staticRequireRegex, function($1, $2, $3) {
						var resolved_path = resolver.resolve($3, basepath);
						return 'require("' + resolved_path + '")';
					});
					return staticRequireSource;
				} else {
					return 'require(Alloy.resolve(' + $3 + ', "' + basepath + '"))';
				}
			});

			fs.writeFileSync(fullpath, source, { mode: 0o755 });
		});
	}

	/**
	 * Replace backslashes for cross-platform usage
	 * Adapted from https://github.com/sindresorhus/slash
	 *
	 * @param {string} intput - value needing to have backslashes replaced in.
	 * @returns {string}
	 */
	function replaceBackSlashes(input) {
		var isExtendedLengthPath = /^\\\\\?\\/.test(input);
		var hasNonAscii = /[^\x00-\x80]+/.test(input);

		if(isExtendedLengthPath || hasNonAscii) {
			return input;
		}

		return input.replace(/\\/g, '/');
	};

	/**
	 * Find all files that match extension criteria
	 *
	 * @param {string} rootpath - Absolute path of the directory from which file search will begin
	 * @param {string[]|string} [patterns="**"] - Pattern(s) to be used when attempting to match files found
	 * @returns {string[]} - Matched file paths
	 */
	function findFiles(rootpath, patterns) {
		logger.trace("inside findFiles()");
		var patterns = patterns || ['**'];
		if(_.isString(patterns)) {
			patterns = [patterns];
		}
		var files = _.map(wrench.readdirSyncRecursive(rootpath), function(filename) {
			return path.posix.sep + replaceBackSlashes(filename);
		});
		var matchedFiles = match(files, patterns, {
			nocase: true,
			matchBase: true,
			dot: true,
		});
		return _.filter(matchedFiles, function(file) {
			return !fs.statSync(path.join(rootpath, file)).isDirectory();
		}) || [];

	};

	/**
	 * Find items in array that match a set of patterns
	 * Adapted from https://github.com/sindresorhus/multimatch
	 *
	 * @param {string[]} list
	 * @param {string[]|string} patterns
	 * @param {Object} options
	 * @returns {string[]}
	 */
	function match(list, patterns, options) {
		list = list || [];
		patterns = patterns || [];
		if(_.isString(patterns)) {
			patterns = [patterns];
		}

		if(list.length === 0 || patterns.length === 0) {
			return [];
		}

		options = options || {};
		return patterns.reduce(function(ret, pattern) {
			var process = _.union
			if(pattern[0] === '!') {
				pattern = pattern.slice(1);
				process = _.difference;
			}
			return process(ret, minimatch.match(list, pattern, options));
		}, []);
	};

	/**
	 *  Find and process all files
	 */
	function loadFiles() {
		logger.trace("inside loadFiles()");
		var allfiles = findFiles(rootpath, includes);
		var filepaths = _.filter(allfiles, function(filepath) {
			return !/.+(package\.json)/.test(filepath);
		});
		_.forEach(filepaths, function(filepath) {
			registry.files.push(filepath);
		});

		var packagepaths = _.filter(allfiles, function(filepath) {
			return(/.+(package\.json)/.test(filepath));
		});
		_.forEach(packagepaths, function(filepath) {
			var content = fs.readFileSync(path.posix.join(rootpath, filepath), 'utf8');
			var json = JSON.parse(content);
			if(json.main) {
				registry.directories.push({
					id: path.posix.dirname(filepath),
					path: path.posix.resolve(path.posix.join(path.posix.dirname(filepath), json.main))
				});
			}
		});

		var indexpaths = _.filter(allfiles, function(filepath) {
			return(/.+(index\.js)/.test(filepath));
		});

		_.forEach(indexpaths, function(filepath) {
			var existingdir = _.find(registry.directories, function(dir) {
				return dir.id === path.posix.dirname(filepath);
			});
			if(!existingdir) {
				registry.directories.push({
					id: path.posix.dirname(filepath),
					path: filepath
				});
			}
		});

		return registry;
	};

	/**
	 *  Write registry file to resolver.js
	 */
	function writeRegistry() {
		logger.trace("inside writeRegistry()");
		var filepath = path.join(rootpath, "resolver.js");
		var content = fs.readFileSync(filepath, 'utf8');
		var regex = /(var\s+registry\s+=\s+)[^;]*(;)/g;
		var modified = content.replace(regex, "$1" + JSON.stringify(registry) + "$2");
		fs.writeFileSync(filepath, modified);
	}

	fs.copySync(path.join(__dirname, "resolver.js"), path.join(rootpath, "resolver.js"));
	fs.copySync(path.join(__dirname, "path.js"), path.join(rootpath, "path.js"));
	fs.copySync(path.join(__dirname, "process.js"), path.join(rootpath, "process.js"));

	loadFiles();

	var r = require("./resolver");
	var resolver = new r(registry, logger);
	registry = resolver.registry;

	injectCode();
	fixFiles();
	writeRegistry();

	Object.defineProperty(this, "registry", {
		get: function() {
			return _.clone(registry);
		},
		enumerable: true,
		configurable: false
	});
}


/**
 * @fileoverview Requires a capital letter at the start of a comment.
 * @author Team Yoast <support@yoast.com>
 */
"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = function(context) {

    var requireCapitalLetter = context.options[0] !== "never";
    var firstLetterRegex = /(@?)(\w)/;
    // variables should be defined here

    //--------------------------------------------------------------------------
    // Helpers
    //--------------------------------------------------------------------------

    // any helper functions should go here or else delete this section

    //--------------------------------------------------------------------------
    // Public
    //--------------------------------------------------------------------------

    /**
     * Returns the first letter of a string regardless of whitespace
     *
     * @param {string} text The text to retrieve the first letter of.
     * @returns {string} The first letter of the text.
     */
    function getFirstLetter(text) {
        var matches = text.match( firstLetterRegex );

        if ( null === matches ) {
            return '';
        }

        // Ignore JSDoc blocks
        if ( '@' === matches[1] ) {
            return '';
        }

        return matches[0];
    }

    /**
     * Reports a spacing error with an appropriate message.
     * @param {ASTNode} node - A comment node to check.
     * @param {string} message - An error message to report
     * @returns {void}
     */
    function report(node, message) {
        context.report({
            node: node,
            message: message
        });
    }

    function checkCommentForCapitalLetter(node) {
        var value = node.value;

        // Ignores empty comments
        if (value.length === 0) {
            return;
        }

        // Ignore `global: someglobal` comments
        if ( 0 === value.trim().indexOf( "global" ) ) {
            return;
        }

        var firstLetter = getFirstLetter(value);

        // Ignores comments without letters
        if ( '' === firstLetter ) {
            return;
        }

        if ( requireCapitalLetter ) {
            if ( firstLetter !== firstLetter.toUpperCase() ) {
                report(node, "Expected an uppercase letter at the start of a comment.");
            }
        } else {
            if ( firstLetter !== firstLetter.toLowerCase() ) {
                report(node, "Expected a lowercase letter at the start of a comment.");
            }
        }
    }

    return {
        LineComment: checkCommentForCapitalLetter,
        BlockComment: checkCommentForCapitalLetter
    };

};

module.exports.schema = [
    {
        enum: ["always", "never"]
    }
];


/**
 * Copyright (c) 2018-present, White Town s.r.o.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import React from 'react'
import { AsyncStorage, View } from 'react-native'

import getTheme from '../utils/getTheme'

const CONSTANTS = {
    theme: {
        themeKey:   'themeKey',
        setTheme:   'setTheme',
        saveTheme:  'saveTheme',
        restoreTheme: 'restoreTheme',
    },
}

const defaultTheme = getTheme()

function hexToRGB(hex, alpha) {
    var r = parseInt(hex.slice(1, 3), 16),
        g = parseInt(hex.slice(3, 5), 16),
        b = parseInt(hex.slice(5, 7), 16);

    if (alpha) {
        return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
    } else {
        return "rgb(" + r + ", " + g + ", " + b + ")";
    }
}

function getNavigationOptions( theme ) {

    const colors = theme.defaultValues.colors

    return {
        headerStyle: {
            backgroundColor: hexToRGB(colors.headerColor, colors.headerOpacity),
        },
        headerTintColor: colors.headerTintColor,
        headerTitleStyle: {
            color: colors.headerTitle,
        },
        headerTransparent: colors.headerOpacity < 1,
        }

}

const initialState = {
    theme: defaultTheme,
    navigationOptions: getNavigationOptions( defaultTheme ),
    contentOffset: 0,
}

//actions

function setTheme(theme) {
    const navigationOptions = getNavigationOptions( theme )
    const contentOffset = theme.defaultValues.colors.headerOpacity < 1 ? (theme.systemValues.isIOS ? theme.systemValues.statusBarHeight : 0) + theme.systemValues.navigationBarHeight : 0
    return {
        type: CONSTANTS.theme.setTheme,
        theme,
        navigationOptions,
        // childNavigationOptions,
        contentOffset,
    }
}

function saveTheme(themeName) {

    return (dispatch) => {

        if (themeName)
            AsyncStorage.setItem(CONSTANTS.theme.themeKey, themeName)
        else
            AsyncStorage.setItem(CONSTANTS.theme.themeKey, 'default')
    //    AsyncStorage.removeItem(CONSTANTS.theme.themeKey)
    }
}

function restoreTheme( themesArray, defTheme = null ) {

    return (dispatch) => {

        AsyncStorage.getItem(CONSTANTS.theme.themeKey)
        .then( (result) => {
            if (result) {
                if (result === 'default') return
                const colors = themesArray[result]
                if (colors) dispatch( setTheme( getTheme(result, colors) ) )
            } else {
                if (defTheme) {
                const colors = themesArray[defTheme]
                if (colors) {
                    dispatch( setTheme( getTheme(result, colors) ) )
                    dispatch( saveTheme( defTheme ) )
                    }
                }
            }
        })

    }
}

function checkThemeChange(N, O = null, rootLevel = false) {
    const n_theme = N.theme ? N.theme.theme||{} : {}

    if (O) {
        const o_theme = O.theme ? O.theme.theme||{} : {}
        if (n_theme.name === o_theme.name) return
//        if (N.theme.navigationOptions === O.theme.navigationOptions) return
    }

    N.navigation.setParams({ navigationOptions: N.theme.navigationOptions })
}

//reducer

function themeReducer(state = initialState, action) {

    switch (action.type) {

        case CONSTANTS.theme.setTheme:
            return {
                ...state,
                theme: action.theme,
                navigationOptions: action.navigationOptions,
                contentOffset: action.contentOffset,
            }
        default:
            return state
    }
}

export {
    CONSTANTS,
    checkThemeChange,
    setTheme,
    saveTheme,
    restoreTheme,
    themeReducer as theme,
}


/**
*
*	VALIDATE: uri
*
*
*	DESCRIPTION:
*		- Validates if a value is a URI.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2015. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2015.
*
*/

'use strict';

// MODULES //

var isString = require( 'validate.io-string' );

// REGEXP //

var URI,
	ILLEGALS,
	HEX1,
	HEX2,
	PATH,
	SCHEME;

// RFC 3986:
URI = /(?:([^:\/?#]+):)?(?:\/\/([^\/?#]*))?([^?#]*)(?:\?[^#]*)?(?:#.*)?/;
/**
	REGEXP

	<scheme name> : <hierarchical part > [ ? <query> ] [ # <fragment> ]

	(?:([^:\/?#]+):)
		-	match the scheme, including the ':', but only capture the scheme name

	?
		-	match the scheme 0 or 1 times

	(?:\/\/([^\/?#]*))
		-	match the hierarchical part which is everything which is not a '/', '#', or '?', but only capture whatever comes after the '//'

	?
		-	match the hierarchical part 0 or 1 times

	([^?#]*)
		-	capture everything (the path) until meeting a '?' or '#'

	(?:\?[^#]*)
		-	match, but don't capture, a query

	?
		-	match the query 0 or 1 times

	(?:#.*)
		-	match, but don't capture, a fragment

	?
		-	match the fragment 0 or 1 times
*/

// Illegal characters (anything which is not in between the square brackets):
ILLEGALS = /[^a-z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\.\-\_\~\%]/i;

// Incomplete HEX escapes:
HEX1 = /%[^0-9a-f]/i;
HEX2 = /%[0-9a-f](:?[^0-9a-f]|$)/i;

// If authority is not present, path must not begin with '//'
PATH = /^\/\//;

// Scheme must begin with a letter, then consist of letters, digits, '+', '.', or '-' => e.g., 'http', 'https', 'ftp'
SCHEME = /^[a-z][a-z0-9\+\-\.]*$/;

// URI //

/**
* FUNCTION: uri( value )
*	Validates if a value is a URI.
*
* @param {*} value - value to validate
* @returns {Boolean} boolean indicating if a value is a URI
*/
function uri( val ) {
	var parts,
		scheme,
		authority,
		path;
	if ( !isString( val ) ) {
		return false;
	}
	// [1] Check for illegal characters:
	if ( ILLEGALS.test( val ) ) {
		return false;
	}
	// [2] Check for incomplete HEX escapes:
	if ( HEX1.test( val ) || HEX2.test( val ) ) {
		return false;
	}
	// [3] Split the string into various URI components:
	parts = val.match( URI );
	scheme = parts[ 1 ];
	authority = parts[ 2 ];
	path = parts[ 3 ];

	// [4] Scheme is required and must be valid:
	if ( !scheme || !scheme.length || !SCHEME.test( scheme.toLowerCase() ) ) {
		return false;
	}
	// [5] If authority is not present, path must not begin with a '//':
	if ( !authority && PATH.test( path ) ) {
		return false;
	}
	return true;
} // end FUNCTION uri()

// EXPORTS //

module.exports = uri;


/*
 * 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';

var _ = require('lodash');
var uuid = require('uuid');
var async = require('async');

/**
 * container builder
 */
module.exports = function(logger, _containers, _sr, _compiler) {

  /**
   * Build the container and update all instantiations of the container
   * with the new speicific block and replace identifiers for new uuids
   */
  var executeBuild = function build(mode, system, cdef, out, cb) {
    _containers.getHandler(system, cdef.type, function(err, container) {
      if (err) { return cb(err); }
      if (!container) {
        err = new Error('no matching container available for type: ' + cdef.type);
        logger.error(err.message);
        return cb(err);
      }

      if (container.build) {
        out.progress('--> executing container specific build for ' +  cdef.id);
        logger.info({ containerDefinition: cdef.id }, 'executing container specific build');
        container.build(mode, system, cdef, out, function(err, specific) {
          if (err) { logger.error(err); out.stdout(err); return cb(err); }

          out.progress('--> ' + cdef.id + ' built');
          logger.info({ containerDefinition: cdef.id }, 'built');
          cb(err);
        });
      } else {
        out.progress('--> no need to build ' + cdef.id);
        cb(null, {});
      }
    });
  };

  var build = function(user, systemId, targets, json, containerDef, target, out, cb) {
    out.progress('--> initiating container build');
    executeBuild('live', json, containerDef, out, function(err) {
      if (err) { out.stdout(err); logger.error(err); return cb(err); }
      cb();
    });
  };

  /**
   * find the container in the supplied target files, return the container with the highest buildHead number
   */
  var findContainer = function findContainer(systemId, revision, targets, containerIdentifier, cb) {
    var cdefId;
    var types = [];
    async.filter(_.keys(targets), function(key, next) {
      _sr.findContainer(systemId, revision, containerIdentifier, key, function(err, containerDefId, cdef) {
        var def;
        if (!err && containerDefId) {
          cdefId = containerDefId;
          def = _.find(targets[key].containerDefinitions, function(def) {
            return def.id === cdefId;
          });

          if (types.indexOf(def.type) < 0) {
            types.push(def.type);
            return next(true);
          }
        }
        next(false);
      });
    },
    function(keys) {
      var result = keys.reduce(function(acc, key) {
        acc[key] = targets[key];
        return acc;
      }, {});
      cb(null, cdefId, result);
    });
  };

  var loadTargets = function loadTargets(systemId, revision, cb) {
    var result = {};
    var path = _sr.repoPath(systemId);

    // TODO we should list the targets based on revision
    // let's just assume that the targets did non change
    _compiler.listTargets(path, function(err, targets) {
      if (err) { return cb(err); }
      async.eachSeries(targets, function(target, next) {
        _sr.getRevision(systemId, revision, target, function(err, json) {
          if (json) {
            result[target] = json;
          }
          // swallow any errors as a target might not be
          // available in that revision
          // FIXME
          next();
        });
      },
      function(err) {
        if (Object.keys(result).length === 0) {
          return cb(new Error('no available environment for the given revision'));
        }
        cb(err, result);
      });
    });
  };

  var _loadTarget = function _loadTarget(systemId, revision, target, cb) {
    var result = {};
    var path = _sr.repoPath(systemId);

    _sr.getRevision(systemId, revision, target, function(err, json) {
      if (err) { return cb(err); }
        result[target] = json;
        cb(err, result);
    });
  };

  var loadMatchingTargets = function loadMatchingTargets(systemId, revision, target, cb) {
    if (target === 'alltargets') {
      loadTargets(systemId, revision, function(err, result) {
        if (err) { return cb(err); }
        cb(err, result);
      });
    }
    else {
      _loadTarget(systemId, revision, target, function(err, result) {
        if (err) { cb(err); }
        cb(err, result);
      });
    }
  };

  return {
    loadTargets: loadTargets,
    loadMatchingTargets: loadMatchingTargets,
    findContainer: findContainer,
    build: build
  };
};


'use strict'

var toString = require('nlcst-to-string')
var modifier = require('unist-util-modify-children')
var gemoji = require('gemoji')

module.exports = modifier(mergeEmoji)

var own = {}.hasOwnProperty

var EMOTICON_NODE = 'EmoticonNode'

/* Magic numbers.
 *
 * Gemoji's are treated by a parser as multiple nodes.
 * Because this modifier walks backwards, the first colon
 * never matches a gemoji it would normaly walk back to
 * the beginning (the first node). However, because the
 * longest gemoji is tokenized as `Punctuation` (`:`),
 * `Punctuation` (`+`), `Word` (`1`), and `Punctuation`
 * (`:`), we can safely break when the modifier walked
 * back more than 4 times. */
var MAX_GEMOJI_PART_COUNT = 12

var shortcodes = []
var unicodes = gemoji.unicode
var byName = gemoji.name
var key

for (key in byName) {
  shortcodes.push(':' + key + ':')
}

/* Merge emoji and github-emoji (punctuation marks,
 * symbols, and words) into an `EmoticonNode`. */
function mergeEmoji(child, index, parent) {
  var siblings = parent.children
  var value = toString(child)
  var siblingIndex
  var node
  var nodes
  var subvalue
  var left
  var right
  var leftMatch
  var rightMatch
  var start
  var pos
  var end
  var replace
  var startIndex
  var nextSibling
  var nextNextSibling
  var possibleEmoji
  var maxSiblingIndex
  var loopIndex
  var lastSibling
  var lastSiblingIndex

  if (child.type === 'WordNode') {
    /* 1⃣ — sometimes a unicode emoji is marked as a word. Mark it as
     * an `EmoticonNode`. */
    if (own.call(unicodes, value)) {
      node = {type: EMOTICON_NODE, value: value}

      if (child.position) {
        node.position = child.position
      }

      siblings[index] = node
    } else {
      /* ❤ — Sometimes a unicode emoji is split in two.  Remove the last
       * and add its value to the first. */
      node = siblings[index - 1]

      if (node && own.call(unicodes, toString(node) + value)) {
        node.type = EMOTICON_NODE
        node.value = toString(node) + value

        if (child.position && node.position) {
          node.position.end = child.position.end
        }

        siblings.splice(index, 1)

        return index
      }
    }
  } else if (own.call(unicodes, value)) {
    child.type = EMOTICON_NODE
    startIndex = index + 1
    nextSibling = siblings[startIndex]
    if (!nextSibling) {
      return
    }

    if (nextSibling.type === 'WordNode') {
      /* 🏌 — Normal emoji. */
      if (!isVarianceSelector(nextSibling)) {
        return
      }

      possibleEmoji = value + toString(nextSibling)
      maxSiblingIndex = siblings.length
      loopIndex = startIndex + 1

      while (
        loopIndex < maxSiblingIndex &&
        loopIndex - startIndex < 5 &&
        siblings[loopIndex].type !== 'WordNode'
      ) {
        possibleEmoji += toString(siblings[loopIndex])
        loopIndex++
      }

      lastSibling = siblings[loopIndex]

      if (lastSibling && lastSibling.type === 'WordNode') {
        possibleEmoji += toString(lastSibling)
      }

      /* 🏌♀ — Emoji with variance selector. */
      if (own.call(unicodes, possibleEmoji)) {
        child.value = possibleEmoji

        if (child.position && lastSibling.position) {
          child.position.end = lastSibling.position.end
        }

        siblings.splice(index + 1, loopIndex - index)

        return index + 1
      }
      /* 👨❤💋👨 — combined emoji. */
    } else if (nextSibling.type === 'SymbolNode') {
      possibleEmoji = value + toString(nextSibling)
      maxSiblingIndex = siblings.length
      loopIndex = startIndex + 1

      while (
        loopIndex < maxSiblingIndex &&
        loopIndex - startIndex < 5 &&
        (siblings[loopIndex].type === 'SymbolNode' ||
          (siblings[loopIndex].type === 'WordNode' &&
            isVarianceSelector(siblings[loopIndex])))
      ) {
        possibleEmoji += toString(siblings[loopIndex])
        loopIndex++
      }

      if (own.call(unicodes, possibleEmoji)) {
        child.value = possibleEmoji
        lastSiblingIndex = loopIndex - 1
        lastSibling = siblings[lastSiblingIndex]

        if (child.position && lastSibling.position) {
          child.position.end = lastSibling.position.end
        }

        siblings.splice(index + 1, lastSiblingIndex - index)

        return index + 1
      }
    }
    /* 🤽♀ — Combined emoji starting in a symbol. */
  } else if (child.type === 'SymbolNode') {
    nextSibling = siblings[index + 1]
    nextNextSibling = siblings[index + 2]
    if (!nextSibling || !nextNextSibling) {
      return
    }

    if (
      (nextSibling.type === 'SymbolNode' || nextSibling.type === 'WordNode') &&
      nextNextSibling &&
      nextNextSibling.type === 'SymbolNode'
    ) {
      possibleEmoji = value + toString(nextSibling) + toString(nextNextSibling)

      if (own.call(unicodes, possibleEmoji)) {
        child.type = EMOTICON_NODE
        child.value = possibleEmoji

        if (child.position && nextNextSibling.position) {
          child.position.end = nextNextSibling.position.end
        }

        siblings.splice(index + 1, 2)

        return index + 1
      }
    }
    /* :+1: — Gemoji shortcodes. */
  } else if (value.charAt(0) === ':') {
    nodes = []
    siblingIndex = index
    subvalue = value
    left = null
    right = null
    leftMatch = null
    rightMatch = null

    if (subvalue.length === 1) {
      rightMatch = child
    } else {
      end = child.position && child.position.end
      start = end && child.position.start
      pos = end && {
        line: start.line,
        column: start.column + 1,
        offset: start.offset + 1
      }

      rightMatch = {
        type: 'PunctuationNode',
        value: ':'
      }

      right = {
        type: 'PunctuationNode',
        value: subvalue.slice(1)
      }

      if (end) {
        rightMatch.position = {start: start, end: pos}
        right.position = {start: pos, end: end}
      }
    }

    while (siblingIndex--) {
      if (index - siblingIndex > MAX_GEMOJI_PART_COUNT) {
        return
      }

      node = siblings[siblingIndex]

      subvalue = toString(node)

      if (subvalue.charAt(subvalue.length - 1) === ':') {
        leftMatch = node
        break
      }

      if (node.children) {
        nodes = nodes.concat(node.children.concat().reverse())
      } else {
        nodes.push(node)
      }

      if (siblingIndex === 0) {
        return
      }
    }

    if (!leftMatch) {
      return
    }

    subvalue = toString(leftMatch)

    if (subvalue.length !== 1) {
      end = leftMatch.position && leftMatch.position.end
      start = end && leftMatch.position.start
      pos = end && {
        line: end.line,
        column: end.column - 1,
        offset: end.offset - 1
      }

      left = {
        type: 'PunctuationNode',
        value: subvalue.slice(0, -1)
      }

      leftMatch = {
        type: 'PunctuationNode',
        value: ':'
      }

      if (end) {
        left.position = {start: start, end: pos}
        leftMatch.position = {start: pos, end: end}
      }
    }

    nodes.push(leftMatch)
    nodes.reverse().push(rightMatch)

    value = toString(nodes)

    if (shortcodes.indexOf(value) === -1) {
      return
    }

    replace = [siblingIndex, index - siblingIndex + 1]

    if (left) {
      replace.push(left)
    }

    child.type = EMOTICON_NODE
    child.value = value

    if (child.position && leftMatch.position) {
      child.position.start = leftMatch.position.start
    }

    if (child.position && rightMatch.position) {
      child.position.end = rightMatch.position.end
    }

    replace.push(child)

    if (right) {
      replace.push(right)
    }

    ;[].splice.apply(siblings, replace)

    return siblingIndex + 3
  }
}

function isVarianceSelector(node) {
  var code = toString(node).charCodeAt(0)
  return code > 65023 && code < 65040
}


/*====================================================
	- HTML Table Filter Generator v1.0
	- By Max Guglielmi
	- mguglielmi.free.fr
  - http://www.javascriptsource.com/repository/javascripts/2006/05/835271/TableFilter_EN.htm
  - modified by @noahedwardhall
  - thank me later
=====================================================*/

let TblId = [];
let StartRow = [];
let SearchFlt = [];

/*====================================================
- adds a filter (input) for each column (td)
- adds button on last column
=====================================================*/
function AddRow (id,n) {
  let t = document.getElementById(id);
  let fltrow = t.insertRow(0);
  let inpclass = "flt";
  for(let i=0; i<n; i++){
    let fltcell = fltrow.insertCell(i);
    let inp = document.createElement("input");
    inp.setAttribute("id", "flt" + i + "_" + id);
    inp.setAttribute("type", "text");
    inp.setAttribute("class", i == n - 1 ? "flt_s" : "flt");
    inp.setAttribute('placeholder', 'Filter')
    inp.addEventListener('keyup', Filter);
    fltcell.appendChild(inp);
  }
}

/*====================================================
- returns text + text of child nodes of a cell
=====================================================*/
function getCellText (n) {
  let s = "";
  let enfants = n.childNodes;
  for(let i=0; i<enfants.length; i++)
  {
    let child = enfants[i];
    if(child.nodeType == 3) s+= child.data;
    else s+= getCellText(child);
  }
  return s.toLowerCase();
}

/*====================================================
- returns starting row for Filter fn for a
given table id
=====================================================*/
function getStartRow(id) {
  let r;
  for(let j in TblId) {
    if(TblId[j] === id) r = StartRow[j];
  }
  return r;
}

/*====================================================
- checks passed node is a ELEMENT_NODE nodeType=1
- removes TEXT_NODE nodeType=3
=====================================================*/
function getChildElms (n) {
  if(n.nodeType == 1){
    let enfants = n.childNodes;
    for(let i=0; i<enfants.length; i++)
    {
      let child = enfants[i];
      if(child.nodeType == 3) n.removeChild(child);
    }
    return n;
  }
}

/*====================================================
- filter (input) ids are stored in this.SearchFlt
array
=====================================================*/
function getFilters(id) {
  SearchFlt = [];
  let t = document.getElementById(id);
  let tr = t.getElementsByTagName("tr")[0];
  let inp = tr.getElementsByTagName("input");

  for(let i=0; i<inp.length; i++) SearchFlt.push(inp[i].getAttribute("id"));

  return SearchFlt;
}

/*====================================================
- returns number of cells in a row
- if nrow param is passed returns number of cells
of that specific row
=====================================================*/
function getCellsNb (id,nrow) {
    let t = document.getElementById(id);
  let tr;
  if(nrow == undefined) tr = t.getElementsByTagName("tr")[0];
  else  tr = t.getElementsByTagName("tr")[nrow];
  let n = getChildElms(tr);
  return n.childNodes.length;
}

const filterTable = {
  /*====================================================
  - Checks if id exists and is a table
  - Calls fn that adds inputs and button
  =====================================================*/
  setFilterGrid (id, ref_row) {
    if (typeof window === 'undefined' || typeof document === 'undefined') return;

  	const tbl = document.getElementById(id);
  	if (tbl && tbl.nodeName.toLowerCase() === "table") {
  		TblId.push(id);
  		ref_row = ref_row === undefined ? StartRow.push(2) : StartRow.push(ref_row+2);
  		//let ncells = getCellsNb(id, ref_row);

  		// AddRow(id, ncells);
  	}
  },

  /*====================================================
  - gets search strings from this.SearchFlt array
  - retrieves data from each td in every single tr
  and compares to search string for current
  column
  - tr is hidden if all search strings are not
  found
  =====================================================*/
  Filter (e) {
    if (typeof document === 'undefined') return;
    const id = e.target.getAttribute("id").split("_")[1];
    let SearchFlt = getFilters(id);
    let t = document.getElementById(id);
    let SearchArgs = [];
    let ncells = getCellsNb(id);

    for(let i in SearchFlt) SearchArgs.push((document.getElementById(SearchFlt[i]).value).toLowerCase());

    let start_row = getStartRow(id);
    let row = t.getElementsByTagName("tr");

    for(let k=start_row; k<row.length; k++){
      /*** if table already filtered some rows are not visible ***/
      if(row[k].style.display === "none") row[k].style.display = "";

      let cell = getChildElms(row[k]).childNodes;
      let nchilds = cell.length;
      let isRowValid = true;

      if(nchilds === ncells){// checks if row has exact cell #
        let cell_value = [];
        let occurence = [];

        for(let j=0; j<nchilds; j++)// this loop retrieves cell data
        {
          let cell_data = getCellText(cell[j]);
          cell_value.push(cell_data);

          if(SearchArgs[j]!==""){
            occurence[j] = cell_data.split(SearchArgs[j]).length;
          }
        }//for j

        for(let t=0; t<ncells; t++) {
          if(SearchArgs[t]!=="" && occurence[t]<2) {
            isRowValid = false;
          }
        }//for t
      }//if

      if(isRowValid===false) row[k].style.display = "none";
      else row[k].style.display = "";
    }// for k
  },
}

export default filterTable;


/*jshint debug:false, noarg:true, noempty:true, eqeqeq:true, bitwise:true, undef:true, curly:true, browser: true, devel: true, jquery:false, strict:true */
/*global  Backbone, Skeletor, _, jQuery, Rollcall, exports, require */

(function () {
  "use strict";

  var Backbone, Skeletor, Drowsy, jQuery, _,
    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) {return i;} } return -1; },
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    __hasProp = {}.hasOwnProperty;

  // This detects if we are in a browser or node.js environment and
  // ensures dependencies are available in the required way
  if (typeof exports !== "undefined" && exports !== null) {
    jQuery = require("jquery");
    _ = require("underscore");
    Backbone = require("backbone");
    Backbone.$ = jQuery;
    Drowsy = require("backbone.drowsy").Drowsy;
    Skeletor = {};
    exports.Skeletor = Skeletor;
  } else {
    window.Skeletor = window.Skeletor || {};
    Skeletor = window.Skeletor;
    jQuery = window.$;
    _ = window._;
    Drowsy = window.Drowsy;
  }

  // a Rollcall 2.0 client
  var Rollcall = function (url, db) {
    this.server = new Drowsy.Server(url);
    this.db = this.server.database(db);

    // User model
    this.User = this.db.Document('users').extend({
      addTag: function (tag) {
        var tags = _.clone(this.get('tags'));

        // if no classes array exists add it
        if (!tags) {
          tags = [];
        }

        tags.push(tag);
        this.set('tags', _.uniq(tags));
      },

      removeTag: function (tag) {
        var tags = this.get('tags');
        this.set('tags', _.without(tags, tag));
      },

      addCohort: function (c) {
        var classes = _.clone(this.get('cohorts'));
        // if no cohorts array exists add it
        if (!cohorts) {
          cohorts = [];
        }

        cohorts.push(c);
        this.set('cohorts', _.uniq(cohorts));
      },

      removeCohort: function (c) {
        var cohorts = this.get('cohorts');
        this.set('cohorts', _.without(cohorts, c));
      },

      isTeacher: function() {
        if (this.get('user_role') === 'teacher') {
          return true;
        } else {
          return false;
        }
      }
    });

    this.Users = this.db.Collection('users').extend({
      model: this.User
    });

    /*
     *   Group model
     */
    this.Group = this.db.Document('groups').extend({
      addGroup: function (group) {
        var groups = _.clone(this.get('groups'));
        groups.push(group);
        this.set('groups', _.uniq(group));
      }
    });

    this.Groups = this.db.Collection('groups').extend({
      model: this.Group
    });

    // Run model
    this.Run = this.db.Document('runs').extend({
    });

    this.Runs = this.db.Collection('runs').extend({
      model: this.Run
    });

    /*
     *   Model for Cohorts
     */
    this.Cohort = this.db.Document('cohorts').extend({
      addDiscussion: function (discussionId) {
        var discussions = _.clone(this.get('discussions'));

        // if no discussions array exists add it
        if (!discussions) {
          discussions = [];
        }

        discussions.push(discussionId);
        this.set('discussions', _.uniq(discussions));
      },

      removeDiscussion: function (discussionId) {
        var discussions = this.get('discussions');
        this.set('discussions', _.without(discussions, discussionId));
      }

    });

    this.Cohorts = this.db.Collection('cohorts').extend({
      model: this.Cohort
    });

    /*
     *   Model for Discussions
     */
    this.Discussion = this.db.Document('discussions').extend({
    });

    this.Discussions = this.db.Collection('discussions').extend({
      model: this.Discussion
    });

  };

  Rollcall.prototype.users = function(selector) {
    selector = selector || {};

    var users = new this.Users();
    var usersPromise = users.fetch({
      data: {
        selector: JSON.stringify(selector),
        strict: false
      }
    });

    return usersPromise.then(function () {
      return users;
    });
  };

  Rollcall.prototype.authenticate = function(username, password) {
    var authenticatePromise = this.user(username)
      .then(function (user) {
        if (!user) {
          return false;
        }

        if (user.get('password') !== password) {
          return false;
        }

        return true;
      });

    return authenticatePromise;
  };

  Rollcall.prototype.identify = function(username) {
    var identifyPromise = this.user(username)
      .then(function (user) {
        if (!user) {
          return null;
        }

        return user;
      });

    return identifyPromise;
  };

  Rollcall.prototype.usersWithTags = function(tags) {
    tags = tags || [];
    var selector = {"tags":{"$all": tags}};

    return this.users(selector);
  };

  Rollcall.prototype.usersWithClasses = function(classes) {
    classes = classes || [];
    var selector = {"classes":{"$all": classes}};

    return this.users(selector);
  };

  Rollcall.prototype.user = function(username) {
    return this.users({"username": username})
    .then(function (users) {
      return users.at(0);
    });
  };

  Rollcall.prototype.userExists = function(username) {
    return this.user(username)
      .then(function (u) {
        if (u) {
          return true;
        } else {
          return false;
        }
      });
  };

  Rollcall.prototype.usersWithUserRole = function (userRole) {
    userRole = userRole || '';
    var selector = {"user_role":userRole};

    return this.users(selector);
  };

  Rollcall.prototype.group = function(groupname) {
    return this.groups({"groupname": groupname})
    .then(function (groups) {
      return groups.at(0);
    });
  };

  Rollcall.prototype.groups = function(selector) {
    selector = selector || {};

    var groups = new this.Groups();
    var groupsPromise = groups.fetch({
      data: {
        selector: JSON.stringify(selector),
        strict: false
      }
    });

    return groupsPromise.then(function () {
      return groups;
    });
  };

  Rollcall.prototype.groupsWithTags = function(tags) {
    tags = tags || [];
    var selector = {"tags":{"$all": tags}};

    return this.groups(selector);
  };

  /*
  * Added run stuff - unsure and therefor experimental
  */

  Rollcall.prototype.runs = function(selector) {
    selector = selector || {};

    var runs = new this.Runs();
    var runsPromise = runs.fetch({
      data: {
        selector: JSON.stringify(selector),
        strict: false
      }
    });

    return runsPromise.then(function () {
      return runs;
    });
  };

  Rollcall.prototype.selectFromCollection = function(selector, collectionName) {
    selector = selector || {};

    var collection = new this[collectionName]();
    var promise = collection.fetch({
      data: {
        selector: JSON.stringify(selector),
        strict: false
      }
    });

    return promise.then(function () {
      return collection;
    });
  };

  this.Rollcall = Rollcall;

}).call(this);