const extend = require('extend');

const uniq = require('./lib/uniq');
const parseOptions = require('./lib/parse_options');
const extract = require('./lib/extract');

function Request(opts) {
  extend(this, parseOptions(opts));
}

Request.prototype.filters = function (request) {
  return extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    // named params in the route are automatically included with the
    // user-supplied list of valid params. given a route /resource/:id
    // the param 'id' will be considered valid even if it isn't listed
    // in the paramsForfilters property.
    find: this.allowedFilters.concat(Object.keys(request.params)),
    normalizer: this.paramNormalizer
  });
};

Request.prototype.relations = function (request) {
  var result = extract({
    context: request,
    contextKeysToSearch: this.requestKeysToSearch,
    find: this.relationKey,
    normalizer: this.paramNormalizer
  }) || [];
  if (result && !Array.isArray(result)) {
    result = [result];
  }
  return uniq(result);
};

Request.prototype.responder = function (response, code, data) {
  // cheap heuristics to detect the server type
  var isExpress = !!response.send;
  var isHapi = !!response.request;
  if (!isExpress && !isHapi) {
    throw new Error('Unsupported server type!');
  }

  var prettyPrint = JSON.stringify(data, null, 2);
  var contentType = 'application/json';

  // both of these should never happen, right?
  if (isExpress) {
    response.set('content-type', contentType).status(code).send(prettyPrint);
  }
  if (isHapi) {
    response(prettyPrint).type(contentType).code(code);
  }
};

Request.prototype.create = function (opts) {
  if (!opts) {
    opts = {};
  }
  var method = opts.method || 'create';
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.create(method, request.body, function (err, data) {
      var code = 201;
      if (err) {
        code = 422;
        data = {
          errors: {
            title: 'Unprocessable Entity',
            detail: err.message
          }
        };
      }
      responder(response, code, data);
    });
  };
};

Request.prototype.read = function (opts) {
  if (!opts) {
    opts = {};
  }
  var format = this.format.bind(this);
  var responder = this.responder;
  var getFilters = this.filters.bind(this);
  var getRelations = this.relations.bind(this);
  var source = this.source;
  var include = opts.include||[];
  var passMode = !!opts.pass;
  var rawMode = !!opts.raw;
  return function (request, response, next) {
    var filters = getFilters(request);
    var relations = getRelations(request).concat(include);
    source.read(filters, relations, opts, function (err, data) {
      var code = 200;
      if (err) {
        code = 400;
        data = {
          errors: {
            title: 'Bad Request',
            detail: err.message
          }
        };
      }
      if (!rawMode) {
        data = format(data, opts);
      }
      if (passMode) {
        response.data = data;
        response.code = code;
        next();
      } else {
        responder(response, code, data);
      }
    });
  };
};


Request.prototype.update = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.update(model, request.body, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.destroy = function (opts) {
  if (!opts) {
    opts = {};
  }
  var responder = this.responder;
  var source = this.source;

  return function (request, response) {
    source.byId(request.param('id'), function (err, model) {
      if (!model) {
        responder(response, 500, {
          title: 'Internal Server Error',
          detail: 'No resource by that id found.'
        });
      } else {
        source.destroy(model, function (err, data) {
          var code = 200;
          if (err) {
            code = 422;
            data = {
              title: 'Unprocessable Entity',
              detail: err.message
            };
          }
          responder(response, code, data);
        });
      }
    });
  };
};

Request.prototype.format = function (data, opts) {
  if (!opts) {
    opts = {};
  }
  var singleResource = !!opts.one;
  var primaryResourceName = this.source.resourceName();
  var primaryResource = data[primaryResourceName];
  var hasLinks = (Object.keys(data).length > 1);
  var output = {};
  if (hasLinks) {
    output.linked = data;
  }
  if (singleResource) {
    output[primaryResourceName] = primaryResource[0];
  } else {
    output[primaryResourceName] = primaryResource;
  }

  delete data[primaryResourceName];
  return output;
};


module.exports = Request;

@@ -2264,47 +2264,8 @@
   %7D%0A
-  var format = this.format.bind(this);%0A
   va
@@ -2290,24 +2290,24 @@
 .responder;%0A
+
   var getFil
@@ -2482,36 +2482,8 @@
 ss;%0A
-  var rawMode = !!opts.raw;%0A
   re
@@ -2884,73 +2884,8 @@
   %7D%0A
-      if (!rawMode) %7B%0A        data = format(data, opts);%0A      %7D%0A
     
@@ -3055,21 +3055,20 @@
 );%0A  %7D;%0A
+
 %7D;%0A%0A
-%0A
 Request.
@@ -4573,554 +4573,8 @@
 %7D;%0A%0A
-Request.prototype.format = function (data, opts) %7B%0A  if (!opts) %7B%0A    opts = %7B%7D;%0A  %7D%0A  var singleResource = !!opts.one;%0A  var primaryResourceName = this.source.resourceName();%0A  var primaryResource = data%5BprimaryResourceName%5D;%0A  var hasLinks = (Object.keys(data).length %3E 1);%0A  var output = %7B%7D;%0A  if (hasLinks) %7B%0A    output.linked = data;%0A  %7D%0A  if (singleResource) %7B%0A    output%5BprimaryResourceName%5D = primaryResource%5B0%5D;%0A  %7D else %7B%0A    output%5BprimaryResourceName%5D = primaryResource;%0A  %7D%0A%0A  delete data%5BprimaryResourceName%5D;%0A  return output;%0A%7D;%0A%0A%0A
 modu

remove formatting entirely