<?php

final class DifferentialRevisionSearchEngine
  extends PhabricatorApplicationSearchEngine {

  public function getResultTypeDescription() {
    return pht('Differential Revisions');
  }

  public function getApplicationClassName() {
    return 'PhabricatorDifferentialApplication';
  }

  protected function newResultBuckets() {
    return DifferentialRevisionResultBucket::getAllResultBuckets();
  }

  public function newQuery() {
    return id(new DifferentialRevisionQuery())
      ->needFlags(true)
      ->needDrafts(true)
      ->needReviewers(true);
  }

  protected function buildQueryFromParameters(array $map) {
    $query = $this->newQuery();

    if ($map['responsiblePHIDs']) {
      $query->withResponsibleUsers($map['responsiblePHIDs']);
    }

    if ($map['authorPHIDs']) {
      $query->withAuthors($map['authorPHIDs']);
    }

    if ($map['reviewerPHIDs']) {
      $query->withReviewers($map['reviewerPHIDs']);
    }

    if ($map['repositoryPHIDs']) {
      $query->withRepositoryPHIDs($map['repositoryPHIDs']);
    }

    if ($map['status']) {
      $query->withStatus($map['status']);
    }

    return $query;
  }

  protected function buildCustomSearchFields() {
    return array(
      id(new PhabricatorSearchDatasourceField())
        ->setLabel(pht('Responsible Users'))
        ->setKey('responsiblePHIDs')
        ->setAliases(array('responsiblePHID', 'responsibles', 'responsible'))
        ->setDatasource(new DifferentialResponsibleDatasource())
        ->setDescription(
          pht('Find revisions that a given user is responsible for.')),
      id(new PhabricatorUsersSearchField())
        ->setLabel(pht('Authors'))
        ->setKey('authorPHIDs')
        ->setAliases(array('author', 'authors', 'authorPHID'))
        ->setDescription(
          pht('Find revisions with specific authors.')),
      id(new PhabricatorSearchDatasourceField())
        ->setLabel(pht('Reviewers'))
        ->setKey('reviewerPHIDs')
        ->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
        ->setDatasource(new DiffusionAuditorFunctionDatasource())
        ->setDescription(
          pht('Find revisions with specific reviewers.')),
      id(new PhabricatorSearchDatasourceField())
        ->setLabel(pht('Repositories'))
        ->setKey('repositoryPHIDs')
        ->setAliases(array('repository', 'repositories', 'repositoryPHID'))
        ->setDatasource(new DiffusionRepositoryFunctionDatasource())
        ->setDescription(
          pht('Find revisions from specific repositories.')),
      id(new PhabricatorSearchSelectField())
        ->setLabel(pht('Status'))
        ->setKey('status')
        ->setOptions($this->getStatusOptions())
        ->setDescription(
          pht('Find revisions with particular statuses.')),
    );
  }

  protected function getURI($path) {
    return '/differential/'.$path;
  }

  protected function getBuiltinQueryNames() {
    $names = array();

    if ($this->requireViewer()->isLoggedIn()) {
      $names['active'] = pht('Active Revisions');
      $names['authored'] = pht('Authored');
    }

    $names['all'] = pht('All Revisions');

    return $names;
  }

  public function buildSavedQueryFromBuiltin($query_key) {
    $query = $this->newSavedQuery();
    $query->setQueryKey($query_key);

    $viewer = $this->requireViewer();

    switch ($query_key) {
      case 'active':
        $bucket_key = DifferentialRevisionRequiredActionResultBucket::BUCKETKEY;

        return $query
          ->setParameter('responsiblePHIDs', array($viewer->getPHID()))
          ->setParameter('status', DifferentialRevisionQuery::STATUS_OPEN)
          ->setParameter('bucket', $bucket_key);
      case 'authored':
        return $query
          ->setParameter('authorPHIDs', array($viewer->getPHID()));
      case 'all':
        return $query;
    }

    return parent::buildSavedQueryFromBuiltin($query_key);
  }

  private function getStatusOptions() {
    return array(
      DifferentialRevisionQuery::STATUS_ANY            => pht('All'),
      DifferentialRevisionQuery::STATUS_OPEN           => pht('Open'),
      DifferentialRevisionQuery::STATUS_ACCEPTED       => pht('Accepted'),
      DifferentialRevisionQuery::STATUS_NEEDS_REVIEW   => pht('Needs Review'),
      DifferentialRevisionQuery::STATUS_NEEDS_REVISION => pht('Needs Revision'),
      DifferentialRevisionQuery::STATUS_CLOSED         => pht('Closed'),
      DifferentialRevisionQuery::STATUS_ABANDONED      => pht('Abandoned'),
    );
  }

  protected function renderResultList(
    array $revisions,
    PhabricatorSavedQuery $query,
    array $handles) {
    assert_instances_of($revisions, 'DifferentialRevision');

    $viewer = $this->requireViewer();
    $template = id(new DifferentialRevisionListView())
      ->setUser($viewer)
      ->setNoBox($this->isPanelContext());

    $bucket = $this->getResultBucket($query);

    $unlanded = $this->loadUnlandedDependencies($revisions);

    $views = array();
    if ($bucket) {
      $bucket->setViewer($viewer);

      try {
        $groups = $bucket->newResultGroups($query, $revisions);

        foreach ($groups as $group) {
          // Don't show groups in Dashboard Panels
          if ($group->getObjects() || !$this->isPanelContext()) {
            $views[] = id(clone $template)
              ->setHeader($group->getName())
              ->setNoDataString($group->getNoDataString())
              ->setRevisions($group->getObjects());
          }
        }
      } catch (Exception $ex) {
        $this->addError($ex->getMessage());
      }
    } else {
      $views[] = id(clone $template)
        ->setRevisions($revisions)
        ->setHandles(array());
    }

    if (!$views) {
      $views[] = id(new DifferentialRevisionListView())
        ->setUser($viewer)
        ->setNoDataString(pht('No revisions found.'));
    }

    $phids = array_mergev(mpull($views, 'getRequiredHandlePHIDs'));
    if ($phids) {
      $handles = id(new PhabricatorHandleQuery())
        ->setViewer($viewer)
        ->withPHIDs($phids)
        ->execute();
    } else {
      $handles = array();
    }

    foreach ($views as $view) {
      $view->setHandles($handles);
      $view->setUnlandedDependencies($unlanded);
    }

    if (count($views) == 1) {
      // Reduce this to a PHUIObjectItemListView so we can get the free
      // support from ApplicationSearch.
      $list = head($views)->render();
    } else {
      $list = $views;
    }

    $result = new PhabricatorApplicationSearchResultView();
    $result->setContent($list);

    return $result;
  }

  protected function getNewUserBody() {
    $create_button = id(new PHUIButtonView())
      ->setTag('a')
      ->setText(pht('Create a Diff'))
      ->setHref('/differential/diff/create/')
      ->setColor(PHUIButtonView::GREEN);

    $icon = $this->getApplication()->getIcon();
    $app_name =  $this->getApplication()->getName();
    $view = id(new PHUIBigInfoView())
      ->setIcon($icon)
      ->setTitle(pht('Welcome to %s', $app_name))
      ->setDescription(
        pht('Pre-commit code review. Revisions that are waiting on your input '.
            'will appear here.'))
      ->addAction($create_button);

      return $view;
  }

  private function loadUnlandedDependencies(array $revisions) {
    $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;

    $phids = array();
    foreach ($revisions as $revision) {
      if ($revision->getStatus() != $status_accepted) {
        continue;
      }

      $phids[] = $revision->getPHID();
    }

    if (!$phids) {
      return array();
    }

    $query = id(new PhabricatorEdgeQuery())
      ->withSourcePHIDs($phids)
      ->withEdgeTypes(
        array(
          DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST,
        ));

    $query->execute();

    $revision_phids = $query->getDestinationPHIDs();
    if (!$revision_phids) {
      return array();
    }

    $viewer = $this->requireViewer();

    $blocking_revisions = id(new DifferentialRevisionQuery())
      ->setViewer($viewer)
      ->withPHIDs($revision_phids)
      ->withStatus(DifferentialRevisionQuery::STATUS_OPEN)
      ->execute();
    $blocking_revisions = mpull($blocking_revisions, null, 'getPHID');

    $result = array();
    foreach ($revisions as $revision) {
      $revision_phid = $revision->getPHID();
      $blocking_phids = $query->getDestinationPHIDs(array($revision_phid));
      $blocking = array_select_keys($blocking_revisions, $blocking_phids);
      if ($blocking) {
        $result[$revision_phid] = $blocking;
      }
    }

    return $result;
  }

}
