text: module.exports = preferredCharsets;
  preferredCharsets.preferredCharsets = preferredCharsets;
  
  function parseAcceptCharset(accept) {
    var accepts = accept.split(',');
  
    for (var i = 0, j = 0; i < accepts.length; i++) {
      var charset = parseCharset(accepts[i].trim(), i);
  
      if (charset) {
        accepts[j++] = charset;
      }
    }
  
    // trim accepts
    accepts.length = j;
  
    return accepts;
  }
  
  function parseCharset(s, i) {
    var match = s.match(/^\s*(\S+?)\s*(?:;(.*))?$/);
    if (!match) return null;
  
    var charset = match[1];
    var q = 1;
    if (match[2]) {
      var params = match[2].split(';')
      for (var i = 0; i < params.length; i ++) {
        var p = params[i].trim().split('=');
        if (p[0] === 'q') {
          q = parseFloat(p[1]);
          break;
        }
      }
    }
  
    return {
      charset: charset,
      q: q,
      i: i
    };
  }
  
  function getCharsetPriority(charset, accepted, index) {
    var priority = {o: -1, q: 0, s: 0};
  
    for (var i = 0; i < accepted.length; i++) {
      var spec = specify(charset, accepted[i], index);
  
      if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
        priority = spec;
      }
    }
  
    return priority;
  }
  
  function specify(charset, spec, index) {
    var s = 0;
    if(spec.charset.toLowerCase() === charset.toLowerCase()){
      s |= 1;
    } else if (spec.charset !== '*' ) {
      return null
    }
  
    return {
      i: index,
      o: spec.i,
      q: spec.q,
      s: s
    }
  }
  
  function preferredCharsets(accept, provided) {
    // RFC 2616 sec 14.2: no header = *
    var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || '');
  
    if (!provided) {
      // sorted list of all charsets
      return accepts.filter(isQuality).sort(compareSpecs).map(function getCharset(spec) {
        return spec.charset;
      });
    }
  
    var priorities = provided.map(function getPriority(type, index) {
      return getCharsetPriority(type, accepts, index);
    });
  
    // sorted list of accepted charsets
    return priorities.filter(isQuality).sort(compareSpecs).map(function getCharset(priority) {
      return provided[priorities.indexOf(priority)];
    });
  }
  
  function compareSpecs(a, b) {
    return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
  }
  
  function isQuality(spec) {
    return spec.q > 0;
  }
  