var assert = require('assert')

var Buffer = require('buffer').Buffer

// Node.js version
var mode = /^v0\.8\./.test(process.version)
  ? 'rusty'
  : /^v0\.(9|10)\./.test(process.version)
    ? 'old'
    : /^v0\.12\./.test(process.version)
      ? 'normal'
      : 'modern'

var HTTPParser

var methods
var reverseMethods

// var kOnHeaders
var kOnHeadersComplete
var kOnMessageComplete
var kOnBody
if (mode === 'normal' || mode === 'modern') {
  HTTPParser = process.binding('http_parser').HTTPParser
  methods = HTTPParser.methods

  // v6
  if (!methods) {
    methods = process.binding('http_parser').methods
  }

  reverseMethods = {}

  methods.forEach(function (method, index) {
    reverseMethods[method] = index
  })

  // kOnHeaders = HTTPParser.kOnHeaders | 0
  kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0
  kOnMessageComplete = HTTPParser.kOnMessageComplete | 0
  kOnBody = HTTPParser.kOnBody | 0
} else {
  // kOnHeaders = 'onHeaders'
  kOnHeadersComplete = 'onHeadersComplete'
  kOnMessageComplete = 'onMessageComplete'
  kOnBody = 'onBody'
}

function Deceiver (socket, options) {
  this.socket = socket
  this.options = options || {}
  this.isClient = this.options.isClient
}
module.exports = Deceiver

Deceiver.create = function create (stream, options) {
  return new Deceiver(stream, options)
}

Deceiver.prototype._toHeaderList = function _toHeaderList (object) {
  var out = []
  var keys = Object.keys(object)

  for (var i = 0; i < keys.length; i++) {
    out.push(keys[i], object[keys[i]])
  }

  return out
}

Deceiver.prototype._isUpgrade = function _isUpgrade (request) {
  return request.method === 'CONNECT' ||
         request.headers.upgrade ||
         request.headers.connection &&
            /(^|\W)upgrade(\W|$)/i.test(request.headers.connection)
}

// TODO(indutny): support CONNECT
if (mode === 'modern') {
  /*
  function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
                                   url, statusCode, statusMessage, upgrade,
                                   shouldKeepAlive) {
   */
  Deceiver.prototype.emitRequest = function emitRequest (request) {
    var parser = this.socket.parser
    assert(parser, 'No parser present')

    parser.execute = null

    var self = this
    var method = reverseMethods[request.method]
    parser.execute = function execute () {
      self._skipExecute(this)
      this[kOnHeadersComplete](1,
                               1,
                               self._toHeaderList(request.headers),
                               method,
                               request.path,
                               0,
                               '',
                               self._isUpgrade(request),
                               true)
      return 0
    }

    this._emitEmpty()
  }

  Deceiver.prototype.emitResponse = function emitResponse (response) {
    var parser = this.socket.parser
    assert(parser, 'No parser present')

    parser.execute = null

    var self = this
    parser.execute = function execute () {
      self._skipExecute(this)
      this[kOnHeadersComplete](1,
                               1,
                               self._toHeaderList(response.headers),
                               response.path,
                               response.code,
                               response.status,
                               response.reason || '',
                               self._isUpgrade(response),
                               true)
      return 0
    }

    this._emitEmpty()
  }
} else {
  /*
    `function parserOnHeadersComplete(info) {`
    info = { .versionMajor, .versionMinor, .url, .headers, .method,
             .statusCode, .statusMessage, .upgrade, .shouldKeepAlive }
   */
  Deceiver.prototype.emitRequest = function emitRequest (request) {
    var parser = this.socket.parser
    assert(parser, 'No parser present')

    var method = request.method
    if (reverseMethods) {
      method = reverseMethods[method]
    }

    var info = {
      versionMajor: 1,
      versionMinor: 1,
      url: request.path,
      headers: this._toHeaderList(request.headers),
      method: method,
      statusCode: 0,
      statusMessage: '',
      upgrade: this._isUpgrade(request),
      shouldKeepAlive: true
    }

    var self = this
    parser.execute = function execute () {
      self._skipExecute(this)
      this[kOnHeadersComplete](info)
      return 0
    }

    this._emitEmpty()
  }

  Deceiver.prototype.emitResponse = function emitResponse (response) {
    var parser = this.socket.parser
    assert(parser, 'No parser present')

    var info = {
      versionMajor: 1,
      versionMinor: 1,
      url: response.path,
      headers: this._toHeaderList(response.headers),
      method: false,
      statusCode: response.status,
      statusMessage: response.reason || '',
      upgrade: this._isUpgrade(response),
      shouldKeepAlive: true
    }

    var self = this
    parser.execute = function execute () {
      self._skipExecute(this)
      this[kOnHeadersComplete](info)
      return 0
    }

    this._emitEmpty()
  }
}

Deceiver.prototype._skipExecute = function _skipExecute (parser) {
  var self = this
  var oldExecute = parser.constructor.prototype.execute
  var oldFinish = parser.constructor.prototype.finish

  parser.execute = null
  parser.finish = null

  parser.execute = function execute (buffer, start, len) {
    // Parser reuse
    if (this.socket !== self.socket) {
      this.execute = oldExecute
      this.finish = oldFinish
      return this.execute(buffer, start, len)
    }

    if (start !== undefined) {
      buffer = buffer.slice(start, start + len)
    }
    self.emitBody(buffer)
    return len
  }

  parser.finish = function finish () {
    // Parser reuse
    if (this.socket !== self.socket) {
      this.execute = oldExecute
      this.finish = oldFinish
      return this.finish()
    }

    this.execute = oldExecute
    this.finish = oldFinish
    self.emitMessageComplete()
  }
}

Deceiver.prototype.emitBody = function emitBody (buffer) {
  var parser = this.socket.parser
  assert(parser, 'No parser present')

  parser[kOnBody](buffer, 0, buffer.length)
}

Deceiver.prototype._emitEmpty = function _emitEmpty () {
  // Emit data to force out handling of UPGRADE
  var empty = new Buffer(0)
  if (this.socket.ondata) { this.socket.ondata(empty, 0, 0) } else {
    this.socket.emit('data', empty)
  }
}

Deceiver.prototype.emitMessageComplete = function emitMessageComplete () {
  var parser = this.socket.parser
  assert(parser, 'No parser present')

  parser[kOnMessageComplete]()
}
