'use strict';

var escapeRegExp = require('escape-string-regexp');
var objectAssign = require('object-assign');
var Transform = require('readable-stream/transform');

module.exports = function ReplaceStream(search, replace, options) {
  var tail = '';
  var totalMatches = 0;
  var isRegex = search instanceof RegExp;

  options = objectAssign({
    limit: Infinity,
    encoding: 'utf8',
    maxMatchLen: 100
  }, options);

  var replaceFn = replace;

  replaceFn = createReplaceFn(replace, isRegex);

  var match;
  if (isRegex) {
    match = matchFromRegex(search, options)
  } else {
    match = matchFromString(search, options);
    options.maxMatchLen = search.length;
  }

  function transform(buf, enc, cb) {
    var matches;
    var lastPos = 0;
    var runningMatch = '';
    var matchCount = 0;
    var rewritten = '';
    var haystack = tail + buf.toString(options.encoding);
    tail = '';

    while (totalMatches < options.limit &&
          (matches = match.exec(haystack)) !== null) {

      matchCount++;
      var before = haystack.slice(lastPos, matches.index);
      var regexMatch = matches;
      lastPos = matches.index + regexMatch[0].length;

      if (lastPos > haystack.length && regexMatch[0].length < options.maxMatchLen) {
        tail = regexMatch[0]
      } else {
        var dataToAppend = getDataToAppend(before,regexMatch);
        rewritten += dataToAppend;
      }
    }

    if (tail.length < 1)
      tail = haystack.slice(lastPos).length > options.maxMatchLen ? haystack.slice(lastPos).slice(0 - options.maxMatchLen) : haystack.slice(lastPos)

    var dataToQueue = getDataToQueue(matchCount,haystack,rewritten,lastPos);
    cb(null, dataToQueue);
  }

  function getDataToAppend(before, match) {
    var dataToAppend = before;

    totalMatches++;

    dataToAppend += isRegex ? replaceFn.apply(this, match.concat([match.index, match.input])) : replaceFn(match[0]);

    return dataToAppend;
  }

  function getDataToQueue(matchCount, haystack, rewritten, lastPos) {
    if (matchCount > 0) {
      if (haystack.length > tail.length) {
        return rewritten + haystack.slice(lastPos, haystack.length - tail.length);
      }

      return rewritten;
    }

    return haystack.slice(0, haystack.length - tail.length);
  }

  function flush(cb) {
    if (tail) {
      this.push(tail);
    }
    cb();
  }

  return new Transform({
    transform: transform,
    flush: flush
  });
};

function createReplaceFn(replace, isRegEx) {
  var regexReplaceFunction = function () {
    var newReplace = replace;
    // ability to us $1 with captures
    // Start at 1 and end at length - 2 to avoid the match parameter and offset
    // And string parameters
    var paramLength = arguments.length - 2;
    for (var i = 1; i < paramLength; i++) {
      newReplace = newReplace.replace(new RegExp('\\$' + i, 'g'), arguments[i] || '')
    }
    return newReplace;
  };

  if (isRegEx && !(replace instanceof Function)) {
    return regexReplaceFunction;
  }

  if (!(replace instanceof Function)) {
    return function stringReplaceFunction() {
      return replace;
    };
  }

  return replace;
}

function matchFromRegex(regex, options) {
  if (options.regExpOptions) {
    regex = new RegExp(regex.source, options.regExpOptions)
  }

  // If there is no global flag then there can only be one match
  if (!regex.global) {
    options.limit = 1;
  }
  return regex;
}

function matchFromString(s, options) {
  if (options.regExpOptions) {
    return new RegExp(escapeRegExp(s), options.regExpOptions);
  }

  return new RegExp(escapeRegExp(s), options.ignoreCase === false ? 'gm' : 'gmi');
}