module.exports = WebConn

var BitField = require('bitfield')
var debug = require('debug')('webtorrent:webconn')
var get = require('simple-get')
var inherits = require('inherits')
var sha1 = require('simple-sha1')
var Wire = require('bittorrent-protocol')

inherits(WebConn, Wire)

/**
 * Converts requests for torrent blocks into http range requests.
 * @param {string} url web seed url
 * @param {Object} parsedTorrent
 */
function WebConn (url, parsedTorrent) {
  var self = this
  Wire.call(this)

  self.url = url
  self.webPeerId = sha1.sync(url)
  self.parsedTorrent = parsedTorrent

  self.setKeepAlive(true)

  self.on('handshake', function (infoHash, peerId) {
    self.handshake(infoHash, self.webPeerId)
    var numPieces = self.parsedTorrent.pieces.length
    var bitfield = new BitField(numPieces)
    for (var i = 0; i <= numPieces; i++) {
      bitfield.set(i, true)
    }
    self.bitfield(bitfield)
  })

  self.on('choke', function () { debug('choke') })
  self.on('unchoke', function () { debug('unchoke') })

  self.once('interested', function () {
    debug('interested')
    self.unchoke()
  })
  self.on('uninterested', function () { debug('uninterested') })

  self.on('bitfield', function () { debug('bitfield') })

  self.on('request', function (pieceIndex, offset, length, callback) {
    debug('request pieceIndex=%d offset=%d length=%d', pieceIndex, offset, length)
    self.httpRequest(pieceIndex, offset, length, callback)
  })
}

WebConn.prototype.httpRequest = function (pieceIndex, offset, length, cb) {
  var self = this
  var pieceOffset = pieceIndex * self.parsedTorrent.pieceLength
  var rangeStart = pieceOffset + offset /* offset within whole torrent */
  var rangeEnd = rangeStart + length - 1

  // Web seed URL format
  // For single-file torrents, you just make HTTP range requests directly to the web seed URL
  // For multi-file torrents, you have to add the torrent folder and file name to the URL
  var files = self.parsedTorrent.files
  var requests
  if (files.length <= 1) {
    requests = [{
      url: self.url,
      start: rangeStart,
      end: rangeEnd
    }]
  } else {
    var requestedFiles = files.filter(function (file) {
      return file.offset <= rangeEnd && (file.offset + file.length) > rangeStart
    })
    if (requestedFiles.length < 1) return cb(new Error('Could not find file corresponnding to web seed range request'))

    requests = requestedFiles.map(function (requestedFile) {
      var fileEnd = requestedFile.offset + requestedFile.length - 1
      var url = self.url +
        (self.url[self.url.length - 1] === '/' ? '' : '/') +
        requestedFile.path
      return {
        url: url,
        fileOffsetInRange: Math.max(requestedFile.offset - rangeStart, 0),
        start: Math.max(rangeStart - requestedFile.offset, 0),
        end: Math.min(fileEnd, rangeEnd - requestedFile.offset)
      }
    })
  }

  // Now make all the HTTP requests we need in order to load this piece
  // Usually that's one requests, but sometimes it will be multiple
  // Send requests in parallel and wait for them all to come back
  var numRequestsSucceeded = 0
  var hasError = false
  if (requests.length > 1) var ret = new Buffer(length)
  requests.forEach(function (request) {
    var url = request.url
    var start = request.start
    var end = request.end
    debug(
      'Requesting url=%s pieceIndex=%d offset=%d length=%d start=%d end=%d',
      url, pieceIndex, offset, length, start, end
    )
    var opts = {
      url: url,
      method: 'GET',
      headers: {
        'user-agent': 'WebTorrent (http://webtorrent.io)',
        'range': 'bytes=' + start + '-' + end
      }
    }
    get.concat(opts, function (err, res, data) {
      if (hasError) return
      if (err) {
        hasError = true
        return cb(err)
      }
      if (res.statusCode < 200 || res.statusCode >= 300) {
        hasError = true
        return cb(new Error('Unexpected HTTP status code ' + res.statusCode))
      }
      debug('Got data of length %d', data.length)
      if (requests.length === 1) {
        // Common case: fetch piece in a single HTTP request, return directly
        return cb(null, data)
      }
      // Rare case: reconstruct multiple HTTP requests across 2+ files into one piece buffer
      data.copy(ret, request.fileOffsetInRange)
      if (++numRequestsSucceeded === requests.length) {
        cb(null, ret)
      }
    })
  })
}
