text: 'use strict';
  
  var paethPredictor = require('./paeth-predictor');
  
  function filterNone(pxData, pxPos, byteWidth, rawData, rawPos) {
    pxData.copy(rawData, rawPos, pxPos, pxPos + byteWidth);
  }
  
  function filterSumNone(pxData, pxPos, byteWidth) {
  
    var sum = 0;
    var length = pxPos + byteWidth;
  
    for (var i = pxPos; i < length; i++) {
      sum += Math.abs(pxData[i]);
    }
    return sum;
  }
  
  function filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
  
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var val = pxData[pxPos + x] - left;
  
      rawData[rawPos + x] = val;
    }
  }
  
  function filterSumSub(pxData, pxPos, byteWidth, bpp) {
  
    var sum = 0;
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var val = pxData[pxPos + x] - left;
  
      sum += Math.abs(val);
    }
  
    return sum;
  }
  
  function filterUp(pxData, pxPos, byteWidth, rawData, rawPos) {
  
    for (var x = 0; x < byteWidth; x++) {
  
      var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
      var val = pxData[pxPos + x] - up;
  
      rawData[rawPos + x] = val;
    }
  }
  
  function filterSumUp(pxData, pxPos, byteWidth) {
  
    var sum = 0;
    var length = pxPos + byteWidth;
    for (var x = pxPos; x < length; x++) {
  
      var up = pxPos > 0 ? pxData[x - byteWidth] : 0;
      var val = pxData[x] - up;
  
      sum += Math.abs(val);
    }
  
    return sum;
  }
  
  function filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
  
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
      var val = pxData[pxPos + x] - ((left + up) >> 1);
  
      rawData[rawPos + x] = val;
    }
  }
  
  function filterSumAvg(pxData, pxPos, byteWidth, bpp) {
  
    var sum = 0;
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
      var val = pxData[pxPos + x] - ((left + up) >> 1);
  
      sum += Math.abs(val);
    }
  
    return sum;
  }
  
  function filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp) {
  
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
      var upleft = pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
      var val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
  
      rawData[rawPos + x] = val;
    }
  }
  
  function filterSumPaeth(pxData, pxPos, byteWidth, bpp) {
    var sum = 0;
    for (var x = 0; x < byteWidth; x++) {
  
      var left = x >= bpp ? pxData[pxPos + x - bpp] : 0;
      var up = pxPos > 0 ? pxData[pxPos + x - byteWidth] : 0;
      var upleft = pxPos > 0 && x >= bpp ? pxData[pxPos + x - (byteWidth + bpp)] : 0;
      var val = pxData[pxPos + x] - paethPredictor(left, up, upleft);
  
      sum += Math.abs(val);
    }
  
    return sum;
  }
  
  var filters = {
    0: filterNone,
    1: filterSub,
    2: filterUp,
    3: filterAvg,
    4: filterPaeth
  };
  
  var filterSums = {
    0: filterSumNone,
    1: filterSumSub,
    2: filterSumUp,
    3: filterSumAvg,
    4: filterSumPaeth
  };
  
  module.exports = function(pxData, width, height, options, bpp) {
  
    var filterTypes;
    if (!('filterType' in options) || options.filterType === -1) {
      filterTypes = [0, 1, 2, 3, 4];
    }
    else if (typeof options.filterType === 'number') {
      filterTypes = [options.filterType];
    }
    else {
      throw new Error('unrecognised filter types');
    }
  
    if (options.bitDepth === 16) bpp *= 2;
    var byteWidth = width * bpp;
    var rawPos = 0;
    var pxPos = 0;
    var rawData = new Buffer((byteWidth + 1) * height);
  
    var sel = filterTypes[0];
  
    for (var y = 0; y < height; y++) {
  
      if (filterTypes.length > 1) {
        // find best filter for this line (with lowest sum of values)
        var min = Infinity;
  
        for (var i = 0; i < filterTypes.length; i++) {
          var sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp);
          if (sum < min) {
            sel = filterTypes[i];
            min = sum;
          }
        }
      }
  
      rawData[rawPos] = sel;
      rawPos++;
      filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp);
      rawPos += byteWidth;
      pxPos += byteWidth;
    }
    return rawData;
  };
  