(function() {
  var CompositeDisposable, Disposable, PropertySet, ScopedPropertyStore, Selector, checkValueAtKeyPath, deepClone, deepDefaults, deprecate, getValueAtKeyPath, includeDeprecatedAPIs, isPlainObject, slick, _, _ref, _ref1, _ref2,
    __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; };

  slick = require('atom-slick');

  _ = require('underscore-plus');

  getValueAtKeyPath = require('key-path-helpers').getValueAtKeyPath;

  _ref = require('grim'), includeDeprecatedAPIs = _ref.includeDeprecatedAPIs, deprecate = _ref.deprecate;

  _ref1 = require('event-kit'), Disposable = _ref1.Disposable, CompositeDisposable = _ref1.CompositeDisposable;

  Selector = require('./selector');

  PropertySet = require('./property-set');

  _ref2 = require('./helpers'), isPlainObject = _ref2.isPlainObject, checkValueAtKeyPath = _ref2.checkValueAtKeyPath, deepDefaults = _ref2.deepDefaults, deepClone = _ref2.deepClone;

  module.exports = ScopedPropertyStore = (function() {
    function ScopedPropertyStore() {
      this.cache = {};
      this.propertySets = [];
      this.escapeCharacterRegex = /[-!"#$%&'*+,/:;=?@|^~()<>{}[\]]/g;
    }

    ScopedPropertyStore.prototype.addProperties = function(source, propertiesBySelector, options) {
      var compositeDisposable, properties, selector, selectorSource, _i, _len, _ref3;
      this.bustCache();
      compositeDisposable = new CompositeDisposable;
      for (selectorSource in propertiesBySelector) {
        properties = propertiesBySelector[selectorSource];
        _ref3 = Selector.create(selectorSource, options);
        for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
          selector = _ref3[_i];
          compositeDisposable.add(this.addPropertySet(new PropertySet(source, selector, properties)));
        }
      }
      this.propertySets.sort(function(a, b) {
        return a.compare(b);
      });
      return compositeDisposable;
    };

    ScopedPropertyStore.prototype.getPropertyValue = function(scopeChain, keyPath, options) {
      var excludeSources, sources;
      if (options != null) {
        sources = options.sources, excludeSources = options.excludeSources;
      }
      return this.withCaching("getPropertyValue:" + scopeChain + ":" + keyPath, (sources != null) || (excludeSources != null), (function(_this) {
        return function() {
          var hasMergedValue, hasValue, mergedValue, scopes, set, value, _i, _len, _ref3, _ref4, _ref5, _ref6;
          scopes = _this.parseScopeChain(scopeChain);
          mergedValue = void 0;
          hasMergedValue = false;
          while (scopes.length > 0) {
            _ref3 = _this.propertySets;
            for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
              set = _ref3[_i];
              if ((excludeSources != null) && (_ref4 = set.source, __indexOf.call(excludeSources, _ref4) >= 0)) {
                continue;
              }
              if ((sources != null) && !(_ref5 = set.source, __indexOf.call(sources, _ref5) >= 0)) {
                continue;
              }
              if (set.matches(scopes)) {
                _ref6 = checkValueAtKeyPath(set.properties, keyPath), value = _ref6[0], hasValue = _ref6[1];
                if (hasValue) {
                  if (hasMergedValue) {
                    deepDefaults(mergedValue, value);
                  } else {
                    hasMergedValue = true;
                    mergedValue = deepClone(value);
                  }
                  if (!isPlainObject(mergedValue)) {
                    return mergedValue;
                  }
                }
              }
            }
            scopes.pop();
          }
          return mergedValue;
        };
      })(this));
    };

    ScopedPropertyStore.prototype.getAll = function(scopeChain, keyPath, options) {
      var excludeSources, scopes, sources, values;
      if (options != null) {
        sources = options.sources, excludeSources = options.excludeSources;
      }
      scopes = this.parseScopeChain(scopeChain);
      values = [];
      return this.withCaching("getAll:" + scopeChain + ":" + keyPath, (sources != null) || (excludeSources != null), (function(_this) {
        return function() {
          var hasValue, set, value, _i, _len, _ref3, _ref4, _ref5, _ref6;
          while (scopes.length > 0) {
            _ref3 = _this.propertySets;
            for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
              set = _ref3[_i];
              if ((excludeSources != null) && (_ref4 = set.source, __indexOf.call(excludeSources, _ref4) >= 0)) {
                continue;
              }
              if ((sources != null) && !(_ref5 = set.source, __indexOf.call(sources, _ref5) >= 0)) {
                continue;
              }
              if (set.matches(scopes)) {
                _ref6 = checkValueAtKeyPath(set.properties, keyPath), value = _ref6[0], hasValue = _ref6[1];
                if (hasValue) {
                  values.push({
                    scopeSelector: set.selector.toString(),
                    value: value
                  });
                }
              }
            }
            scopes.pop();
          }
          return values;
        };
      })(this));
    };

    ScopedPropertyStore.prototype.propertiesForSource = function(source) {
      var propertiesBySelector, propertySet, propertySets, selector;
      propertySets = this.mergeMatchingPropertySets(this.propertySets.filter(function(set) {
        return set.source === source;
      }));
      propertiesBySelector = {};
      for (selector in propertySets) {
        propertySet = propertySets[selector];
        propertiesBySelector[selector] = propertySet.properties;
      }
      return propertiesBySelector;
    };

    ScopedPropertyStore.prototype.propertiesForSourceAndSelector = function(source, scopeSelector) {
      var properties, propertySet, propertySets, selector, setSelector, _i, _len, _ref3;
      propertySets = this.mergeMatchingPropertySets(this.propertySets.filter(function(set) {
        return set.source === source;
      }));
      properties = {};
      _ref3 = Selector.create(scopeSelector);
      for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
        selector = _ref3[_i];
        for (setSelector in propertySets) {
          propertySet = propertySets[setSelector];
          if (selector.isEqual(setSelector)) {
            _.extend(properties, propertySet.properties);
          }
        }
      }
      return properties;
    };

    ScopedPropertyStore.prototype.propertiesForSelector = function(scopeSelector) {
      var properties, propertySet, propertySets, selector, setSelector, _i, _len, _ref3;
      propertySets = this.mergeMatchingPropertySets(this.propertySets);
      properties = {};
      _ref3 = Selector.create(scopeSelector);
      for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
        selector = _ref3[_i];
        for (setSelector in propertySets) {
          propertySet = propertySets[setSelector];
          if (selector.isEqual(setSelector)) {
            _.extend(properties, propertySet.properties);
          }
        }
      }
      return properties;
    };

    ScopedPropertyStore.prototype.removePropertiesForSource = function(source) {
      this.bustCache();
      return this.propertySets = this.propertySets.filter(function(set) {
        return set.source !== source;
      });
    };

    ScopedPropertyStore.prototype.removePropertiesForSourceAndSelector = function(source, scopeSelector) {
      var selector, _i, _len, _ref3;
      this.bustCache();
      _ref3 = Selector.create(scopeSelector);
      for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
        selector = _ref3[_i];
        this.propertySets = this.propertySets.filter(function(set) {
          return !(set.source === source && set.selector.isEqual(selector));
        });
      }
    };

    ScopedPropertyStore.prototype.mergeMatchingPropertySets = function(propertySets) {
      var matchingPropertySet, merged, propertySet, selector, _i, _len;
      merged = {};
      for (_i = 0, _len = propertySets.length; _i < _len; _i++) {
        propertySet = propertySets[_i];
        selector = propertySet.selector.toString() || '*';
        if (matchingPropertySet = merged[selector]) {
          merged[selector] = matchingPropertySet.merge(propertySet);
        } else {
          merged[selector] = propertySet;
        }
      }
      return merged;
    };

    ScopedPropertyStore.prototype.bustCache = function() {
      return this.cache = {};
    };

    ScopedPropertyStore.prototype.withCaching = function(cacheKey, skipCache, callback) {
      if (skipCache) {
        return callback();
      }
      if (this.cache.hasOwnProperty(cacheKey)) {
        return this.cache[cacheKey];
      } else {
        return this.cache[cacheKey] = callback();
      }
    };

    ScopedPropertyStore.prototype.addPropertySet = function(propertySet) {
      this.propertySets.push(propertySet);
      return new Disposable((function(_this) {
        return function() {
          var index;
          index = _this.propertySets.indexOf(propertySet);
          if (index > -1) {
            _this.propertySets.splice(index, 1);
          }
          return _this.bustCache();
        };
      })(this));
    };

    ScopedPropertyStore.prototype.parseScopeChain = function(scopeChain) {
      var scope, _i, _len, _ref3, _ref4, _results;
      scopeChain = scopeChain.replace(this.escapeCharacterRegex, function(match) {
        return "\\" + match[0];
      });
      _ref4 = (_ref3 = slick.parse(scopeChain)[0]) != null ? _ref3 : [];
      _results = [];
      for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
        scope = _ref4[_i];
        _results.push(scope);
      }
      return _results;
    };

    return ScopedPropertyStore;

  })();

  if (includeDeprecatedAPIs) {
    ScopedPropertyStore.prototype.removeProperties = function(source) {
      deprecate('::addProperties() now returns a disposable. Call .dispose() on that instead.');
      return this.removePropertiesForSource(source);
    };
  }

}).call(this);
