// Generated by CoffeeScript 2.4.1
var Tail, environment, events, fs,
  boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };

events = require("events");

fs = require('fs');

environment = process.env['NODE_ENV'] || 'development';

Tail = class Tail extends events.EventEmitter {
  readBlock() {
    var block, stream;
    boundMethodCheck(this, Tail);
    if (this.queue.length >= 1) {
      block = this.queue[0];
      if (block.end > block.start) {
        stream = fs.createReadStream(this.filename, {
          start: block.start,
          end: block.end - 1,
          encoding: this.encoding
        });
        stream.on('error', (error) => {
          if (this.logger) {
            this.logger.error(`Tail error: ${error}`);
          }
          return this.emit('error', error);
        });
        stream.on('end', () => {
          var x;
          x = this.queue.shift();
          if (this.queue.length > 0) {
            this.internalDispatcher.emit("next");
          }
          if (this.flushAtEOF && this.buffer.length > 0) {
            this.emit("line", this.buffer);
            return this.buffer = '';
          }
        });
        return stream.on('data', (data) => {
          var chunk, i, len, parts, results;
          if (this.separator === null) {
            return this.emit("line", data);
          } else {
            this.buffer += data;
            parts = this.buffer.split(this.separator);
            this.buffer = parts.pop();
            results = [];
            for (i = 0, len = parts.length; i < len; i++) {
              chunk = parts[i];
              results.push(this.emit("line", chunk));
            }
            return results;
          }
        });
      }
    }
  }

  constructor(filename, options = {}) {
    var err, fromBeginning;
    super(filename, options);
    this.readBlock = this.readBlock.bind(this);
    this.change = this.change.bind(this);
    this.filename = filename;
    ({separator: this.separator = /[\r]{0,1}\n/, fsWatchOptions: this.fsWatchOptions = {}, follow: this.follow = true, logger: this.logger, useWatchFile: this.useWatchFile = false, flushAtEOF: this.flushAtEOF = false, encoding: this.encoding = "utf-8", fromBeginning = false} = options);
    if (this.logger) {
      this.logger.info("Tail starting...");
      this.logger.info(`filename: ${this.filename}`);
      this.logger.info(`encoding: ${this.encoding}`);
      try {
        fs.accessSync(this.filename, fs.constants.F_OK);
      } catch (error1) {
        err = error1;
        if (err.code === 'ENOENT') {
          throw err;
        }
      }
    }
    this.buffer = '';
    this.internalDispatcher = new events.EventEmitter();
    this.queue = [];
    this.isWatching = false;
    this.internalDispatcher.on('next', () => {
      return this.readBlock();
    });
    this.watch(fromBeginning);
  }

  change(filename) {
    var err, stats;
    boundMethodCheck(this, Tail);
    try {
      stats = fs.statSync(filename);
    } catch (error1) {
      err = error1;
      if (this.logger) {
        this.logger.error(`change event for ${filename} failed: ${err}`);
      }
      this.emit("error", `change event for ${filename} failed: ${err}`);
      return;
    }
    if (stats.size < this.pos) { //scenario where texts is not appended but it's actually a w+
      this.pos = stats.size;
    }
    if (stats.size > this.pos) {
      this.queue.push({
        start: this.pos,
        end: stats.size
      });
      this.pos = stats.size;
      if (this.queue.length === 1) {
        return this.internalDispatcher.emit("next");
      }
    }
  }

  watch(fromBeginning) {
    var err, stats;
    if (this.isWatching) {
      return;
    }
    if (this.logger) {
      this.logger.info(`filesystem.watch present? ${fs.watch !== void 0}`);
      this.logger.info(`useWatchFile: ${this.useWatchFile}`);
      this.logger.info(`fromBeginning: ${fromBeginning}`);
    }
    this.isWatching = true;
    try {
      stats = fs.statSync(this.filename);
    } catch (error1) {
      err = error1;
      if (this.logger) {
        this.logger.error(`watch for ${this.filename} failed: ${err}`);
      }
      this.emit("error", `watch for ${this.filename} failed: ${err}`);
      return;
    }
    this.pos = fromBeginning ? 0 : stats.size;
    if (this.pos === 0) {
      this.change(this.filename);
    }
    if (!this.useWatchFile && fs.watch) {
      if (this.logger) {
        this.logger.info("watch strategy: watch");
      }
      return this.watcher = fs.watch(this.filename, this.fsWatchOptions, (e, filename) => {
        return this.watchEvent(e, filename);
      });
    } else {
      if (this.logger) {
        this.logger.info("watch strategy: watchFile");
      }
      return fs.watchFile(this.filename, this.fsWatchOptions, (curr, prev) => {
        return this.watchFileEvent(curr, prev);
      });
    }
  }

  rename(filename) {
    //MacOS sometimes throws a rename event for no reason.
    //Different platforms might behave differently.
    //see https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener
    //filename might not be present.
    //https://nodejs.org/api/fs.html#fs_filename_argument
    //Better solution would be check inode but it will require a timeout and
    // a sync file read.
    if (filename === void 0 || filename !== this.filename) {
      this.unwatch();
      if (this.follow) {
        return setTimeout((() => {
          return this.watch();
        }), 1000);
      } else {
        if (this.logger) {
          this.logger.error(`'rename' event for ${this.filename}. File not available.`);
        }
        return this.emit("error", `'rename' event for ${this.filename}. File not available.`);
      }
    } else {

    }
  }

  // @logger.info("rename event but same filename")
  watchEvent(e, evtFilename) {
    if (e === 'change') {
      return this.change(this.filename);
    } else if (e === 'rename') {
      return this.rename(evtFilename);
    }
  }

  watchFileEvent(curr, prev) {
    if (curr.size > prev.size) {
      this.pos = curr.size; // Update @pos so that a consumer can determine if entire file has been handled
      this.queue.push({
        start: prev.size,
        end: curr.size
      });
      if (this.queue.length === 1) {
        return this.internalDispatcher.emit("next");
      }
    }
  }

  unwatch() {
    if (this.watcher) {
      this.watcher.close();
    } else {
      fs.unwatchFile(this.filename);
    }
    this.isWatching = false;
    this.queue = [];
    if (this.logger) {
      return this.logger.info("Unwatch ", this.filename);
    }
  }

};

exports.Tail = Tail;
