module.exports = function inject(opt) {
  // options
  var opt = opt || {};
  var ignore = opt.ignore || opt.excludeList || ['.js', '.css', '.svg', '.ico', '.woff', '.png', '.jpg', '.jpeg'];
  var html = opt.html || _html;
  var rules = opt.rules || [{
    match: /<\/body>/,
    fn: prepend
  }, {
    match: /<\/html>/,
    fn: prepend
  }, {
    match: /<\!DOCTYPE.+>/,
    fn: append
  }];
  var snippetBuilder = function(snippet) {
    if (snippet) {
      if (snippet instanceof Array) {
        return snippet.join("");
      } else {
        return snippet;
      }
    } else {
      return "";
    }
  };

  var snippet = snippetBuilder(opt.snippet);

  var runAll = opt.runAll || false;

  // helper functions
  var regex = (function() {
    var matches = rules.map(function(item) {
      return item.match.source;
    }).join('|');

    return new RegExp(matches);
  })();

  function prepend(w, s) {
    return s + w;
  }

  function append(w, s) {
    return w + s;
  }

  function _html(str) {
    if (!str) return false;
    return /<[:_-\w\s\!\/\=\"\']+>/i.test(str);
  }

  function exists(body) {
    if (!body) return false;
    return regex.test(body);
  }

  function snip(body) {
    if (!body) return false;
    return (~body.lastIndexOf("/livereload.js"));
  }

  function snap(body) {
    var _body = body;
    rules.some(function(rule) {
      if (rule.match.test(_body)) {
        _body = _body.replace(rule.match, function(w) {
          return rule.fn(w, rule.snippet|| snippet);
        });
        if (runAll === false) {
          return true;
        } else {
          return false;
        }
      }
      return false;
    });
    return _body;
  }

  function accept(req) {
    var ha = req.headers["accept"];
    if (!ha) return false;
    return (~ha.indexOf("html"));
  }

  function leave(req) {
    var url = req.url;
    var ignored = false;
    if (!url) return true;
    ignore.forEach(function(item) {
      if (~url.indexOf(item)) {
        ignored = true;
      }
    });
    return ignored;
  }

  // middleware
  return function inject(req, res, next) {
    if (res.inject) return next();

    var writeHead = res.writeHead;
    var write = res.write;
    var end = res.end;

    if (!accept(req) || leave(req)) {
      return next();
    }

    function restore() {
      res.writeHead = writeHead;
      res.write = write;
      res.end = end;
    }

    res.push = function(chunk) {
      res.data = (res.data || '') + chunk;
    };

    res.inject = res.write = function(string, encoding) {
      if (string !== undefined) {
        var body = string instanceof Buffer ? string.toString(encoding) : string;
        if (exists(body) && !snip(res.data)) {
          res.push(snap(body));
          return true;
        } else if (html(body) || html(res.data)) {
          res.push(body);
          return true;
        } else {
          restore();
          return write.call(res, string, encoding);
        }
      }
      return true;
    };

    res.writeHead = function() {};

    res.end = function(string, encoding) {
      restore();
      var result = res.inject(string, encoding);
      if (!result) return end.call(res, string, encoding);
      if (res.data !== undefined && !res._header) res.setHeader('content-length', Buffer.byteLength(res.data, encoding));
      res.end(res.data, encoding);
    };
    next();
  };

}
