(function() {
  var BackwardsMultiLine, BackwardsSingleLine, ForwardsMultiLine, ForwardsSingleLine, MultiLineSearchCallbackArgument, Point, Range, SingleLineSearchCallbackArgument,
    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  Point = require('./point');

  Range = require('./range');

  SingleLineSearchCallbackArgument = (function() {
    SingleLineSearchCallbackArgument.prototype.lineTextOffset = 0;

    Object.defineProperty(SingleLineSearchCallbackArgument.prototype, 'range', {
      get: function() {
        return this.computedRange != null ? this.computedRange : this.computedRange = Range(Point(this.row, this.lineOffset + this.match.index), Point(this.row, this.lineOffset + this.match.index + this.matchText.length));
      },
      set: function(computedRange) {
        this.computedRange = computedRange;
      }
    });

    Object.defineProperty(SingleLineSearchCallbackArgument.prototype, 'lineText', {
      get: function() {
        return this.buffer.lineForRow(this.row);
      }
    });

    function SingleLineSearchCallbackArgument(buffer, row1, match1, lineOffset1) {
      this.buffer = buffer;
      this.row = row1;
      this.match = match1;
      this.lineOffset = lineOffset1;
      this.stop = bind(this.stop, this);
      this.replace = bind(this.replace, this);
      this.stopped = false;
      this.matchText = this.match[0];
    }

    SingleLineSearchCallbackArgument.prototype.replace = function(text) {
      this.replacementText = text;
      return this.buffer.setTextInRange(this.range, this.replacementText);
    };

    SingleLineSearchCallbackArgument.prototype.stop = function() {
      return this.stopped = true;
    };

    return SingleLineSearchCallbackArgument;

  })();

  ForwardsSingleLine = (function() {
    function ForwardsSingleLine(buffer, regex, range1) {
      this.buffer = buffer;
      this.regex = regex;
      this.range = range1;
    }

    ForwardsSingleLine.prototype.iterate = function(callback, global) {
      var argument, line, lineOffset, match, row;
      row = this.range.start.row;
      line = this.buffer.lineForRow(row);
      lineOffset = 0;
      this.regex.lastIndex = this.range.start.column;
      while (row < this.range.end.row) {
        if (match = this.regex.exec(line)) {
          argument = new SingleLineSearchCallbackArgument(this.buffer, row, match, lineOffset);
          callback(argument);
          if (argument.stopped || !global) {
            return;
          }
          if (argument.replacementText != null) {
            lineOffset += argument.replacementText.length - argument.matchText.length;
          }
          if (match[0].length === 0) {
            this.regex.lastIndex++;
          }
        } else {
          row++;
          lineOffset = 0;
          line = this.buffer.lineForRow(row);
          this.regex.lastIndex = 0;
        }
      }
      line = line.slice(0, this.range.end.column - lineOffset);
      while (match = this.regex.exec(line)) {
        if (line.length !== 0 && match.index === this.range.end.column) {
          break;
        }
        argument = new SingleLineSearchCallbackArgument(this.buffer, row, match, lineOffset);
        callback(argument);
        if (argument.stopped || !global) {
          return;
        }
        if (argument.replacementText != null) {
          lineOffset += argument.replacementText.length - argument.matchText.length;
        }
        if (match[0].length === 0) {
          this.regex.lastIndex++;
        }
      }
    };

    return ForwardsSingleLine;

  })();

  BackwardsSingleLine = (function() {
    function BackwardsSingleLine(buffer, regex, range1) {
      this.buffer = buffer;
      this.regex = regex;
      this.range = range1;
    }

    BackwardsSingleLine.prototype.iterate = function(callback, global) {
      var argument, bufferedMatches, endColumn, line, match, row;
      row = this.range.end.row;
      endColumn = this.range.end.column;
      line = this.buffer.lineForRow(row).slice(0, this.range.end.column);
      bufferedMatches = [];
      while (row > this.range.start.row) {
        if (match = this.regex.exec(line)) {
          if (row < this.range.end.row || match.index < endColumn || (match.index === endColumn && endColumn === 0)) {
            bufferedMatches.push(match);
          }
          if (match[0].length === 0) {
            this.regex.lastIndex++;
          }
        } else {
          while (match = bufferedMatches.pop()) {
            argument = new SingleLineSearchCallbackArgument(this.buffer, row, match, 0);
            callback(argument);
            if (argument.stopped || !global) {
              return;
            }
          }
          row--;
          line = this.buffer.lineForRow(row);
          this.regex.lastIndex = 0;
        }
      }
      this.regex.lastIndex = this.range.start.column;
      while (match = this.regex.exec(line)) {
        if (row === this.range.end.row && (match.index > endColumn || match.index === endColumn && endColumn > 0)) {
          break;
        }
        bufferedMatches.push(match);
        if (match[0].length === 0) {
          this.regex.lastIndex++;
        }
      }
      while (match = bufferedMatches.pop()) {
        argument = new SingleLineSearchCallbackArgument(this.buffer, row, match, 0);
        callback(argument);
        if (argument.stopped || !global) {
          return;
        }
      }
    };

    return BackwardsSingleLine;

  })();

  MultiLineSearchCallbackArgument = (function() {
    MultiLineSearchCallbackArgument.prototype.lineTextOffset = 0;

    Object.defineProperty(MultiLineSearchCallbackArgument.prototype, 'range', {
      get: function() {
        var endPosition, matchEndIndex, matchStartIndex, startPosition;
        if (this.computedRange != null) {
          return this.computedRange;
        }
        matchStartIndex = this.match.index;
        matchEndIndex = matchStartIndex + this.matchText.length;
        startPosition = this.buffer.positionForCharacterIndex(matchStartIndex + this.lengthDelta);
        endPosition = this.buffer.positionForCharacterIndex(matchEndIndex + this.lengthDelta);
        return this.computedRange = new Range(startPosition, endPosition);
      },
      set: function(range) {
        return this.computedRange = range;
      }
    });

    Object.defineProperty(MultiLineSearchCallbackArgument.prototype, 'lineText', {
      get: function() {
        return this.buffer.lineForRow(this.range.start.row);
      }
    });

    function MultiLineSearchCallbackArgument(buffer, match1, lengthDelta1) {
      this.buffer = buffer;
      this.match = match1;
      this.lengthDelta = lengthDelta1;
      this.stop = bind(this.stop, this);
      this.replace = bind(this.replace, this);
      this.stopped = false;
      this.replacementText = null;
      this.matchText = this.match[0];
    }

    MultiLineSearchCallbackArgument.prototype.replace = function(text) {
      this.replacementText = text;
      return this.buffer.setTextInRange(this.range, this.replacementText);
    };

    MultiLineSearchCallbackArgument.prototype.stop = function() {
      return this.stopped = true;
    };

    return MultiLineSearchCallbackArgument;

  })();

  ForwardsMultiLine = (function() {
    function ForwardsMultiLine(buffer, regex, range) {
      this.buffer = buffer;
      this.regex = regex;
      this.startIndex = this.buffer.characterIndexForPosition(range.start);
      this.endIndex = this.buffer.characterIndexForPosition(range.end);
      this.rangeEndColumn = range.end.column;
      this.text = this.buffer.getText();
      this.regex.lastIndex = this.startIndex;
    }

    ForwardsMultiLine.prototype.iterate = function(callback, global) {
      var argument, lengthDelta, match;
      lengthDelta = 0;
      while (match = this.next()) {
        argument = new MultiLineSearchCallbackArgument(this.buffer, match, lengthDelta);
        callback(argument);
        if (argument.replacementText != null) {
          lengthDelta += argument.replacementText.length - argument.matchText.length;
        }
        if (!(global && !argument.stopped)) {
          break;
        }
      }
    };

    ForwardsMultiLine.prototype.next = function() {
      var match, matchEndIndex, matchLength, matchStartIndex, submatch;
      if (match = this.regex.exec(this.text)) {
        matchLength = match[0].length;
        matchStartIndex = match.index;
        matchEndIndex = matchStartIndex + matchLength;
        if (matchEndIndex > this.endIndex) {
          match = null;
          if (matchStartIndex < this.endIndex || matchStartIndex === this.endIndex && this.rangeEndColumn === 0) {
            this.regex.lastIndex = 0;
            if (submatch = this.regex.exec(this.text.slice(matchStartIndex, this.endIndex))) {
              submatch.index = matchStartIndex;
              match = submatch;
            }
          }
          this.regex.lastIndex = 2e308;
        } else {
          if (matchStartIndex === this.endIndex && this.rangeEndColumn > 0) {
            return null;
          }
          if (matchLength === 0) {
            matchEndIndex++;
          }
          this.regex.lastIndex = matchEndIndex;
        }
        return match;
      }
    };

    return ForwardsMultiLine;

  })();

  BackwardsMultiLine = (function() {
    function BackwardsMultiLine(buffer, regex, range, chunkSize) {
      this.buffer = buffer;
      this.regex = regex;
      this.chunkSize = chunkSize;
      this.text = this.buffer.getText();
      this.startIndex = this.buffer.characterIndexForPosition(range.start);
      this.endIndex = this.buffer.characterIndexForPosition(range.end);
      this.rangeEndColumn = range.end.column;
      this.chunkStartIndex = this.chunkEndIndex = this.endIndex;
      this.bufferedMatches = [];
      this.lastMatchIndex = 2e308;
      this.didInitialScan = false;
    }

    BackwardsMultiLine.prototype.iterate = function(callback, global) {
      var argument, match;
      while (match = this.next()) {
        argument = new MultiLineSearchCallbackArgument(this.buffer, match, 0);
        callback(argument);
        if (!(global && !argument.stopped)) {
          break;
        }
      }
    };

    BackwardsMultiLine.prototype.next = function() {
      while (this.bufferedMatches.length === 0 && (!this.didInitialScan || this.chunkStartIndex > this.startIndex)) {
        this.scanNextChunk();
      }
      return this.bufferedMatches.pop();
    };

    BackwardsMultiLine.prototype.scanNextChunk = function() {
      var firstResultIndex, match, matchEndIndex, matchLength, matchStartIndex, submatch;
      this.chunkEndIndex = Math.min(this.chunkEndIndex, this.lastMatchIndex);
      this.chunkStartIndex = Math.max(this.startIndex, this.chunkStartIndex - this.chunkSize);
      this.didInitialScan = true;
      firstResultIndex = null;
      this.regex.lastIndex = this.chunkStartIndex;
      while (match = this.regex.exec(this.text)) {
        matchLength = match[0].length;
        matchStartIndex = match.index;
        matchEndIndex = matchStartIndex + matchLength;
        if (matchStartIndex === this.chunkStartIndex && this.chunkStartIndex > this.startIndex) {
          break;
        }
        if (matchStartIndex > this.chunkEndIndex || (matchStartIndex === this.endIndex && this.rangeEndColumn > 0)) {
          break;
        }
        if (matchEndIndex > this.chunkEndIndex) {
          this.regex.lastIndex = 0;
          if (submatch = this.regex.exec(this.text.slice(matchStartIndex, this.chunkEndIndex))) {
            submatch.index = matchStartIndex;
            if (firstResultIndex == null) {
              firstResultIndex = matchStartIndex;
            }
            this.bufferedMatches.push(submatch);
          }
          break;
        } else {
          if (firstResultIndex == null) {
            firstResultIndex = matchStartIndex;
          }
          this.bufferedMatches.push(match);
          if (matchLength === 0) {
            matchEndIndex++;
          }
          this.regex.lastIndex = matchEndIndex;
        }
      }
      if (firstResultIndex) {
        this.lastMatchIndex = firstResultIndex;
      }
    };

    return BackwardsMultiLine;

  })();

  module.exports = {
    ForwardsMultiLine: ForwardsMultiLine,
    BackwardsMultiLine: BackwardsMultiLine,
    ForwardsSingleLine: ForwardsSingleLine,
    BackwardsSingleLine: BackwardsSingleLine
  };

}).call(this);
