<?php

/**
 * @task config     Configuring Repository Engines
 * @task internal   Internals
 */
abstract class PhabricatorRepositoryEngine extends Phobject {

  private $repository;
  private $verbose;

  /**
   * @task config
   */
  public function setRepository(PhabricatorRepository $repository) {
    $this->repository = $repository;
    return $this;
  }


  /**
   * @task config
   */
  protected function getRepository() {
    if ($this->repository === null) {
      throw new PhutilInvalidStateException('setRepository');
    }

    return $this->repository;
  }


  /**
   * @task config
   */
  public function setVerbose($verbose) {
    $this->verbose = $verbose;
    return $this;
  }


  /**
   * @task config
   */
  public function getVerbose() {
    return $this->verbose;
  }


  public function getViewer() {
    return PhabricatorUser::getOmnipotentUser();
  }

  protected function newRepositoryLock(
    PhabricatorRepository $repository,
    $lock_key,
    $lock_device_only) {

    $lock_parts = array(
      'repositoryPHID' => $repository->getPHID(),
    );

    if ($lock_device_only) {
      $device = AlmanacKeys::getLiveDevice();
      if ($device) {
        $lock_parts['devicePHID'] = $device->getPHID();
      }
    }

    return PhabricatorGlobalLock::newLock($lock_key, $lock_parts);
  }

  /**
   * @task internal
   */
  protected function log($pattern /* ... */) {
    if ($this->getVerbose()) {
      $console = PhutilConsole::getConsole();
      $argv = func_get_args();
      array_unshift($argv, "%s\n");
      call_user_func_array(array($console, 'writeOut'), $argv);
    }
    return $this;
  }

  final protected function queueCommitImportTask(
    PhabricatorRepository $repository,
    $commit_phid,
    $task_priority,
    $via) {

    $vcs = $repository->getVersionControlSystem();
    switch ($vcs) {
      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
        $class = 'PhabricatorRepositoryGitCommitMessageParserWorker';
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
        $class = 'PhabricatorRepositorySvnCommitMessageParserWorker';
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
        $class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker';
        break;
      default:
        throw new Exception(
          pht(
            'Unknown repository type "%s"!',
            $vcs));
    }

    $data = array(
      'commitPHID' => $commit_phid,
    );

    if ($via !== null) {
      $data['via'] = $via;
    }

    $options = array(
      'priority' => $task_priority,
      'objectPHID' => $commit_phid,
      'containerPHID' => $repository->getPHID(),
    );

    PhabricatorWorker::scheduleTask($class, $data, $options);
  }

  final protected function getImportTaskPriority(
    PhabricatorRepository $repository,
    array $refs) {
    assert_instances_of($refs, 'PhabricatorRepositoryCommitRef');

    // If the repository is importing for the first time, we schedule tasks
    // at IMPORT priority, which is very low. Making progress on importing a
    // new repository for the first time is less important than any other
    // daemon task.

    // If the repository has finished importing and we're just catching up
    // on recent commits, we usually schedule discovery at COMMIT priority,
    // which is slightly below the default priority.

    // Note that followup tasks and triggered tasks (like those generated by
    // Herald or Harbormaster) will queue at DEFAULT priority, so that each
    // commit tends to fully import before we start the next one. This tends
    // to give imports fairly predictable progress. See T11677 for some
    // discussion.

    if ($repository->isImporting()) {
      $this->log(
        pht(
          'Importing %s commit(s) at low priority ("PRIORITY_IMPORT") '.
          'because this repository is still importing.',
          phutil_count($refs)));

      return PhabricatorWorker::PRIORITY_IMPORT;
    }

    // See T13369. If we've discovered a lot of commits at once, import them
    // at lower priority.

    // This is mostly aimed at reducing the impact that synchronizing thousands
    // of commits from a remote upstream has on other repositories. The queue
    // is "mostly FIFO", so queueing a thousand commit imports can stall other
    // repositories.

    // In a perfect world we'd probably give repositories round-robin queue
    // priority, but we don't currently have the primitives for this and there
    // isn't a strong case for building them.

    // Use "a whole lot of commits showed up at once" as a heuristic for
    // detecting "someone synchronized an upstream", and import them at a lower
    // priority to more closely approximate fair scheduling.

    if (count($refs) >= PhabricatorRepository::LOWPRI_THRESHOLD) {
      $this->log(
        pht(
          'Importing %s commit(s) at low priority ("PRIORITY_IMPORT") '.
          'because many commits were discovered at once.',
          phutil_count($refs)));

      return PhabricatorWorker::PRIORITY_IMPORT;
    }

    // Otherwise, import at normal priority.

    if ($refs) {
      $this->log(
        pht(
          'Importing %s commit(s) at normal priority ("PRIORITY_COMMIT").',
          phutil_count($refs)));
    }

    return PhabricatorWorker::PRIORITY_COMMIT;
  }


}
