<?php

abstract class DiffusionController extends PhabricatorController {

  private $diffusionRequest;

  protected function getDiffusionRequest() {
    if (!$this->diffusionRequest) {
      throw new PhutilInvalidStateException('loadDiffusionContext');
    }
    return $this->diffusionRequest;
  }

  protected function hasDiffusionRequest() {
    return (bool)$this->diffusionRequest;
  }

  public function willBeginExecution() {
    $request = $this->getRequest();

    // Check if this is a VCS request, e.g. from "git clone", "hg clone", or
    // "svn checkout". If it is, we jump off into repository serving code to
    // process the request.

    $serve_controller = new DiffusionServeController();
    if ($serve_controller->isVCSRequest($request)) {
      return $this->delegateToController($serve_controller);
    }

    return parent::willBeginExecution();
  }

  protected function loadDiffusionContextForEdit() {
    return $this->loadContext(
      array(
        'edit' => true,
      ));
  }

  protected function loadDiffusionContext() {
    return $this->loadContext(array());
  }

  private function loadContext(array $options) {
    $request = $this->getRequest();
    $viewer = $this->getViewer();

    $identifier = $this->getRepositoryIdentifierFromRequest($request);

    $params = $options + array(
      'repository' => $identifier,
      'user' => $viewer,
      'blob' => $this->getDiffusionBlobFromRequest($request),
      'commit' => $request->getURIData('commit'),
      'path' => $request->getURIData('path'),
      'line' => $request->getURIData('line'),
      'branch' => $request->getURIData('branch'),
      'lint' => $request->getStr('lint'),
    );

    $drequest = DiffusionRequest::newFromDictionary($params);

    if (!$drequest) {
      return new Aphront404Response();
    }

    $this->diffusionRequest = $drequest;

    return null;
  }

  protected function getDiffusionBlobFromRequest(AphrontRequest $request) {
    return $request->getURIData('dblob');
  }

  protected function getRepositoryIdentifierFromRequest(
    AphrontRequest $request) {

    $identifier = $request->getURIData('repositoryCallsign');
    if (strlen($identifier)) {
      return $identifier;
    }

    $id = $request->getURIData('repositoryID');
    if (strlen($id)) {
      return (int)$id;
    }

    return null;
  }

  public function buildCrumbs(array $spec = array()) {
    $crumbs = $this->buildApplicationCrumbs();
    $crumb_list = $this->buildCrumbList($spec);
    foreach ($crumb_list as $crumb) {
      $crumbs->addCrumb($crumb);
    }
    return $crumbs;
  }

  private function buildCrumbList(array $spec = array()) {

    $spec = $spec + array(
      'commit'  => null,
      'tags'    => null,
      'branches'    => null,
      'view'    => null,
    );

    $crumb_list = array();

    // On the home page, we don't have a DiffusionRequest.
    if ($this->hasDiffusionRequest()) {
      $drequest = $this->getDiffusionRequest();
      $repository = $drequest->getRepository();
    } else {
      $drequest = null;
      $repository = null;
    }

    if (!$repository) {
      return $crumb_list;
    }

    $repository_name = $repository->getName();

    if (!$spec['commit'] && !$spec['tags'] && !$spec['branches']) {
      $branch_name = $drequest->getBranch();
      if ($branch_name) {
        $repository_name .= ' ('.$branch_name.')';
      }
    }

    $crumb = id(new PHUICrumbView())
      ->setName($repository_name);
    if (!$spec['view'] && !$spec['commit'] &&
        !$spec['tags'] && !$spec['branches']) {
      $crumb_list[] = $crumb;
      return $crumb_list;
    }
    $crumb->setHref(
      $drequest->generateURI(
        array(
          'action' => 'branch',
          'path' => '/',
        )));
    $crumb_list[] = $crumb;

    $stable_commit = $drequest->getStableCommit();
    $commit_name = $repository->formatCommitName($stable_commit);
    $commit_uri = $repository->getCommitURI($stable_commit);

    if ($spec['tags']) {
      $crumb = new PHUICrumbView();
      if ($spec['commit']) {
        $crumb->setName(pht('Tags for %s', $commit_name));
        $crumb->setHref($commit_uri);
      } else {
        $crumb->setName(pht('Tags'));
      }
      $crumb_list[] = $crumb;
      return $crumb_list;
    }

    if ($spec['branches']) {
      $crumb = id(new PHUICrumbView())
        ->setName(pht('Branches'));
      $crumb_list[] = $crumb;
      return $crumb_list;
    }

    if ($spec['commit']) {
      $crumb = id(new PHUICrumbView())
        ->setName($commit_name)
        ->setHref($commit_uri);
      $crumb_list[] = $crumb;
      return $crumb_list;
    }

    $crumb = new PHUICrumbView();
    $view = $spec['view'];

    switch ($view) {
      case 'history':
        $view_name = pht('History');
        break;
      case 'browse':
        $view_name = pht('Browse');
        break;
      case 'lint':
        $view_name = pht('Lint');
        break;
      case 'change':
        $view_name = pht('Change');
        break;
    }

    $crumb = id(new PHUICrumbView())
      ->setName($view_name);

    $crumb_list[] = $crumb;
    return $crumb_list;
  }

  protected function callConduitWithDiffusionRequest(
    $method,
    array $params = array()) {

    $user = $this->getRequest()->getUser();
    $drequest = $this->getDiffusionRequest();

    return DiffusionQuery::callConduitWithDiffusionRequest(
      $user,
      $drequest,
      $method,
      $params);
  }

  protected function getRepositoryControllerURI(
    PhabricatorRepository $repository,
    $path) {
    return $repository->getPathURI($path);
  }

  protected function renderPathLinks(DiffusionRequest $drequest, $action) {
    $path = $drequest->getPath();
    $path_parts = array_filter(explode('/', trim($path, '/')));

    $divider = phutil_tag(
      'span',
      array(
        'class' => 'phui-header-divider',
      ),
      '/');

    $links = array();
    if ($path_parts) {
      $links[] = phutil_tag(
        'a',
        array(
          'href' => $drequest->generateURI(
            array(
              'action' => $action,
              'path' => '',
            )),
        ),
        $drequest->getRepository()->getDisplayName());
      $links[] = $divider;
      $accum = '';
      $last_key = last_key($path_parts);
      foreach ($path_parts as $key => $part) {
        $accum .= '/'.$part;
        if ($key === $last_key) {
          $links[] = $part;
        } else {
          $links[] = phutil_tag(
            'a',
            array(
              'href' => $drequest->generateURI(
                array(
                  'action' => $action,
                  'path' => $accum.'/',
                )),
            ),
            $part);
          $links[] = $divider;
        }
      }
    } else {
      $links[] = $drequest->getRepository()->getDisplayName();
      $links[] = $divider;
    }

    return $links;
  }

  protected function renderStatusMessage($title, $body) {
    return id(new PHUIInfoView())
      ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
      ->setTitle($title)
      ->appendChild($body);
  }

  protected function renderTablePagerBox(PHUIPagerView $pager) {
    return id(new PHUIBoxView())
      ->addMargin(PHUI::MARGIN_LARGE)
      ->appendChild($pager);
  }

  protected function renderDirectoryReadme(DiffusionBrowseResultSet $browse) {
    $readme_path = $browse->getReadmePath();
    if ($readme_path === null) {
      return null;
    }

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

    try {
      $result = $this->callConduitWithDiffusionRequest(
        'diffusion.filecontentquery',
        array(
          'path' => $readme_path,
          'commit' => $drequest->getStableCommit(),
        ));
    } catch (Exception $ex) {
      return null;
    }

    $file_phid = $result['filePHID'];
    if (!$file_phid) {
      return null;
    }

    $file = id(new PhabricatorFileQuery())
      ->setViewer($viewer)
      ->withPHIDs(array($file_phid))
      ->executeOne();
    if (!$file) {
      return null;
    }

    $corpus = $file->loadFileData();

    if (!strlen($corpus)) {
      return null;
    }

    return id(new DiffusionReadmeView())
      ->setUser($this->getViewer())
      ->setPath($readme_path)
      ->setContent($corpus);
  }

}
