'use strict';
var assert = require('assert');
var querystring = require('querystring');

var assign = require('object-assign');

var DEFAULT_QUERY = {
  link: '',
  sort: '',
  embed: '',
  fields: '',
  pagination: {
    skip: 0,
    page: 1,
    limit: 0,
  },
};

var BLACK_LISTED_QUERY_PARAMETERS = [
  'skip',
  'link',
  'page',
  'sort',
  'embed',
  'limit',
  'fields',
  'pagination',
];

function parseQuery(qs) {
  var rawQuery;
  var parsedQuery;
  var transformations = [
    parseSortingFrom,
    parseFieldsFrom,
    parsePaginationFrom,
    parseEmbeddingFrom,
    parseLinkFrom,
    parseFilterFrom,
  ];

  assert(qs, '`qs` parameter is missing.');
  assert(isEitherStringOrObject(qs), '`qs` parameter must be either a string or an object.');

  qs = typeof qs === 'string' ? querystring.parse(qs) : qs;

  rawQuery = assign({}, DEFAULT_QUERY, qs);
  rawQuery.pagination = assign({}, DEFAULT_QUERY.pagination, rawQuery.pagination);

  parsedQuery = transformations
  .map(function (transform) { return transform(rawQuery); })
  .reduce(function (query, field) { return assign({}, query, field); }, {});

  return parsedQuery;
}

function parseSortingFrom(q) {
  var sortFields = q.sort.split(',');
  var sort = sortFields.map(
    function (sf) {
      var sortFieldName = getFieldName(sf);
      var sortFieldDirection = getSortDirection(sf);
      var sorting = {};
      sorting[sortFieldName] = sortFieldDirection;

      return sorting;
    }
  )
  .reduce(function (query, f) { return assign({}, query, f); }, {});

  return { sort: sort };
}

function getFieldName(sf) {
  return sf[0] === '-' ? sf.slice(1) : sf;
}

function getSortDirection(sf) {
  return sf[0] === '-' ? 'descending' : 'ascending';
}

function parseFieldsFrom(q) {
  var fields = q.fields.split(',');

  return { fields: fields };
}

function parsePaginationFrom(q) {
  var parsedSkip = parseInt(q.skip, 10);
  var parsedPage = parseInt(q.page, 10);
  var parsedLimit = parseInt(q.limit, 10);

  var skip = isNaN(parsedSkip) ? DEFAULT_QUERY.pagination.skip : parsedSkip;
  var page = isNaN(parsedPage) ? DEFAULT_QUERY.pagination.page : parsedPage;
  var limit = isNaN(parsedLimit) ? DEFAULT_QUERY.pagination.limit : parsedLimit;

  return { pagination: { skip: skip, page: page, limit: limit } };
}

function parseEmbeddingFrom(q) {
  var embed = q.embed.split(',');

  return {
    embed: embed,
  };
}

function parseLinkFrom(q) {
  var link = q.link.split(',');

  return {
    link: link,
  };
}

function parseFilterFrom(q) {
  return except(BLACK_LISTED_QUERY_PARAMETERS, q);
}

function except(blackListedKeys, q) {
  var keys = Object.keys(q);
  var keysWithoutBlacklisted = keys.filter(not(isInList(blackListedKeys)));
  var queryWithoutBlacklisted = keysWithoutBlacklisted.reduce(function (result, k) {
    var query = {};
    query[k] = q[k];

    return assign({}, result, query);
  }, {});

  return { filter: queryWithoutBlacklisted };
}

function not(fn) {
  return function () {
    return !fn.apply(null, arguments);
  };
}

function isInList(list) {
  return function (item) {
    return !!list.filter(function (li) {
      return li === item;
    })[0];
  };
}

function isEitherStringOrObject(qs) {
  return typeof qs === 'string' || typeof qs === 'object';
}

module.exports = parseQuery;

@@ -510,26 +510,43 @@
    parse
-F
+QueryParamAsArray('f
 ields
-From
+')
 ,%0A    pa
@@ -577,40 +577,70 @@
 arse
-EmbeddingFrom,%0A    parseLinkFrom
+QueryParamAsArray('embed'),%0A    parseQueryParamAsArray('link')
 ,%0A  
@@ -1789,47 +1789,113 @@
 arse
-FieldsFrom
+QueryParamAsArray(p) %7B%0A  return function parseQueryParameter
 (q) %7B%0A
+  
   var 
-fields = q.fields
+query = %7B%7D;%0A    query%5Bp%5D = q%5Bp%5D
 .spl
@@ -1896,32 +1896,34 @@
 p%5D.split(',');%0A%0A
+  
   return %7B field
@@ -1919,24 +1919,16 @@
 urn 
-%7B fields: fields
+query;%0A 
  %7D;%0A
@@ -2400,209 +2400,8 @@
 %0A%7D%0A%0A
-function parseEmbeddingFrom(q) %7B%0A  var embed = q.embed.split(',');%0A%0A  return %7B%0A    embed: embed,%0A  %7D;%0A%7D%0A%0Afunction parseLinkFrom(q) %7B%0A  var link = q.link.split(',');%0A%0A  return %7B%0A    link: link,%0A  %7D;%0A%7D%0A%0A
 func

Refactor fields, embed and link parsers.