(function() {
  var Directory, Disposable, Emitter, EmitterMixin, File, Grim, PathWatcher, Q, crypto, fs, iconv, path, runas, _, _ref,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    __slice = [].slice;

  crypto = require('crypto');

  path = require('path');

  _ = require('underscore-plus');

  _ref = require('event-kit'), Emitter = _ref.Emitter, Disposable = _ref.Disposable;

  fs = require('fs-plus');

  Grim = require('grim');

  Q = require('q');

  runas = null;

  iconv = null;

  Directory = null;

  PathWatcher = require('./main');

  module.exports = File = (function() {
    File.prototype.encoding = 'utf8';

    File.prototype.realPath = null;

    File.prototype.subscriptionCount = 0;


    /*
    Section: Construction
     */

    function File(filePath, symlink) {
      this.symlink = symlink != null ? symlink : false;
      this.didRemoveSubscription = __bind(this.didRemoveSubscription, this);
      this.willAddSubscription = __bind(this.willAddSubscription, this);
      if (filePath) {
        filePath = path.normalize(filePath);
      }
      this.path = filePath;
      this.emitter = new Emitter;
      if (Grim.includeDeprecatedAPIs) {
        this.on('contents-changed-subscription-will-be-added', this.willAddSubscription);
        this.on('moved-subscription-will-be-added', this.willAddSubscription);
        this.on('removed-subscription-will-be-added', this.willAddSubscription);
        this.on('contents-changed-subscription-removed', this.didRemoveSubscription);
        this.on('moved-subscription-removed', this.didRemoveSubscription);
        this.on('removed-subscription-removed', this.didRemoveSubscription);
      }
      this.cachedContents = null;
      this.reportOnDeprecations = true;
    }

    File.prototype.create = function() {
      return this.exists().then((function(_this) {
        return function(isExistingFile) {
          var parent;
          if (!isExistingFile) {
            parent = _this.getParent();
            return parent.create().then(function() {
              return _this.write('').then(function() {
                return true;
              });
            });
          } else {
            return false;
          }
        };
      })(this));
    };


    /*
    Section: Event Subscription
     */

    File.prototype.onDidChange = function(callback) {
      this.willAddSubscription();
      return this.trackUnsubscription(this.emitter.on('did-change', callback));
    };

    File.prototype.onDidRename = function(callback) {
      this.willAddSubscription();
      return this.trackUnsubscription(this.emitter.on('did-rename', callback));
    };

    File.prototype.onDidDelete = function(callback) {
      this.willAddSubscription();
      return this.trackUnsubscription(this.emitter.on('did-delete', callback));
    };

    File.prototype.onWillThrowWatchError = function(callback) {
      return this.emitter.on('will-throw-watch-error', callback);
    };

    File.prototype.willAddSubscription = function() {
      this.subscriptionCount++;
      try {
        return this.subscribeToNativeChangeEvents();
      } catch (_error) {}
    };

    File.prototype.didRemoveSubscription = function() {
      this.subscriptionCount--;
      if (this.subscriptionCount === 0) {
        return this.unsubscribeFromNativeChangeEvents();
      }
    };

    File.prototype.trackUnsubscription = function(subscription) {
      return new Disposable((function(_this) {
        return function() {
          subscription.dispose();
          return _this.didRemoveSubscription();
        };
      })(this));
    };


    /*
    Section: File Metadata
     */

    File.prototype.isFile = function() {
      return true;
    };

    File.prototype.isDirectory = function() {
      return false;
    };

    File.prototype.exists = function() {
      return Q.Promise((function(_this) {
        return function(resolve, reject) {
          return fs.exists(_this.getPath(), resolve);
        };
      })(this));
    };

    File.prototype.existsSync = function() {
      return fs.existsSync(this.getPath());
    };

    File.prototype.getDigest = function() {
      if (this.digest) {
        return Q(this.digest);
      }
      return this.read().then((function(_this) {
        return function(contents) {
          return _this.digest;
        };
      })(this));
    };

    File.prototype.getDigestSync = function() {
      if (!this.digest) {
        this.readSync();
      }
      return this.digest;
    };

    File.prototype.setDigest = function(contents) {
      return this.digest = crypto.createHash('sha1').update(contents != null ? contents : '').digest('hex');
    };

    File.prototype.setEncoding = function(encoding) {
      this.encoding = encoding != null ? encoding : 'utf8';
    };

    File.prototype.getEncoding = function() {
      return this.encoding;
    };


    /*
    Section: Managing Paths
     */

    File.prototype.getPath = function() {
      return this.path;
    };

    File.prototype.setPath = function(path) {
      this.path = path;
      return this.realPath = null;
    };

    File.prototype.getRealPathSync = function() {
      var error;
      if (this.realPath == null) {
        try {
          this.realPath = fs.realpathSync(this.path);
        } catch (_error) {
          error = _error;
          this.realPath = this.path;
        }
      }
      return this.realPath;
    };

    File.prototype.getRealPath = function() {
      if (this.realPath != null) {
        return Q(this.realPath);
      } else {
        return Q.nfcall(fs.realpath, this.path).then((function(_this) {
          return function(realPath) {
            return _this.realPath = realPath;
          };
        })(this));
      }
    };

    File.prototype.getBaseName = function() {
      return path.basename(this.path);
    };


    /*
    Section: Traversing
     */

    File.prototype.getParent = function() {
      if (Directory == null) {
        Directory = require('./directory');
      }
      return new Directory(path.dirname(this.path));
    };


    /*
    Section: Reading and Writing
     */

    File.prototype.readSync = function(flushCache) {
      var encoding;
      if (!this.existsSync()) {
        this.cachedContents = null;
      } else if ((this.cachedContents == null) || flushCache) {
        encoding = this.getEncoding();
        if (encoding === 'utf8') {
          this.cachedContents = fs.readFileSync(this.getPath(), encoding);
        } else {
          if (iconv == null) {
            iconv = require('iconv-lite');
          }
          this.cachedContents = iconv.decode(fs.readFileSync(this.getPath()), encoding);
        }
      }
      this.setDigest(this.cachedContents);
      return this.cachedContents;
    };

    File.prototype.writeFileSync = function(filePath, contents) {
      var encoding;
      encoding = this.getEncoding();
      if (encoding === 'utf8') {
        return fs.writeFileSync(filePath, contents, {
          encoding: encoding
        });
      } else {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        return fs.writeFileSync(filePath, iconv.encode(contents, encoding));
      }
    };

    File.prototype.read = function(flushCache) {
      var bytesRead, content, deferred, encoding, promise, readStream;
      if ((this.cachedContents != null) && !flushCache) {
        promise = Q(this.cachedContents);
      } else {
        deferred = Q.defer();
        promise = deferred.promise;
        content = [];
        bytesRead = 0;
        encoding = this.getEncoding();
        if (encoding === 'utf8') {
          readStream = fs.createReadStream(this.getPath(), {
            encoding: encoding
          });
        } else {
          if (iconv == null) {
            iconv = require('iconv-lite');
          }
          readStream = fs.createReadStream(this.getPath()).pipe(iconv.decodeStream(encoding));
        }
        readStream.on('data', function(chunk) {
          content.push(chunk);
          bytesRead += chunk.length;
          return deferred.notify(bytesRead);
        });
        readStream.on('end', function() {
          return deferred.resolve(content.join(''));
        });
        readStream.on('error', function(error) {
          if (error.code === 'ENOENT') {
            return deferred.resolve(null);
          } else {
            return deferred.reject(error);
          }
        });
      }
      return promise.then((function(_this) {
        return function(contents) {
          _this.setDigest(contents);
          return _this.cachedContents = contents;
        };
      })(this));
    };

    File.prototype.write = function(text) {
      return this.exists().then((function(_this) {
        return function(previouslyExisted) {
          return _this.writeFile(_this.getPath(), text).then(function() {
            _this.cachedContents = text;
            if (!previouslyExisted && _this.hasSubscriptions()) {
              _this.subscribeToNativeChangeEvents();
            }
            return void 0;
          });
        };
      })(this));
    };

    File.prototype.writeSync = function(text) {
      var previouslyExisted;
      previouslyExisted = this.existsSync();
      this.writeFileWithPrivilegeEscalationSync(this.getPath(), text);
      this.cachedContents = text;
      if (!previouslyExisted && this.hasSubscriptions()) {
        this.subscribeToNativeChangeEvents();
      }
      return void 0;
    };

    File.prototype.writeFile = function(filePath, contents) {
      var encoding;
      encoding = this.getEncoding();
      if (encoding === 'utf8') {
        return Q.nfcall(fs.writeFile, filePath, contents, {
          encoding: encoding
        });
      } else {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        return Q.nfcall(fs.writeFile, filePath, iconv.encode(contents, encoding));
      }
    };

    File.prototype.writeFileWithPrivilegeEscalationSync = function(filePath, text) {
      var error;
      try {
        return this.writeFileSync(filePath, text);
      } catch (_error) {
        error = _error;
        if (error.code === 'EACCES' && process.platform === 'darwin') {
          if (runas == null) {
            runas = require('runas');
          }
          if (runas('/bin/dd', ["of=" + filePath], {
            stdin: text,
            admin: true
          }) !== 0) {
            throw error;
          }
        } else {
          throw error;
        }
      }
    };


    /*
    Section: Private
     */

    File.prototype.handleNativeChangeEvent = function(eventType, eventPath) {
      var error, handleReadError, oldContents;
      switch (eventType) {
        case 'delete':
          this.unsubscribeFromNativeChangeEvents();
          return this.detectResurrectionAfterDelay();
        case 'rename':
          this.setPath(eventPath);
          if (Grim.includeDeprecatedAPIs) {
            this.emit('moved');
          }
          return this.emitter.emit('did-rename');
        case 'change':
        case 'resurrect':
          oldContents = this.cachedContents;
          handleReadError = (function(_this) {
            return function(error) {
              var handle, handled, newError;
              _this.unsubscribeFromNativeChangeEvents();
              handled = false;
              handle = function() {
                return handled = true;
              };
              error.eventType = eventType;
              _this.emitter.emit('will-throw-watch-error', {
                error: error,
                handle: handle
              });
              if (!handled) {
                newError = new Error("Cannot read file after file `" + eventType + "` event: " + _this.path);
                newError.originalError = error;
                newError.code = "ENOENT";
                newError.path;
                return console.error(newError);
              }
            };
          })(this);
          try {
            return this.read(true)["catch"](handleReadError).done((function(_this) {
              return function(newContents) {
                if (oldContents !== newContents) {
                  if (Grim.includeDeprecatedAPIs) {
                    _this.emit('contents-changed');
                  }
                  return _this.emitter.emit('did-change');
                }
              };
            })(this));
          } catch (_error) {
            error = _error;
            return handleReadError(error);
          }
      }
    };

    File.prototype.detectResurrectionAfterDelay = function() {
      return _.delay(((function(_this) {
        return function() {
          return _this.detectResurrection();
        };
      })(this)), 50);
    };

    File.prototype.detectResurrection = function() {
      return this.exists().then((function(_this) {
        return function(exists) {
          if (exists) {
            _this.subscribeToNativeChangeEvents();
            return _this.handleNativeChangeEvent('resurrect', _this.getPath());
          } else {
            _this.cachedContents = null;
            if (Grim.includeDeprecatedAPIs) {
              _this.emit('removed');
            }
            return _this.emitter.emit('did-delete');
          }
        };
      })(this));
    };

    File.prototype.subscribeToNativeChangeEvents = function() {
      return this.watchSubscription != null ? this.watchSubscription : this.watchSubscription = PathWatcher.watch(this.path, (function(_this) {
        return function() {
          var args;
          args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          return _this.handleNativeChangeEvent.apply(_this, args);
        };
      })(this));
    };

    File.prototype.unsubscribeFromNativeChangeEvents = function() {
      if (this.watchSubscription != null) {
        this.watchSubscription.close();
        return this.watchSubscription = null;
      }
    };

    return File;

  })();

  if (Grim.includeDeprecatedAPIs) {
    EmitterMixin = require('emissary').Emitter;
    EmitterMixin.includeInto(File);
    File.prototype.on = function(eventName) {
      switch (eventName) {
        case 'contents-changed':
          Grim.deprecate("Use File::onDidChange instead");
          break;
        case 'moved':
          Grim.deprecate("Use File::onDidRename instead");
          break;
        case 'removed':
          Grim.deprecate("Use File::onDidDelete instead");
          break;
        default:
          if (this.reportOnDeprecations) {
            Grim.deprecate("Subscribing via ::on is deprecated. Use documented event subscription methods instead.");
          }
      }
      return EmitterMixin.prototype.on.apply(this, arguments);
    };
  } else {
    File.prototype.hasSubscriptions = function() {
      return this.subscriptionCount > 0;
    };
  }

}).call(this);
