<?php
// Copyright 2004-present Facebook.  All rights reserved.

class FbcodeCppLinter extends ArcanistLinter {
  const FLINT      = "/home/engshare/tools/flint";
  const LINT_ERROR   = 1;
  const LINT_WARNING = 2;
  const LINT_ADVICE  = 3;
  const C_FLAG = "--c_mode=true";

  private $rawLintOutput = array();

  public function willLintPaths(array $paths) {
    if (!file_exists(self::FLINT)) {
      return;
    }
    $futures = array();
    foreach ($paths as $p) {
      $lpath = $this->getEngine()->getFilePathOnDisk($p);
      $lpath_file = file($lpath);
      if (preg_match('/\.(c)$/', $lpath) ||
          preg_match('/-\*-.*Mode: C[; ].*-\*-/', $lpath_file[0]) ||
          preg_match('/vim(:.*)*:\s*(set\s+)?filetype=c\s*:/', $lpath_file[0])
          ) {
        $futures[$p] = new ExecFuture("%s %s %s 2>&1",
                           self::FLINT, self::C_FLAG,
                           $this->getEngine()->getFilePathOnDisk($p));
      } else {
        $futures[$p] = new ExecFuture("%s %s 2>&1",
          self::FLINT, $this->getEngine()->getFilePathOnDisk($p));
      }
    }

    foreach (Futures($futures)->limit(8) as $p => $f) {
      $this->rawLintOutput[$p] = $f->resolvex();
    }

    return;
  }

  public function getLinterName() {
    return "FBCPP";
  }

  public function lintPath($path) {
    $this->runCppLint($path);
  }

  private function runCppLint($path) {
    $msgs = $this->getCppLintOutput($path);
    foreach ($msgs as $m) {
      $this->raiseLintAtLine($m['line'], 0, $m['severity'], $m['msg']);
    }
  }

  private function adviseOnEachPattern(
    $path,
    $regex,
    $message,
    $lint_type = self::LINT_ADVICE,
    $match_idx = 0) {
      $file_data = $this->getData($path);
      $matches = array();
      if (!preg_match_all($regex, $file_data, $matches, PREG_OFFSET_CAPTURE)) {
        return;
      }

      foreach ($matches[$match_idx] as $match) {
        list($match_str, $offset) = $match;
        $this->raiseLintAtOffset($offset, $lint_type, $message, $match_str);
      }
  }

  public function getLintSeverityMap() {
    return array(
      self::LINT_WARNING => ArcanistLintSeverity::SEVERITY_WARNING,
      self::LINT_ADVICE  => ArcanistLintSeverity::SEVERITY_ADVICE,
      self::LINT_ERROR   => ArcanistLintSeverity::SEVERITY_ERROR
    );
  }

  public function getLintNameMap() {
    return array(
      self::LINT_ADVICE   => "CppLint Advice",
      self::LINT_WARNING  => "CppLint Warning",
      self::LINT_ERROR    => "CppLint Error"
    );
  }

  private function getCppLintOutput($path) {
     if (!array_key_exists($path, $this->rawLintOutput)) {
       return array();
     }
    list($output) = $this->rawLintOutput[$path];

    $msgs = array();
    $current = null;
    $matches = array();
    foreach (explode("\n", $output) as $line) {
      if (preg_match('/.*?:(\d+):(.*)/', $line, $matches)) {
        if ($current) {
          $msgs[] = $current;
        }
        $line = $matches[1];
        $text = $matches[2];
        if (preg_match('/.*Warning.*/', $text)) {
          $sev = self::LINT_WARNING;
        } else if (preg_match('/.*Advice.*/', $text)) {
          $sev = self::LINT_ADVICE;
        } else {
          $sev = self::LINT_ERROR;
        }
        $current = array('line'     => $line,
                         'msg'      => $text,
                         'severity' => $sev);
      } else if ($current) {
        $current['msg'] .= ' ' . $line;
      }
    }
    if ($current) {
      $msgs[] = $current;
    }

    return $msgs;
  }
}
