text: /*
   *   Copyright 2014-2015 Guy Bedford (http://guybedford.com)
   *
   *   Licensed under the Apache License, Version 2.0 (the "License");
   *   you may not use this file except in compliance with the License.
   *   You may obtain a copy of the License at
   *
   *       http://www.apache.org/licenses/LICENSE-2.0
   *
   *   Unless required by applicable law or agreed to in writing, software
   *   distributed under the License is distributed on an "AS IS" BASIS,
   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *   See the License for the specific language governing permissions and
   *   limitations under the License.
   */
  
  var registry = require('../registry');
  var config = require('../config');
  var extend = require('../common').extend;
  var hasProperties = require('../common').hasProperties;
  var fs = require('graceful-fs');
  var asp = require('rsvp').denodeify;
  var path = require('path');
  var PackageName = require('./package-name');
  var alphabetize = require('../common').alphabetize;
  var stringify = require('../common').stringify;
  var ui = require('../ui');
  var extractObj = require('./utils').extractObj;
  var globalConfig = require('../global-config');
  
  /*
   * Loader Configuration Class
   *
   * baseURL
   * registries
   * transpiler
   * babelOptions
   * traceurOptions
   *
   * paths
   * bundles
   * depCache
   *
   * baseMap
   * depMap
   * versions
   *
   */
  // and registry is a path rule ending in ':*'
  var registryRegEx = /\:\*$/;
  function Config(fileName) {
    this.__fileName = fileName;
  }
  Config.prototype.read = function(prompts, sync) {
    if (this.__read)
      throw 'Config already read';
    this.__read = true;
  
    var self = this;
    var source;
    try {
      source = fs.readFileSync(this.__fileName);
    }
    catch(e) {
      source = '';
    }
  
    var cfg = {};
    var System = {
      config: function(_cfg) {
        for (var c in _cfg) {
          if (!_cfg.hasOwnProperty(c))
            continue;
  
          var v = _cfg[c];
          if (typeof v === 'object') {
            cfg[c] = cfg[c] || {};
            for (var p in v) {
              if (!v.hasOwnProperty(p))
                continue;
              cfg[c][p] = v[p];
            }
          }
          else
            cfg[c] = v;
        }
      },
      paths: {},
      map: {},
      versions: {}
    };
    eval(source.toString());
  
    // allow declarative form too
    var config = System.config;
    delete System.config;
    config(System);
  
    self.__originalConfig = cfg;
  
    self.baseURL = cfg.baseURL;
  
    // NB deprecate cfg.parser with 0.11.0
    self.transpiler = cfg.transpiler || cfg.parser || globalConfig.config.defaultTranspiler;
    // NB deprecate babel rename with 0.13
    if (self.transpiler === '6to5')
      self.transpiler = 'babel';
  
    self.babelOptions = cfg.babelOptions || {};
    self.traceurOptions = cfg.traceurOptions || {};
  
    // separate paths into registries and paths
    self.registries = {};
    self.paths = {};
    for (var p in cfg.paths) {
      if (p.match(registryRegEx)) {
        var registryName = p.substr(0, p.length - 2);
        var registryPath = new RegistryPath(registryName, cfg.paths[p]);
        self.registries[registryName] = registryPath;
        if (self.__local === undefined) {
          if (registryPath.mode === 'local')
            self.__local = true;
          else
            self.__local = false;
        }
      }
      else {
        self.paths[p] = cfg.paths[p];
      }
    }
  
    self.paths = cfg.paths;
  
    self.shim = cfg.shim;
    self.bundles = cfg.bundles;
    self.depCache = cfg.depCache;
  
    // separate map into baseMap and depMap
    self.baseMap = {};
    self.depMap = {};
    for (var d in cfg.map) {
      if (typeof cfg.map[d] === 'string')
        self.baseMap[d] = new PackageName(cfg.map[d]);
      else {
        var depMap = cfg.map[d];
        self.depMap[d] = {};
        for (var m in depMap)
          if (depMap.hasOwnProperty(m)) {
            self.depMap[d][m] = new PackageName(depMap[m]);
          }
      }
    }
  
    // ensure that everything in baseMap has a depMap, even if empty
    var baseMap = self.baseMap, exactName;
    for (var n in baseMap) {
      if (baseMap.hasOwnProperty(n)) {
        exactName = baseMap[n].exactName;
        self.depMap[exactName] = self.depMap[exactName] || {};
      }
    }
  
    self.versions = self.versions || {};
    for (var v in cfg.versions) {
      if (typeof cfg.versions[v] === 'string')
        self.versions[v] = [cfg.versions[v]];
      else
        self.versions[v] = cfg.versions[v];
    }
  
    if (hasProperties(self.versions))
      upgradeToExactVersionResolution(self);
  
  
    if (!prompts)
      return;
  
    if (sync)
      throw 'Configuration file has not been initialized. Run jspm init first.';
  
    return ui.input('Enter client baseURL (public folder URL)', self.baseURL || '/')
    .then(function(baseURL) {
      self.baseURL = baseURL;
  
      return ui.input('Which ES6 transpiler would you like to use, %Traceur% or %Babel%?', self.transpiler);
    })
    .then(function(transpiler) {
      transpiler = transpiler.toLowerCase();
      if (transpiler !== 'babel' && transpiler !== 'traceur')
        transpiler = globalConfig.config.defaultTranspiler;
      self.transpiler = transpiler;
      globalConfig.config.defaultTranspiler = transpiler;
    });
  };
  
  Config.prototype.ensureRegistry = function(registryName, remote) {
    var registries = this.registries;
  
    if (typeof remote === 'undefined') {
      // detect the registry mode from the first registry
      var registryKeys = Object.keys(registries);
      if (registryKeys.length > 0) {
        remote = registries[registryKeys[0]].mode === 'remote';
      } else {
        remote = false;
      }
    }
  
    if (registries[registryName]) {
      if (remote)
        registries[registryName].setRemote();
      else
        registries[registryName].setLocal();
      return;
    }
  
    var ep = registries[registryName] = new RegistryPath(registryName);
    if (remote)
      ep.setRemote();
    else
      ep.setLocal();
  };
  
  // return the loader configuration for a server loading use
  Config.prototype.getConfig = function() {
    var cfg = extend({}, this.__originalConfig);
  
    // set all registry paths to be local paths
    cfg.paths = extend({}, cfg.paths);
    var registries = this.registries;
    for (var e in registries) {
      if (registries.hasOwnProperty(e))
        cfg.paths[e + ':*'] = registries[e].local;
    }
    return cfg;
  };
  
  /*
   * RegistryPath object
   */
  var jspmPackages;
  function RegistryPath(name, registryPath) {
    jspmPackages = jspmPackages || path.relative(config.pjson.baseURL, config.pjson.packages).replace(/\\/g, '/');
    var registryRemote = registry.load(name).remote;
    this.remote = registryRemote + '/*.js';
    this.local = jspmPackages + '/' + name + '/*.js';
  
    this.mode = 'local';
    if (registryPath === this.remote)
      this.mode = 'remote';
    this.path = registryPath;
  }
  RegistryPath.prototype.setLocal = function() {
    this.path = this.local;
    this.mode = 'local';
  };
  RegistryPath.prototype.setRemote = function() {
    this.path = this.remote;
    this.mode = 'remote';
  };
  RegistryPath.prototype.write = function() {
    return this.path;
  };
  
  Config.prototype.write = function() {
    // extract over original config to keep initial values
    var cfg = extractObj(this, this.__originalConfig),
        cfgRegistries, cfgMap, cfgVersions;
  
    // allow * path to be overridden
    if (!cfg.paths['*'])
      cfg.paths['*'] = '*.js';
  
    cfgRegistries = cfg.registries;
    for (var e in cfgRegistries) {
      if (!cfgRegistries.hasOwnProperty(e))
        continue;
      var val = cfgRegistries[e];
      delete cfgRegistries[e];
      cfgRegistries[e + ':*'] = val;
    }
  
    extend(cfg.paths, alphabetize(cfg.registries));
    delete cfg.registries;
  
    cfg.baseMap = alphabetize(cfg.baseMap);
    cfg.map = extend(cfg.baseMap, alphabetize(cfg.depMap));
    delete cfg.baseMap;
    delete cfg.depMap;
  
    cfgMap = cfg.map;
    for (var p in cfgMap) {
      if (!cfgMap.hasOwnProperty(p))
        continue;
      var subMap = cfgMap[p];
      if (typeof subMap === 'object') {
        if (!hasProperties(subMap))
          delete cfgMap[p];
        else
          cfgMap[p] = alphabetize(cfgMap[p]);
      }
    }
  
    cfgVersions = cfg.versions;
    for (var v in cfgVersions) {
      if (!cfgVersions.hasOwnProperty(v))
        continue;
      var version = cfgVersions[v];
      if (version.length === 1)
        cfgVersions[v] = version[0];
      if (version.length === 0)
        delete cfgVersions[v];
    }
  
    var configContent = '';
  
    var meta = cfg.meta;
    var depCache = cfg.depCache;
    var map = cfg.map;
    var versions = alphabetize(cfg.versions);
  
    delete cfg.meta;
    delete cfg.depCache;
    delete cfg.map;
    delete cfg.versions;
  
    if (!hasProperties(cfg.babelOptions))
      delete cfg.babelOptions;
    if (!hasProperties(cfg.traceurOptions))
      delete cfg.traceurOptions;
  
    if (cfg.bundles && !hasProperties(cfg.bundles))
      delete cfg.bundles;
  
    if (hasProperties(cfg))
      configContent += 'System.config(' + stringify(cfg) + ');' + config.newLine + config.newLine;
  
    cfg.meta = meta;
    cfg.depCache = depCache;
    cfg.map = map;
    cfg.versions = versions;
  
    if (hasProperties(meta))
      configContent += 'System.config(' + stringify({ meta: meta }) + ');' + config.newLine + config.newLine;
  
    if (hasProperties(depCache))
      configContent += 'System.config(' + stringify({ depCache: depCache }) + ');' + config.newLine + config.newLine;
  
    if (hasProperties(map))
      configContent += 'System.config(' + stringify({ map: map }) + ');' + config.newLine + config.newLine;
  
    if (hasProperties(versions))
      configContent += 'System.config(' + stringify({ versions: versions }) + ');' + config.newLine + config.newLine;
  
    return asp(fs.writeFile)(this.__fileName, configContent);
  };
  module.exports = Config;
  
  
  
  
  // --- can be removed after jspm@0.8 is fully deprecated --
  var semver = require('../semver');
  function upgradeToExactVersionResolution(config) {
    // run through depMap and baseMap, and assign exact version matches
    Object.keys(config.baseMap).forEach(function(p) {
      upgradeToExactVersionResolveRange(config.baseMap[p], config);
    });
  
    Object.keys(config.depMap).forEach(function(p) {
      var curMap = config.depMap[p];
      Object.keys(curMap).forEach(function(p) {
        upgradeToExactVersionResolveRange(curMap[p], config);
      });
    });
  
    config.versions = {};
  }
  function upgradeToExactVersionResolveRange(range, config) {
    var versions = config.versions[range.name];
    if (versions)
    versions.sort(semver.compare).reverse().some(function(version) {
      if (semver.match(range.version, version)) {
        range.setVersion(version);
        return true;
      }
    });
  }
  