'use strict';

var path       = require('path');
var semver     = require('semver');
var chalk      = require('chalk');
var EOL        = require('os').EOL;
var fs         = require('fs');
var readFile   = fs.readFileSync;
var fileExists = fs.existsSync;

function EmberCLIDependencyChecker(project) {
  this.name    = 'ember-cli-dependency-checker';
  this.project = project;
  this.checkDependencies(project);
}

EmberCLIDependencyChecker.prototype.checkDependencies = function(project) {

  var isUnsatisfied = function(pkg) {
    return !!pkg.needsUpdate;
  };

  var bowerDeps = this.readBowerDependencies(project);
  var unsatisfiedBowerDeps = bowerDeps.filter(isUnsatisfied);

  var npmDeps = this.readNPMDependencies(project);
  var unsatisfiedNPMDeps = npmDeps.filter(isUnsatisfied);

  var message = '';
  message += this.reportUnsatisfiedPackages('npm', unsatisfiedNPMDeps);
  message += this.reportUnsatisfiedPackages('bower', unsatisfiedBowerDeps);

  if(message !== '') {
    throw new DependencyError(message);
  }
};

EmberCLIDependencyChecker.prototype.lookupNodeModuleVersion = function(project, name) {
  var nodePackage = path.join(project.root, 'node_modules', name, 'package.json');
  return this.lookupPackageVersion(nodePackage);
};

EmberCLIDependencyChecker.prototype.lookupBowerPackageVersion = function(project, name) {
  var dotBowerFile = path.join(project.root, project.bowerDirectory, name, '.bower.json');
  var version = this.lookupPackageVersion(dotBowerFile);
  if(!version) {
    var bowerFile = path.join(project.root, project.bowerDirectory, name, 'bower.json');
    version = this.lookupPackageVersion(bowerFile);
  }
  return version;
};

EmberCLIDependencyChecker.prototype.lookupPackageVersion = function(path) {
  if(fileExists(path)) {
    var pkg = readFile(path);
    var version = null;
    try {
      version = JSON.parse(pkg).version || null;
    } catch(e) {
      // JSON parse error
    }
    return version;
  } else {
    return null;
  }
};

EmberCLIDependencyChecker.prototype.updateRequired = function(name, version, versionInstalled) {
  if (!versionInstalled) {
    return true;
  }

  var isGitRepo = function(str) {
    return (/^git(\+(ssh|https?))?:\/\//i).test(str) || (/\.git\/?$/i).test(str) || (/^git@/i).test(str);
  };

  if (isGitRepo(version)) {
    var parts = version.split('#');
    if (parts.length === 2) {
      version = this.semver.valid(parts[1]);
      if (!version) {
        return false;
      }
    }
  }

  if (!semver.validRange(version)) {
    return false;
  }

  return !semver.satisfies(versionInstalled, version);
};

EmberCLIDependencyChecker.prototype.readBowerDependencies = function(project) {
  var bowerFilePath = path.join(project.root, 'bower.json');
  var bowerFileContents = fs.readFileSync(bowerFilePath);
  var dependencies = JSON.parse(bowerFileContents).dependencies;
  return Object.keys(dependencies).map(function(name) {
    var versionSpecified = dependencies[name];
    var versionInstalled = this.lookupBowerPackageVersion(project, name);
    return this.resolvePackage(name, versionSpecified, versionInstalled);
  }, this);
};

EmberCLIDependencyChecker.prototype.readNPMDependencies = function(project) {
  var dependencies = project.dependencies();
  return Object.keys(dependencies).map(function(name) {
    var versionSpecified = dependencies[name];
    var versionInstalled = this.lookupNodeModuleVersion(project, name);
    return this.resolvePackage(name, versionSpecified, versionInstalled);
  }, this);
};

EmberCLIDependencyChecker.prototype.resolvePackage = function(name, versionSpecified, versionInstalled) {
  return {
    name: name,
    versionSpecified: versionSpecified,
    versionInstalled: versionInstalled,
    needsUpdate: this.updateRequired(name, versionSpecified, versionInstalled)
  };
};

EmberCLIDependencyChecker.prototype.reportUnsatisfiedPackages = function(type, packages) {
  var message = '';
  if(packages.length > 0) {
    message += EOL + chalk.red('Missing ' + type + ' packages: ') + EOL;

    packages.map(function(pkg) {
      message += chalk.green('Package: ') + chalk.cyan(pkg.name) + EOL;
      message += chalk.grey('  * Specified: ') + chalk.green(pkg.versionSpecified) + EOL;
      message += chalk.grey('  * Installed: ') + chalk.green(pkg.versionInstalled || '(not installed)') + EOL + EOL;
    }, this);

    message += chalk.red('Run `'+ type +' install` to install missing dependencies.') + EOL;
  }
  return message;
};

function DependencyError(message) {
  this.name     = 'DependencyError';
  this.message  = message;

  if (process.env.EMBER_VERBOSE_ERRORS === 'true') {
    this.stack = (new Error()).stack;
    this.suppressStacktrace = false;
  } else {
    this.suppressStacktrace = true;
  }
}

DependencyError.prototype = Error.prototype;
DependencyError.prototype.constructor = DependencyError;

module.exports = EmberCLIDependencyChecker;

@@ -394,23 +394,16 @@
 dencies(
-project
 );%0A%7D%0A%0AEm
@@ -461,31 +461,24 @@
  = function(
-project
 ) %7B%0A%0A  var i
@@ -581,39 +581,32 @@
 werDependencies(
-project
 );%0A  var unsatis
@@ -696,23 +696,16 @@
 dencies(
-project
 );%0A  var
@@ -1065,33 +1065,24 @@
  = function(
-project, 
 name) %7B%0A  va
@@ -1099,32 +1099,37 @@
 age = path.join(
+this.
 project.root, 'n
@@ -1287,33 +1287,24 @@
  = function(
-project, 
 name) %7B%0A  va
@@ -1322,32 +1322,37 @@
 ile = path.join(
+this.
 project.root, pr
@@ -1341,32 +1341,37 @@
 s.project.root, 
+this.
 project.bowerDir
@@ -1496,32 +1496,37 @@
 ile = path.join(
+this.
 project.root, pr
@@ -1523,16 +1523,21 @@
 t.root, 
+this.
 project.
@@ -2644,39 +2644,32 @@
 cies = function(
-project
 ) %7B%0A  var bowerF
@@ -2688,16 +2688,21 @@
 th.join(
+this.
 project.
@@ -3002,33 +3002,24 @@
 kageVersion(
-project, 
 name);%0A    r
@@ -3170,23 +3170,16 @@
 unction(
-project
 ) %7B%0A  va
@@ -3195,16 +3195,21 @@
 ncies = 
+this.
 project.
@@ -3387,17 +3387,8 @@
 ion(
-project, 
 name

Access project object directly instead of passing as argument to instance methods