const stripAnsi = require('strip-ansi')
const log = require('../util/log')

function keyValueArray (columns, keys, obj) {
  return keys.map(el => {
    if (typeof columns[el] === 'function') {
      return columns[el](obj)
    } else {
      return obj[el]
    }
  })
}

function findMaxLengths (rows) {
  var widths = []

  for (let i = 0; i < rows.length; i++) {
    for (let j = 0; j < rows[i].length; j++) {
      let item = stripAnsi(rows[i][j]) + ''
      if (!widths[j] || widths[j] < item.length) {
        widths[j] = item.length
      }
    }
  }

  return widths
}

function createTable (rows, fitWidth) {
  function padEnd (string, maxLength, fillString) {
    // Don't pad to infinity.
    if (maxLength === Infinity) {
      return string
    }

    // Coerce to string.
    string = string + ''

    // Pad space chars.
    while (stripAnsi(string).length < maxLength) {
      string = string + fillString
    }

    // If the string is too long, substring it.
    if (string.length > maxLength) {
      return string.substr(0, maxLength)
    }

    return string
  }

  // Find target width.
  if (fitWidth > 0) {
    var targetWidth = Math.floor((fitWidth / rows[0].length) - 3)
  } else {
    targetWidth = Infinity
  }

  let maxWidths = findMaxLengths(rows)
  let widths = []

  // Budget for how much more space we can add if needed.
  var budget = 0

  // Calcualte initial column sizes.
  for (let i = 0; i < maxWidths.length; i++) {
    if (maxWidths[i] < targetWidth) {
      budget += (targetWidth - maxWidths[i])
      widths[i] = maxWidths[i]
    } else {
      widths[i] = targetWidth
    }
  }

  if (targetWidth < Infinity) {
    // Add budget until all items can be shown, or we run out of extra space.
    while (budget > 0) {
      var okCount = 0

      for (let i = 0; i < widths.length; i++) {
        let diff = maxWidths[i] - widths[i]

        // If the diff is >0, that means that there may be wrapping.
        if (diff > 0 && budget > 0) {
          // Add extra spaces.
          widths[i] += 1
          budget -= 1
        } else {
          okCount += 1
        }
      }

      // If they all are Ok, end.
      if (okCount === widths.length) {
        break
      }
    }
  }

  // Store the output rows
  var outputRows = []

  // Cursors for each item in the current row.
  var cursors = []

  // Additonal row overflow flag
  let flag = false

  for (let i = 0; i < rows.length; i++) {
    var line = []

    for (let j = 0; j < rows[i].length; j++) {
      cursors[j] = cursors[j] || 0

      // Extract item
      let rawItem = ''
      if (typeof rawItem === 'object') {
        rawItem = '[Object]'
      } else {
        rawItem = rows[i][j] + ''
      }

      // Slice for this row.
      let inputItem = rawItem.substr(cursors[j], widths[j])

      if (inputItem === '-') {
        line.push(padEnd(inputItem, widths[j], '-'))
      } else {
        // Pad the item and add to the line.
        let item = padEnd(inputItem, widths[j], ' ')
        line.push(item)

        // If the item is too long for one row, flag it to be printed below.
        if (cursors[j] + inputItem.length < rawItem.length && inputItem !== '') {
          flag = true
        }

        // Advance cursor.
        cursors[j] += widths[j]
      }
    }

    // Add line to output rows.
    outputRows.push(line)

    // If the flag is true, we need to repeat the last line.
    if (flag) {
      i -= 1
      flag = false
    } else {
      // Reset cursors.
      cursors = []
    }
  }

  return outputRows
}

module.exports.format = function (data, options) {
  // Keys for each column
  var keys = []

  // Separators for each column
  var separators = []

  // If data is not an array,
  // Turn it into one.
  if (!(data instanceof Array)) {
    data = [data]
  }

  // Ensure we have a column list.
  // If there are no items, grab the keys from the data object.
  if ((!options.columns || Object.keys(options.columns).length === 0) && data.length > 0) {
    options.columns = {}

    for (let item of Object.keys(data[0])) {
      options.columns[item] = true
    }
  }

  // Add to collection of keys
  for (var key in options.columns) {
    keys.push(key)
    separators.push('-')
  }

  // Create the rows
  var items = [
    keys,
    separators,
    ...data.map(data => keyValueArray(options.columns, keys, data))
  ]

  if (options.program && options.program.output !== undefined) {
    var targetWidth = 120
  } else {
    targetWidth = process.stdout.columns
  }

  // Normalize column widths.
  items = createTable(items, targetWidth).map(el => {
    return el.join(' | ').replace(/\n/g, '')
  }).join('\n')

  if (options.program) {
    // Disable color output
    if (!options.program.color) { items = stripAnsi(items) }

    // If reporting output is defined, ignore console log here.
    if (options.program.output === undefined) {
      log.raw(items)
    }
  } else {
    log.raw(items)
  }

  return items
}

const fs = require('fs-extra')
const path = require('path')

module.exports.finalReport = async function (reports, program) {
  if (program.output === undefined) {
    return
  }

  // Ensure the output directory exists.
  fs.ensureDirSync(program.output)

  // Write each report to the disk
  for (var report of reports) {
    var outPath = path.join(program.output, report.name + '.txt')
    log.action('saving', outPath)

    if (program.output === '-') {
      console.log(report.contents)
    } else {
      fs.writeFileSync(outPath, report.contents, 'utf8')
    }
  }
}