(function() {
  var Directory, Disposable, Emitter, EmitterMixin, File, Grim, PathWatcher, 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');

  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, includeDeprecatedAPIs) {
      this.symlink = symlink != null ? symlink : false;
      if (includeDeprecatedAPIs == null) {
        includeDeprecatedAPIs = Grim.includeDeprecatedAPIs;
      }
      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 (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.isSymbolicLink = function() {
      return this.symlink;
    };

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

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

    File.prototype.getDigest = function() {
      if (this.digest != null) {
        return Promise.resolve(this.digest);
      } else {
        return this.read().then((function(_this) {
          return function() {
            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) {
      if (encoding == null) {
        encoding = 'utf8';
      }
      if (encoding !== 'utf8') {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        iconv.getCodec(encoding);
      }
      return this.encoding = encoding;
    };

    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 Promise.resolve(this.realPath);
      } else {
        return new Promise((function(_this) {
          return function(resolve, reject) {
            return fs.realpath(_this.path, function(err, result) {
              if (err != null) {
                return reject(err);
              } else {
                return resolve(_this.realPath = result);
              }
            });
          };
        })(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 promise;
      if ((this.cachedContents != null) && !flushCache) {
        promise = Promise.resolve(this.cachedContents);
      } else {
        promise = new Promise((function(_this) {
          return function(resolve, reject) {
            var content, readStream;
            content = [];
            readStream = _this.createReadStream();
            readStream.on('data', function(chunk) {
              return content.push(chunk);
            });
            readStream.on('end', function() {
              return resolve(content.join(''));
            });
            return readStream.on('error', function(error) {
              if (error.code === 'ENOENT') {
                return resolve(null);
              } else {
                return reject(error);
              }
            });
          };
        })(this));
      }
      return promise.then((function(_this) {
        return function(contents) {
          _this.setDigest(contents);
          return _this.cachedContents = contents;
        };
      })(this));
    };

    File.prototype.createReadStream = function() {
      var encoding;
      encoding = this.getEncoding();
      if (encoding === 'utf8') {
        return fs.createReadStream(this.getPath(), {
          encoding: encoding
        });
      } else {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        return fs.createReadStream(this.getPath()).pipe(iconv.decodeStream(encoding));
      }
    };

    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;
            _this.setDigest(text);
            if (!previouslyExisted && _this.hasSubscriptions()) {
              _this.subscribeToNativeChangeEvents();
            }
            return void 0;
          });
        };
      })(this));
    };

    File.prototype.createWriteStream = function() {
      var encoding, stream;
      encoding = this.getEncoding();
      if (encoding === 'utf8') {
        return fs.createWriteStream(this.getPath(), {
          encoding: encoding
        });
      } else {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        stream = iconv.encodeStream(encoding);
        stream.pipe(fs.createWriteStream(this.getPath()));
        return stream;
      }
    };

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

    File.prototype.safeWriteSync = function(text) {
      var directoryFD, error, fd;
      try {
        fd = fs.openSync(this.getPath(), 'w');
        fs.writeSync(fd, text);
        fs.fdatasyncSync(fd);
        fs.closeSync(fd);
        if (process.platform !== 'win32') {
          try {
            directoryFD = fs.openSync(path.dirname(this.getPath()), 'r');
            fs.fdatasyncSync(directoryFD);
            fs.closeSync(directoryFD);
          } catch (_error) {
            error = _error;
            console.warn("Non-fatal error syncing parent directory of " + (this.getPath()));
          }
        }
      } catch (_error) {
        error = _error;
        if (error.code === 'EACCES' && process.platform === 'darwin') {
          if (runas == null) {
            runas = require('runas');
          }
          if (runas('/bin/dd', ["of=" + (this.getPath())], {
            stdin: text,
            admin: true
          }) !== 0) {
            throw error;
          }
          if (runas('/bin/sync', [], {
            admin: true
          }) !== 0) {
            throw error;
          }
        } else {
          throw error;
        }
      }
    };

    File.prototype.writeFile = function(filePath, contents) {
      var encoding;
      encoding = this.getEncoding();
      if (encoding === 'utf8') {
        return new Promise(function(resolve, reject) {
          return fs.writeFile(filePath, contents, {
            encoding: encoding
          }, function(err, result) {
            if (err != null) {
              return reject(err);
            } else {
              return resolve(result);
            }
          });
        });
      } else {
        if (iconv == null) {
          iconv = require('iconv-lite');
        }
        return new Promise(function(resolve, reject) {
          return fs.writeFile(filePath, iconv.encode(contents, encoding), function(err, result) {
            if (err != null) {
              return reject(err);
            } else {
              return resolve(result);
            }
          });
        });
      }
    };

    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;
        }
      }
    };

    File.prototype.safeRemoveSync = function() {
      var error, fd;
      try {
        fd = fs.openSync(this.getPath(), 'a');
        fs.fdatasyncSync(fd);
        fs.closeSync(fd);
        fs.removeSync(this.getPath());
      } catch (_error) {
        error = _error;
        if (error.code === 'EACCES' && process.platform === 'darwin') {
          if (runas == null) {
            runas = require('runas');
          }
          if (runas('/bin/sync', [], {
            admin: true
          }) !== 0) {
            throw error;
          }
          if (runas('/bin/rm', ['-f', this.getPath()], {
            admin: true
          }) !== 0) {
            throw error;
          }
        } else {
          throw error;
        }
      }
    };


    /*
    Section: Private
     */

    File.prototype.handleNativeChangeEvent = function(eventType, eventPath) {
      var error, handleReadError, handleResolve, 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 {
            handleResolve = (function(_this) {
              return function(newContents) {
                if (oldContents !== newContents) {
                  if (Grim.includeDeprecatedAPIs) {
                    _this.emit('contents-changed');
                  }
                  return _this.emitter.emit('did-change');
                }
              };
            })(this);
            return this.read(true).then(handleResolve, handleReadError);
          } 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);
