
// Node
import Stream from 'stream'
// 3rd
import Immutable from 'immutable'
import R from 'ramda'
import mime from 'mime-types'

const defaults = {
  status: 200,
  headers: new Immutable.Map(),
  body: '',
  state: new Immutable.Map()
}

class Response extends Immutable.Record(defaults) {

  // CONSTRUCTORS

  static make (status, headers, body) {
    return new Response(status, headers, body)
  }

  static notFound () {
    return new Response(404, {}, 'Not Found')
  }

  static notModified () {
    return new Response(304, { 'content-length': 0 })
  }

  static ok (body) {
    return new Response(200, {}, body)
  }

  static json (obj) {
    return new Response(200).json(obj)
  }

  static redirect (url, status) {
    status = status || 302
    return new Response(status, { 'location': url })
  }

  // Request, Maybe String -> Response
  static redirectBack (request, altUrl) {
    const url = request.getHeader('referrer') || altUrl || '/'
    return new Response(302, { 'location': url })
  }

  // Map the state of a klobb Response object onto the
  // underlying Node request object.
  //
  // TODO: This is just minimally stubbed out
  //
  // (Response, NodeResponse) -> void
  static send (response, nres) {
    response = response.finalize() // Should probably do this in outer middleware

    const status = response.status
    const headers = response.headers.toJS()
    const body = response.body

    nres.writeHead(status, headers)

    if (typeof body === 'string') return nres.end(body)
    if (Buffer.isBuffer(body)) return nres.end(body)
    if (body instanceof Stream) return body.pipe(nres)
    nres.end(body)
  }

  constructor (status, headers, body) {
    // Create an object that doesn't have undefined keys so we can lean on
    // our Immutable.Record defaults
    const opts = R.reject(R.isNil, { status, headers, body })
    // Handle plain objects
    if (opts.headers) opts.headers = Immutable.Map(opts.headers)

    super(opts)
  }

  // INSTANCE METHODS

  // Calculates final headers to be sent based on the state of the request
  //
  // Returns new request that's ready to be used with Response.send.
  finalize () {
    // Don't use `this` past this point. We're accumulating
    // the final response representation.
    let finalResponse = this

    // Determine content-length
    let length
    if (typeof finalResponse.body === 'string') {
      length = Buffer.byteLength(finalResponse.body)
    } else if (Buffer.isBuffer(finalResponse.body)) {
      length = Buffer.byteLength(finalResponse.body)
    } else {
      length = finalResponse.getHeader('content-length')
    }

    // Determine full content-type
    // Ex: mime.contentType('text/plain') -> 'text/plain; chartset=utf-8'
    let type
    if (typeof finalResponse.getHeader('content-type') === 'string') {
      // mime.contentType returns false if it can't discern,
      // so turn it into undefined
      type = mime.contentType(finalResponse.getHeader('content-type')) || undefined
    }

    return finalResponse
      .setHeader('content-length', length)
      .setHeader('content-type', type)
  }

  // Does not set the header if val is undefined/null
  //
  // (String, any) -> Response
  setHeader (key, val) {
    if (val === undefined || val === null) return this
    return this.setIn(['headers', key.toLowerCase()], val)
  }

  // String -> Maybe String
  getHeader (key) {
    return this.getIn(['headers', key.toLowerCase()])
  }

  setStatus (status) {
    return this.set('status', status)
  }

  // Use this function to update the body so that it can maintain the optional
  // fs.Stats object, which can be used for etag caching.
  //
  // (Stream, fs.StatsObject) -> Response
  setBody (body, stats) {
    if (stats) body._stats = stats // lame
    return this.set('body', body)
  }

  json (obj) {
    const body = JSON.stringify(obj, null, '  ')
    return this.setBody(body)
      .setHeader('content-type', 'application/json')
  }

  // Chaining convenience
  //
  // Ex: Request.ok().tap(doSomething1).tap(doSomething2) => Request
  //
  // (Response -> Response)
  tap (f) {
    return f(this)
  }
}

export default Response

@@ -2067,29 +2067,24 @@
 the request%0A
-  //%0A
   // Returns
@@ -2140,16 +2140,16 @@
 e.send.%0A
+
   finali
@@ -3035,33 +3035,44 @@
       .setHeader
-(
+s(%7B%0A        
 'content-length'
@@ -3075,35 +3075,26 @@
 gth'
-,
+:
  length
-)
+,
 %0A      
-.setHeader(
+  
 'con
@@ -3107,14 +3107,22 @@
 ype'
-,
+:
  type
+%0A      %7D
 )%0A  
@@ -3178,21 +3178,16 @@
 ed/null%0A
-  //%0A
   // (St
@@ -3246,41 +3246,20 @@
 if (
-val === undefined %7C%7C val === null
+R.isNil(key)
 ) re
@@ -3328,24 +3328,255 @@
 , val)%0A  %7D%0A%0A
+  // Does not set header if val is undefined/null%0A  // Ex: setHeaders(%7B 'content-type': 'this', 'x-header': 'that' %7D)%0A  // Object -%3E Response%0A  setHeaders (obj) %7B%0A    return this.mergeDeep(%7B headers: R.reject(R.isNil, obj) %7D)%0A  %7D%0A%0A
   // String 
@@ -3873,21 +3873,16 @@
 aching.%0A
-  //%0A
   // (St
@@ -4201,21 +4201,16 @@
 enience%0A
-  //%0A
   // Ex:
@@ -4270,21 +4270,16 @@
 Request%0A
-  //%0A
   // (Re

Implement Response#setHeaders