/**
 * Module dependencies.
 */
'use strict';

const Counter = require('passthrough-counter');
const humanize = require('humanize-number');
const bytes = require('bytes');
const chalk = require('chalk');

/**
 * TTY check for dev format.
 */

const isatty = process.stdout.isTTY;

/**
 * Expose logger.
 */

module.exports = dev;

/**
 * Color map.
 */

const colorCodes = {
  5: 'red',
  4: 'yellow',
  3: 'cyan',
  2: 'green',
  1: 'green',
  0: 'yellow'
};

/**
 * Development logger.
 */

function dev(opts) {
  return function logger(ctx, next) {
    // request
    const start = new Date;
    console.log('  ' + chalk.gray('<--')
      + ' ' + chalk.bold('%s')
      + ' ' + chalk.gray('%s'),
        ctx.method,
        ctx.originalUrl);

    return next().then(function() {

      // calculate the length of a streaming response
      // by intercepting the stream with a counter.
      // only necessary if a content-length header is currently not set.
      const length = ctx.response.length;
      const body = ctx.body;
      let counter;
      if (null == length && body && body.readable) {
        ctx.body = body
          .pipe(counter = Counter())
          .on('error', ctx.onerror);
      }

      // log when the response is finished or closed,
      // whichever happens first.
      const res = ctx.res;

      const onfinish = done.bind(null, 'finish');
      const onclose = done.bind(null, 'close');

      res.once('finish', onfinish);
      res.once('close', onclose);

      function done(event){
        res.removeListener('finish', onfinish);
        res.removeListener('close', onclose);
        log(ctx, start, counter ? counter.length : length, null, event);
      }

    }, function(err) {
      // log uncaught downstream errors
      log(ctx, start, null, err);
      throw err;
    });

  }
}

/**
 * Log helper.
 */

function log(ctx, start, len, err, event) {
  // get the status code of the response
  const status = err
    ? (err.status || 500)
    : (ctx.status || 404);

  // set the color of the status code;
  const s = status / 100 | 0;
  const color = colorCodes[s];

  // get the human readable response length
  let length;
  if (~[204, 205, 304].indexOf(status)) {
    length = '';
  } else if (null == len) {
    length = '-';
  } else {
    length = bytes(len);
  }

  const upstream = err ? chalk.red('xxx')
    : event === 'close' ? chalk.yellow('-x-')
    : chalk.gray('-->')

  console.log('  ' + upstream
    + ' ' + chalk.bold('%s')
    + ' ' + chalk.gray('%s')
    + ' ' + chalk[color]('%s')
    + ' ' + chalk.gray('%s')
    + ' ' + chalk.gray('%s'),
      ctx.method,
      ctx.originalUrl,
      status,
      time(start),
      length);
}

/**
 * Show the response time in a human readable format.
 * In milliseconds if less than 10 seconds,
 * in seconds otherwise.
 */

function time(start) {
  const delta = new Date - start;
  return humanize(delta < 10000
    ? delta + 'ms'
    : Math.round(delta / 1000) + 's');
}