<?php

final class DiffusionBlameController extends DiffusionController {

  public function shouldAllowPublic() {
    return true;
  }

  public function handleRequest(AphrontRequest $request) {
    $response = $this->loadDiffusionContext();
    if ($response) {
      return $response;
    }

    $viewer = $this->getViewer();
    $drequest = $this->getDiffusionRequest();
    $repository = $drequest->getRepository();

    $blame = $this->loadBlame();

    $identifiers = array_fuse($blame);
    if ($identifiers) {
      $commits = id(new DiffusionCommitQuery())
        ->setViewer($viewer)
        ->withRepository($repository)
        ->withIdentifiers($identifiers)
        ->execute();
      $commits = mpull($commits, null, 'getCommitIdentifier');
    } else {
      $commits = array();
    }

    $commit_map = mpull($commits, 'getCommitIdentifier', 'getPHID');

    $revisions = array();
    $revision_map = array();
    if ($commits) {
      $revision_ids = id(new DifferentialRevision())
        ->loadIDsByCommitPHIDs(array_keys($commit_map));
      if ($revision_ids) {
        $revisions = id(new DifferentialRevisionQuery())
          ->setViewer($viewer)
          ->withIDs($revision_ids)
          ->execute();
        $revisions = mpull($revisions, null, 'getID');
      }

      foreach ($revision_ids as $commit_phid => $revision_id) {
        // If the viewer can't actually see this revision, skip it.
        if (!isset($revisions[$revision_id])) {
          continue;
        }
        $revision_map[$commit_map[$commit_phid]] = $revision_id;
      }
    }

    $base_href = (string)$drequest->generateURI(
      array(
        'action' => 'browse',
        'stable' => true,
      ));

    $skip_text = pht('Skip Past This Commit');
    $skip_icon = id(new PHUIIconView())
      ->setIcon('fa-backward');

    Javelin::initBehavior('phabricator-tooltips');

    $handle_phids = array();
    foreach ($commits as $commit) {
      $author_phid = $commit->getAuthorPHID();
      if ($author_phid) {
        $handle_phids[] = $author_phid;
      }
    }

    foreach ($revisions as $revision) {
      $handle_phids[] = $revision->getAuthorPHID();
    }

    $handles = $viewer->loadHandles($handle_phids);

    $map = array();
    foreach ($identifiers as $identifier) {
      $revision_id = idx($revision_map, $identifier);
      if ($revision_id) {
        $revision = idx($revisions, $revision_id);
      } else {
        $revision = null;
      }

      $skip_href = $base_href.'?before='.$identifier;

      $skip_link = javelin_tag(
        'a',
        array(
          'href'  => $skip_href,
          'sigil' => 'has-tooltip',
          'meta'  => array(
            'tip'     => $skip_text,
            'align'   => 'E',
            'size'    => 300,
          ),
        ),
        $skip_icon);

      $commit = $commits[$identifier];

      $author_phid = $commit->getAuthorPHID();
      if (!$author_phid && $revision) {
        $author_phid = $revision->getAuthorPHID();
      }

      if (!$author_phid) {
        // This means we couldn't identify an author for the commit or the
        // revision. We just render a blank for alignment.
        $author_style = null;
        $author_href = null;
        $author_sigil = null;
        $author_meta = null;
      } else {
        $author_src = $handles[$author_phid]->getImageURI();
        $author_style = 'background-image: url('.$author_src.');';
        $author_href = $handles[$author_phid]->getURI();
        $author_sigil = 'has-tooltip';
        $author_meta = array(
          'tip' => $handles[$author_phid]->getName(),
          'align' => 'E',
        );
      }

      $author_link = javelin_tag(
        $author_href ? 'a' : 'span',
        array(
          'class' => 'phabricator-source-blame-author',
          'style' => $author_style,
          'href' => $author_href,
          'sigil' => $author_sigil,
          'meta' => $author_meta,
        ));

      $commit_link = javelin_tag(
        'a',
        array(
          'href' => $commit->getURI(),
          'sigil' => 'has-tooltip',
          'meta'  => array(
            'tip'   => $this->renderCommitTooltip($commit, $handles),
            'align' => 'E',
            'size'  => 600,
          ),
        ),
        $commit->getLocalName());

      $info = array(
        $author_link,
        $commit_link,
      );

      if ($revision) {
        $revision_link = phutil_tag(
          'a',
          array(
            'href' => $revision->getURI(),
            'sigil' => 'has-tooltip',
            'meta'  => array(
              'tip'   => $this->renderRevisionTooltip($revision, $handles),
              'align' => 'E',
              'size'  => 600,
            ),
          ),
          $revision->getMonogram());


        $info = array(
          $info,
          ' / ',
          $revision_link,
        );
      }

      $data = array(
        'skip' => $skip_link,
        'info' => hsprintf('%s', $info),
      );

      $map[$identifier] = $data;
    }

    return id(new AphrontAjaxResponse())->setContent(
      array(
        'blame' => $blame,
        'map' => $map,
      ));
  }

  private function loadBlame() {
    $drequest = $this->getDiffusionRequest();

    $commit = $drequest->getCommit();
    $path = $drequest->getPath();

    $blame_timeout = 15;

    $blame = $this->callConduitWithDiffusionRequest(
      'diffusion.blame',
      array(
        'commit' => $commit,
        'paths' => array($path),
        'timeout' => $blame_timeout,
      ));

    return idx($blame, $path, array());
  }

  private function renderRevisionTooltip(
    DifferentialRevision $revision,
    $handles) {
    $viewer = $this->getViewer();

    $date = phabricator_date($revision->getDateModified(), $viewer);
    $monogram = $revision->getMonogram();
    $title = $revision->getTitle();
    $header = "{$monogram} {$title}";

    $author = $handles[$revision->getAuthorPHID()]->getName();

    return "{$header}\n{$date} \xC2\xB7 {$author}";
  }

  private function renderCommitTooltip(
    PhabricatorRepositoryCommit $commit,
    $handles) {

    $viewer = $this->getViewer();

    $date = phabricator_date($commit->getEpoch(), $viewer);
    $summary = trim($commit->getSummary());

    $author_phid = $commit->getAuthorPHID();
    if ($author_phid && isset($handles[$author_phid])) {
      $author_name = $handles[$author_phid]->getName();
    } else {
      $author_name = null;
    }

    if ($author_name) {
      return "{$summary}\n{$date} \xC2\xB7 {$author_name}";
    } else {
      return "{$summary}\n{$date}";
    }
  }

}