/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
'use strict';

var chalk = require('chalk');
var exec = require('child_process').exec;

var hasWarned = {};

function getFlowTypeCheckMiddleware(options) {
  return function(req, res, next) {
    if (options.skipflow) {
      return next();
    }
    if (options.flowroot || options.projectRoots.length === 1) {
      var flowroot = options.flowroot || options.projectRoots[0];
    } else {
      if (!hasWarned.noRoot) {
        hasWarned.noRoot = true;
        console.warn('flow: No suitable root');
      }
      return next();
    }
    exec('command -v flow >/dev/null 2>&1', function(error, stdout) {
      if (error) {
        if (!hasWarned.noFlow) {
          hasWarned.noFlow = true;
          console.warn(chalk.yellow('flow: Skipping because not installed.  Install with ' +
            '`brew install flow`.'));
        }
        return next();
      } else {
        return doFlowTypecheck(res, flowroot, next);
      }
    });
  };
}

function doFlowTypecheck(res, flowroot, next) {
  var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20';
  var start = Date.now();
  // Log start message if flow is slow to let user know something is happening.
  var flowSlow = setTimeout(
    function() {
      console.log(chalk.gray('flow: Running static typechecks.'));
    },
    500
  );
  exec(flowCmd, function(flowError, stdout, stderr) {
    clearTimeout(flowSlow);
    if (!flowError) {
      console.log(chalk.gray(
        'flow: Typechecks passed (' + (Date.now() - start) + 'ms).')
      );
      return next();
    } else {
      try {
        var flowResponse = JSON.parse(stdout);
        var errors = [];
        var errorNum = 1;
        flowResponse.errors.forEach(function(err) {
          // flow errors are paired across callsites, so we indent and prefix to
          // group them
          var indent = '';
          err.message.forEach(function(msg) {
            errors.push({
              description: indent + 'E' + errorNum + ': ' + msg.descr,
              filename: msg.path,
              lineNumber: msg.line,
              column: msg.start,
            });
            indent = '  ';
          });
          errorNum++;
        });
        var error = {
          status: 500,
          message: 'Flow found type errors.  If you think these are wrong, ' +
            'make sure your flow bin and .flowconfig are up to date, or ' +
            'disable with --skipflow.',
          type: 'FlowError',
          errors: errors,
        };
        console.error(chalk.yellow('flow: Error running command `' + flowCmd +
          '`:\n' + JSON.stringify(error))
        );
        res.writeHead(error.status, {
          'Content-Type': 'application/json; charset=UTF-8',
        });
        res.end(JSON.stringify(error));
      } catch (e) {
        if (stderr.match(/Could not find a \.flowconfig/)) {
          if (!hasWarned.noConfig) {
            hasWarned.noConfig = true;
            console.warn(chalk.yellow('flow: ' + stderr));
          }
        } else {
          if (!hasWarned.brokenFlow) {
            hasWarned.brokenFlow = true;
            console.warn(chalk.yellow(
              'Flow failed to provide parseable output:\n\n`' + stdout +
              '`.\n' + 'stderr: `' + stderr + '`'
            ));
          }
        }
        return next();
      }
    }
  });
}

module.exports = getFlowTypeCheckMiddleware;
