<?php
final class PythonFormatLinter extends ArcanistFutureLinter {
  public function getLinterName() {
    return 'BLACK';
  }

  protected function getFuturesLimit() {
    return 8;
  }

  protected function buildFutures(array $paths) {
    $vcs_root = $this->getVCSRoot();
    $futures = array();
    foreach ($paths as $path) {
      $fullPath = $this->getEngine()->getFilePathOnDisk($path);
      $dirPath = dirname($fullPath);
      $futures[$path] = new ExecFuture(
        '/usr/local/bin/black --diff %s',
        $this->getEngine()->getFilePathOnDisk($path));
      $futures[$path]->setCWD($this->getProjectRoot());
    }
    return $futures;
  }

  protected function resolveFuture($path, Future $future) {
    list($err, $stdout, $stderr) = $future->resolve();
    if ($err) {
      $this->addLintMessage(
        $this->parseLinterError($path, $stdout, $stderr, $err));
    } else {
      $messages = $this->parseLinterOutput($path, $stdout, $stderr);
      if ($messages !== false) {
        foreach ($messages as $message) {
          $this->addLintMessage($message);
        }
      }
    }
  }

  protected function parseLinterOutput($path, $stdout, $stderr) {
    if (empty($stdout) || substr($stdout, 0, 3) != '---') {
        return array();
    }

    $messages = array();
    $parser = new ArcanistDiffParser();
    $changes = $parser->parseDiff($stdout);

    foreach ($changes as $change) {
      foreach ($change->getHunks() as $hunk) {
        $repl = array();
        $orig = array();

        $lines = phutil_split_lines($hunk->getCorpus(), false);
        foreach ($lines as $line) {
          if (empty($line)) {
            continue;
          }

          $char = $line[0];
          $rest = substr($line, 1);

          switch ($char) {
            case '-':
              $orig[] = $rest;
              break;

            case '+':
              $repl[] = $rest;
              break;

            case '~':
              break;

            case ' ':
              $orig[] = $rest;
              $repl[] = $rest;
              break;
          }
        }

        $messages[] = id(new ArcanistLintMessage())
          ->setPath($path)
          ->setLine($hunk->getOldOffset())
          ->setChar(1)
          ->setCode($this->getLinterName())
          ->setSeverity(ArcanistLintSeverity::SEVERITY_AUTOFIX)
          ->setName('format')
          ->setOriginalText(implode("\n", $orig))
          ->setReplacementText(implode("\n", $repl))
          ->setBypassChangedLineFiltering(true);
      }
    }

    return $messages;
  }

  protected function parseLinterError($path, $stdout, $stderr, $err) {
    $matches = null;
    preg_match(
      '/error: cannot format -: Cannot parse: (?P<line>\d+):(?P<column>\d+): (?P<message>.*)/',
      $stderr,
      $matches);
    if ($matches) {
      $line = $matches['line'];
      $col = $matches['column'] + 1;
      $message = $matches['message'];
      $name = 'Cannot parse';
      $severity = ArcanistLintSeverity::SEVERITY_ERROR;
    } else {
      $line = 1;
      $col = 1;
      $message = $stderr;
      $name = sprintf('Command execution failed (%d)', $err);
      $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
    }

    return id(new ArcanistLintMessage())
      ->setPath($path)
      ->setLine($line)
      ->setChar($col)
      ->setCode($this->getLinterName())
      ->setSeverity($severity)
      ->setName($name)
      ->setDescription($message)
      ->setBypassChangedLineFiltering(true);
  }
}