'use strict'

var ms = require('mississippi')
var jsonstream = require('JSONStream')
var columnify = require('columnify')

// This module consumes package data in the following format:
//
// {
//   name: String,
//   description: String,
//   maintainers: [{ username: String, email: String }],
//   keywords: String | [String],
//   version: String,
//   date: Date // can be null,
// }
//
// The returned stream will format this package data
// into a byte stream of formatted, displayable output.

module.exports = formatPackageStream
function formatPackageStream (opts) {
  opts = opts || {}
  if (opts.json) {
    return jsonOutputStream()
  } else {
    return textOutputStream(opts)
  }
}

function jsonOutputStream () {
  return ms.pipeline.obj(
    ms.through.obj(),
    jsonstream.stringify('[', ',', ']'),
    ms.through()
  )
}

function textOutputStream (opts) {
  var line = 0
  return ms.through.obj(function (pkg, enc, cb) {
    cb(null, prettify(pkg, ++line, opts))
  })
}

function prettify (data, num, opts) {
  opts = opts || {}
  var truncate = !opts.long

  var pkg = normalizePackage(data, opts)

  var columns = opts.description
    ? ['name', 'description', 'author', 'date', 'version', 'keywords']
    : ['name', 'author', 'date', 'version', 'keywords']

  if (opts.parseable) {
    return columns.map(function (col) {
      return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ')
    }).join('\t')
  }

  var output = columnify(
    [pkg],
    {
      include: columns,
      showHeaders: num <= 1,
      columnSplitter: ' | ',
      truncate: truncate,
      config: {
        name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' },
        description: { minWidth: 20, maxWidth: 20 },
        author: { minWidth: 15, maxWidth: 15 },
        date: { maxWidth: 11 },
        version: { minWidth: 8, maxWidth: 8 },
        keywords: { maxWidth: Infinity }
      }
    }
  )
  output = trimToMaxWidth(output)
  if (opts.color) {
    output = highlightSearchTerms(output, opts.args)
  }
  return output
}

var colors = [31, 33, 32, 36, 34, 35]
var cl = colors.length

function addColorMarker (str, arg, i) {
  var m = i % cl + 1
  var markStart = String.fromCharCode(m)
  var markEnd = String.fromCharCode(0)

  if (arg.charAt(0) === '/') {
    return str.replace(
      new RegExp(arg.substr(1, arg.length - 2), 'gi'),
      function (bit) { return markStart + bit + markEnd }
    )
  }

  // just a normal string, do the split/map thing
  var pieces = str.toLowerCase().split(arg.toLowerCase())
  var p = 0

  return pieces.map(function (piece) {
    piece = str.substr(p, piece.length)
    var mark = markStart +
               str.substr(p + piece.length, arg.length) +
               markEnd
    p += piece.length + arg.length
    return piece + mark
  }).join('')
}

function colorize (line) {
  for (var i = 0; i < cl; i++) {
    var m = i + 1
    var color = '\u001B[' + colors[i] + 'm'
    line = line.split(String.fromCharCode(m)).join(color)
  }
  var uncolor = '\u001B[0m'
  return line.split('\u0000').join(uncolor)
}

function getMaxWidth () {
  var cols
  try {
    var tty = require('tty')
    var stdout = process.stdout
    cols = !tty.isatty(stdout.fd) ? Infinity : process.stdout.getWindowSize()[0]
    cols = (cols === 0) ? Infinity : cols
  } catch (ex) { cols = Infinity }
  return cols
}

function trimToMaxWidth (str) {
  var maxWidth = getMaxWidth()
  return str.split('\n').map(function (line) {
    return line.slice(0, maxWidth)
  }).join('\n')
}

function highlightSearchTerms (str, terms) {
  terms.forEach(function (arg, i) {
    str = addColorMarker(str, arg, i)
  })

  return colorize(str).trim()
}

function normalizePackage (data, opts) {
  opts = opts || {}
  return {
    name: data.name,
    description: opts.description ? data.description : '',
    author: (data.maintainers || []).map(function (m) {
      return '=' + m.username
    }).join(' '),
    keywords: Array.isArray(data.keywords)
      ? data.keywords.join(' ')
      : typeof data.keywords === 'string'
        ? data.keywords.replace(/[,\s]+/, ' ')
        : '',
    version: data.version,
    date: (data.date &&
          (data.date.toISOString() // remove time
            .split('T').join(' ')
            .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
            .slice(0, -5)) ||
          'prehistoric'
  }
}
