(function() {
  var CSON, CompositeDisposable, Disposable, Emitter, File, Snippet, SnippetExpansion, async, fs, path, _, _ref,
    __slice = [].slice;

  path = require('path');

  _ref = require('atom'), Emitter = _ref.Emitter, Disposable = _ref.Disposable, CompositeDisposable = _ref.CompositeDisposable;

  _ = require('underscore-plus');

  async = require('async');

  CSON = require('season');

  File = require('pathwatcher').File;

  fs = require('fs-plus');

  Snippet = require('./snippet');

  SnippetExpansion = require('./snippet-expansion');

  module.exports = {
    loaded: false,
    activate: function() {
      var snippets;
      this.subscriptions = new CompositeDisposable;
      this.subscriptions.add(atom.workspace.addOpener((function(_this) {
        return function(uri) {
          if (uri === 'atom://.atom/snippets') {
            return atom.project.open(_this.getUserSnippetsPath());
          }
        };
      })(this)));
      this.loadAll();
      this.watchUserSnippets((function(_this) {
        return function(watchDisposable) {
          return _this.subscriptions.add(watchDisposable);
        };
      })(this));
      snippets = this;
      this.subscriptions.add(atom.commands.add('atom-text-editor', {
        'snippets:expand': function(event) {
          var editor;
          editor = this.getModel();
          if (snippets.snippetToExpandUnderCursor(editor)) {
            snippets.clearExpansions(editor);
            return snippets.expandSnippetsUnderCursors(editor);
          } else {
            return event.abortKeyBinding();
          }
        },
        'snippets:next-tab-stop': function(event) {
          var editor;
          editor = this.getModel();
          if (!snippets.goToNextTabStop(editor)) {
            return event.abortKeyBinding();
          }
        },
        'snippets:previous-tab-stop': function(event) {
          var editor;
          editor = this.getModel();
          if (!snippets.goToPreviousTabStop(editor)) {
            return event.abortKeyBinding();
          }
        },
        'snippets:available': function(event) {
          var SnippetsAvailable, editor;
          editor = this.getModel();
          SnippetsAvailable = require('./snippets-available');
          if (snippets.availableSnippetsView == null) {
            snippets.availableSnippetsView = new SnippetsAvailable(snippets);
          }
          return snippets.availableSnippetsView.toggle(editor);
        }
      }));
      return this.subscriptions.add(atom.workspace.observeTextEditors((function(_this) {
        return function(editor) {
          return _this.clearExpansions(editor);
        };
      })(this)));
    },
    deactivate: function() {
      var _ref1;
      if ((_ref1 = this.emitter) != null) {
        _ref1.dispose();
      }
      this.emitter = null;
      this.editorSnippetExpansions = null;
      return atom.config.transact((function(_this) {
        return function() {
          return _this.subscriptions.dispose();
        };
      })(this));
    },
    getUserSnippetsPath: function() {
      var userSnippetsPath;
      userSnippetsPath = CSON.resolve(path.join(atom.getConfigDirPath(), 'snippets'));
      return userSnippetsPath != null ? userSnippetsPath : path.join(atom.getConfigDirPath(), 'snippets.cson');
    },
    loadAll: function(callback) {
      return this.loadBundledSnippets((function(_this) {
        return function(bundledSnippets) {
          return _this.loadPackageSnippets(function(packageSnippets) {
            return _this.loadUserSnippets(function(userSnippets) {
              atom.config.transact(function() {
                var filepath, snippetSet, snippetsBySelector, _i, _len, _ref1, _results;
                _ref1 = [bundledSnippets, packageSnippets, userSnippets];
                _results = [];
                for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
                  snippetSet = _ref1[_i];
                  _results.push((function() {
                    var _results1;
                    _results1 = [];
                    for (filepath in snippetSet) {
                      snippetsBySelector = snippetSet[filepath];
                      _results1.push(this.add(filepath, snippetsBySelector));
                    }
                    return _results1;
                  }).call(_this));
                }
                return _results;
              });
              return _this.doneLoading();
            });
          });
        };
      })(this));
    },
    loadBundledSnippets: function(callback) {
      var bundledSnippetsPath;
      bundledSnippetsPath = CSON.resolve(path.join(__dirname, 'snippets'));
      return this.loadSnippetsFile(bundledSnippetsPath, function(snippets) {
        var snippetsByPath;
        snippetsByPath = {};
        snippetsByPath[bundledSnippetsPath] = snippets;
        return callback(snippetsByPath);
      });
    },
    loadUserSnippets: function(callback) {
      var userSnippetsPath;
      userSnippetsPath = this.getUserSnippetsPath();
      return fs.stat(userSnippetsPath, (function(_this) {
        return function(error, stat) {
          if (stat != null ? stat.isFile() : void 0) {
            return _this.loadSnippetsFile(userSnippetsPath, function(snippets) {
              var result;
              result = {};
              result[userSnippetsPath] = snippets;
              return callback(result);
            });
          } else {
            return callback({});
          }
        };
      })(this));
    },
    watchUserSnippets: function(callback) {
      var userSnippetsPath;
      userSnippetsPath = this.getUserSnippetsPath();
      return fs.stat(userSnippetsPath, (function(_this) {
        return function(error, stat) {
          var e, message, userSnipetsFileDisposable, userSnippetsFile;
          if (stat != null ? stat.isFile() : void 0) {
            userSnipetsFileDisposable = new CompositeDisposable();
            userSnippetsFile = new File(userSnippetsPath);
            try {
              userSnipetsFileDisposable.add(userSnippetsFile.onDidChange(function() {
                return _this.handleUserSnippetsDidChange();
              }));
              userSnipetsFileDisposable.add(userSnippetsFile.onDidDelete(function() {
                return _this.handleUserSnippetsDidChange();
              }));
              userSnipetsFileDisposable.add(userSnippetsFile.onDidRename(function() {
                return _this.handleUserSnippetsDidChange();
              }));
            } catch (_error) {
              e = _error;
              message = "Unable to watch path: `snippets.cson`. Make sure you have permissions\nto the `~/.atom` directory and `" + userSnippetsPath + "`.\n\nOn linux there are currently problems with watch sizes. See\n[this document][watches] for more info.\n[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path";
              atom.notifications.addError(message, {
                dismissable: true
              });
            }
            return callback(userSnipetsFileDisposable);
          } else {
            return callback(new Disposable(function() {}));
          }
        };
      })(this));
    },
    handleUserSnippetsDidChange: function() {
      var userSnippetsPath;
      userSnippetsPath = this.getUserSnippetsPath();
      return atom.config.transact((function(_this) {
        return function() {
          atom.config.unset(null, {
            source: userSnippetsPath
          });
          return _this.loadSnippetsFile(userSnippetsPath, function(result) {
            return _this.add(userSnippetsPath, result);
          });
        };
      })(this));
    },
    loadPackageSnippets: function(callback) {
      var pack, packages, snippetsDirPaths;
      packages = atom.packages.getLoadedPackages();
      snippetsDirPaths = (function() {
        var _i, _len, _results;
        _results = [];
        for (_i = 0, _len = packages.length; _i < _len; _i++) {
          pack = packages[_i];
          _results.push(path.join(pack.path, 'snippets'));
        }
        return _results;
      })();
      return async.map(snippetsDirPaths, this.loadSnippetsDirectory.bind(this), (function(_this) {
        return function(error, results) {
          return callback(_.extend.apply(_, [{}].concat(__slice.call(results))));
        };
      })(this));
    },
    doneLoading: function() {
      this.loaded = true;
      return this.getEmitter().emit('did-load-snippets');
    },
    onDidLoadSnippets: function(callback) {
      return this.getEmitter().on('did-load-snippets', callback);
    },
    getEmitter: function() {
      return this.emitter != null ? this.emitter : this.emitter = new Emitter;
    },
    loadSnippetsDirectory: function(snippetsDirPath, callback) {
      return fs.isDirectory(snippetsDirPath, (function(_this) {
        return function(isDirectory) {
          if (!isDirectory) {
            return callback(null, {});
          }
          return fs.readdir(snippetsDirPath, function(error, entries) {
            if (error) {
              console.warn("Error reading snippets directory " + snippetsDirPath, error);
              return callback(null, {});
            }
            return async.map(entries, function(entry, done) {
              var filePath;
              filePath = path.join(snippetsDirPath, entry);
              return _this.loadSnippetsFile(filePath, function(snippets) {
                return done(null, {
                  filePath: filePath,
                  snippets: snippets
                });
              });
            }, function(error, results) {
              var filePath, snippets, snippetsByPath, _i, _len, _ref1;
              snippetsByPath = {};
              for (_i = 0, _len = results.length; _i < _len; _i++) {
                _ref1 = results[_i], filePath = _ref1.filePath, snippets = _ref1.snippets;
                snippetsByPath[filePath] = snippets;
              }
              return callback(null, snippetsByPath);
            });
          });
        };
      })(this));
    },
    loadSnippetsFile: function(filePath, callback) {
      if (!CSON.isObjectPath(filePath)) {
        return callback({});
      }
      return CSON.readFile(filePath, (function(_this) {
        return function(error, object) {
          var _ref1, _ref2;
          if (object == null) {
            object = {};
          }
          if (error != null) {
            console.warn("Error reading snippets file '" + filePath + "': " + ((_ref1 = error.stack) != null ? _ref1 : error));
            if ((_ref2 = atom.notifications) != null) {
              _ref2.addError("Failed to load snippets from '" + filePath + "'", {
                detail: error.message,
                dismissable: true
              });
            }
          }
          return callback(object);
        };
      })(this));
    },
    add: function(filePath, snippetsBySelector) {
      var attributes, body, bodyTree, name, prefix, selector, snippet, snippetsByName, snippetsByPrefix, _results;
      _results = [];
      for (selector in snippetsBySelector) {
        snippetsByName = snippetsBySelector[selector];
        snippetsByPrefix = {};
        for (name in snippetsByName) {
          attributes = snippetsByName[name];
          prefix = attributes.prefix, body = attributes.body, bodyTree = attributes.bodyTree;
          if (typeof body !== 'string') {
            continue;
          }
          if (bodyTree == null) {
            bodyTree = this.getBodyParser().parse(body);
          }
          snippet = new Snippet({
            name: name,
            prefix: prefix,
            bodyTree: bodyTree,
            bodyText: body
          });
          snippetsByPrefix[snippet.prefix] = snippet;
        }
        _results.push(atom.config.set('snippets', snippetsByPrefix, {
          source: filePath,
          scopeSelector: selector
        }));
      }
      return _results;
    },
    getBodyParser: function() {
      return this.bodyParser != null ? this.bodyParser : this.bodyParser = require('./snippet-body-parser');
    },
    getPrefixText: function(snippets, editor) {
      var cursor, cursorSnippetPrefix, cursorWordPrefix, position, prefixStart, snippetPrefix, wordPrefix, wordRegex, wordStart, _i, _len, _ref1, _ref2;
      wordRegex = this.wordRegexForSnippets(snippets);
      _ref1 = [], snippetPrefix = _ref1[0], wordPrefix = _ref1[1];
      _ref2 = editor.getCursors();
      for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
        cursor = _ref2[_i];
        position = cursor.getBufferPosition();
        prefixStart = cursor.getBeginningOfCurrentWordBufferPosition({
          wordRegex: wordRegex
        });
        cursorSnippetPrefix = editor.getTextInRange([prefixStart, position]);
        if ((snippetPrefix != null) && cursorSnippetPrefix !== snippetPrefix) {
          return null;
        }
        snippetPrefix = cursorSnippetPrefix;
        wordStart = cursor.getBeginningOfCurrentWordBufferPosition();
        cursorWordPrefix = editor.getTextInRange([wordStart, position]);
        if ((wordPrefix != null) && cursorWordPrefix !== wordPrefix) {
          return null;
        }
        wordPrefix = cursorWordPrefix;
      }
      return {
        snippetPrefix: snippetPrefix,
        wordPrefix: wordPrefix
      };
    },
    wordRegexForSnippets: function(snippets) {
      var character, prefix, prefixCharacters, prefixes, _i, _len;
      prefixes = {};
      for (prefix in snippets) {
        for (_i = 0, _len = prefix.length; _i < _len; _i++) {
          character = prefix[_i];
          prefixes[character] = true;
        }
      }
      prefixCharacters = Object.keys(prefixes).join('');
      return new RegExp("[" + (_.escapeRegExp(prefixCharacters)) + "]+");
    },
    snippetForPrefix: function(snippets, prefix, wordPrefix) {
      var longestPrefixMatch, snippet, snippetPrefix;
      longestPrefixMatch = null;
      for (snippetPrefix in snippets) {
        snippet = snippets[snippetPrefix];
        if (_.endsWith(prefix, snippetPrefix) && wordPrefix.length <= snippetPrefix.length) {
          if ((longestPrefixMatch == null) || snippetPrefix.length > longestPrefixMatch.prefix.length) {
            longestPrefixMatch = snippet;
          }
        }
      }
      return longestPrefixMatch;
    },
    getSnippets: function(editor) {
      return atom.config.get('snippets', {
        scope: editor.getLastCursor().getScopeDescriptor()
      });
    },
    snippetToExpandUnderCursor: function(editor) {
      var prefixData, snippets;
      if (!editor.getLastSelection().isEmpty()) {
        return false;
      }
      snippets = this.getSnippets(editor);
      if (_.isEmpty(snippets)) {
        return false;
      }
      if (prefixData = this.getPrefixText(snippets, editor)) {
        return this.snippetForPrefix(snippets, prefixData.snippetPrefix, prefixData.wordPrefix);
      }
    },
    expandSnippetsUnderCursors: function(editor) {
      var snippet;
      if (!(snippet = this.snippetToExpandUnderCursor(editor))) {
        return false;
      }
      editor.transact((function(_this) {
        return function() {
          var cursor, cursorPosition, cursors, startPoint, _i, _len, _results;
          cursors = editor.getCursors();
          _results = [];
          for (_i = 0, _len = cursors.length; _i < _len; _i++) {
            cursor = cursors[_i];
            cursorPosition = cursor.getBufferPosition();
            startPoint = cursorPosition.translate([0, -snippet.prefix.length], [0, 0]);
            cursor.selection.setBufferRange([startPoint, cursorPosition]);
            _results.push(_this.insert(snippet, editor, cursor));
          }
          return _results;
        };
      })(this));
      return true;
    },
    goToNextTabStop: function(editor) {
      var expansion, nextTabStopVisited, _i, _len, _ref1;
      nextTabStopVisited = false;
      _ref1 = this.getExpansions(editor);
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        expansion = _ref1[_i];
        if (expansion != null ? expansion.goToNextTabStop() : void 0) {
          nextTabStopVisited = true;
        }
      }
      return nextTabStopVisited;
    },
    goToPreviousTabStop: function(editor) {
      var expansion, previousTabStopVisited, _i, _len, _ref1;
      previousTabStopVisited = false;
      _ref1 = this.getExpansions(editor);
      for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
        expansion = _ref1[_i];
        if (expansion != null ? expansion.goToPreviousTabStop() : void 0) {
          previousTabStopVisited = true;
        }
      }
      return previousTabStopVisited;
    },
    getExpansions: function(editor) {
      var _ref1, _ref2;
      return (_ref1 = (_ref2 = this.editorSnippetExpansions) != null ? _ref2.get(editor) : void 0) != null ? _ref1 : [];
    },
    clearExpansions: function(editor) {
      if (this.editorSnippetExpansions == null) {
        this.editorSnippetExpansions = new WeakMap();
      }
      return this.editorSnippetExpansions.set(editor, []);
    },
    addExpansion: function(editor, snippetExpansion) {
      return this.getExpansions(editor).push(snippetExpansion);
    },
    insert: function(snippet, editor, cursor) {
      var bodyTree;
      if (editor == null) {
        editor = atom.workspace.getActiveTextEditor();
      }
      if (cursor == null) {
        cursor = editor.getLastCursor();
      }
      if (typeof snippet === 'string') {
        bodyTree = this.getBodyParser().parse(snippet);
        snippet = new Snippet({
          name: '__anonymous',
          prefix: '',
          bodyTree: bodyTree,
          bodyText: snippet
        });
      }
      return new SnippetExpansion(snippet, editor, cursor, this);
    },
    provideSnippets: function() {
      return {
        bundledSnippetsLoaded: (function(_this) {
          return function() {
            return _this.loaded;
          };
        })(this),
        insertSnippet: this.insert.bind(this)
      };
    }
  };

}).call(this);
