<?php

/**
 * @task config Configuring Fields
 * @task error Handling Errors
 * @task io Reading and Writing Field Values
 * @task util Utility Methods
 */
abstract class PhabricatorSearchField extends Phobject {

  private $key;
  private $viewer;
  private $value;
  private $label;
  private $aliases = array();
  private $errors = array();


/* -(  Configuring Fields  )------------------------------------------------- */


  /**
   * Set the primary key for the field, like `projectPHIDs`.
   *
   * You can set human-readable aliases with @{method:setAliases}.
   *
   * The key should be a short, unique (within a search engine) string which
   * does not contain any special characters.
   *
   * @param string Unique key which identifies the field.
   * @return this
   * @task config
   */
  public function setKey($key) {
    $this->key = $key;
    return $this;
  }


  /**
   * Get the field's key.
   *
   * @return string Unique key for this field.
   * @task config
   */
  public function getKey() {
    return $this->key;
  }


  /**
   * Set a human-readable label for the field.
   *
   * This should be a short text string, like "Reviewers" or "Colors".
   *
   * @param string Short, human-readable field label.
   * @return this
   * task config
   */
  public function setLabel($label) {
    $this->label = $label;
    return $this;
  }


  /**
   * Get the field's human-readable label.
   *
   * @return string Short, human-readable field label.
   * @task config
   */
  public function getLabel() {
    return $this->label;
  }


  /**
   * Set the acting viewer.
   *
   * Engines do not need to do this explicitly; it will be done on their
   * behalf by the caller.
   *
   * @param PhabricatorUser Viewer.
   * @return this
   * @task config
   */
  public function setViewer(PhabricatorUser $viewer) {
    $this->viewer = $viewer;
    return $this;
  }


  /**
   * Get the acting viewer.
   *
   * @return PhabricatorUser Viewer.
   * @task config
   */
  public function getViewer() {
    return $this->viewer;
  }


  /**
   * Provide alternate field aliases, usually more human-readable versions
   * of the key.
   *
   * These aliases can be used when building GET requests, so you can provide
   * an alias like `authors` to let users write `&authors=alincoln` instead of
   * `&authorPHIDs=alincoln`. This is a little easier to use.
   *
   * @param list<string> List of aliases for this field.
   * @return this
   * @task config
   */
  public function setAliases(array $aliases) {
    $this->aliases = $aliases;
    return $this;
  }


  /**
   * Get aliases for this field.
   *
   * @return list<string> List of aliases for this field.
   * @task config
   */
  public function getAliases() {
    return $this->aliases;
  }


/* -(  Handling Errors  )---------------------------------------------------- */


  protected function addError($short, $long) {
    $this->errors[] = array($short, $long);
    return $this;
  }

  public function getErrors() {
    return $this->errors;
  }

  protected function validateControlValue($value) {
    return;
  }

  protected function getShortError() {
    $error = head($this->getErrors());
    if ($error) {
      return head($error);
    }
    return null;
  }


/* -(  Reading and Writing Field Values  )----------------------------------- */


  public function readValueFromRequest(AphrontRequest $request) {
    $check = array_merge(array($this->getKey()), $this->getAliases());
    foreach ($check as $key) {
      if ($this->getValueExistsInRequest($request, $key)) {
        return $this->getValueFromRequest($request, $key);
      }
    }
    return $this->getDefaultValue();
  }

  protected function getValueExistsInRequest(AphrontRequest $request, $key) {
    return $request->getExists($key);
  }

  abstract protected function getValueFromRequest(
    AphrontRequest $request,
    $key);

  public function readValueFromSavedQuery(PhabricatorSavedQuery $saved) {
    $value = $saved->getParameter(
      $this->getKey(),
      $this->getDefaultValue());
    $this->value = $this->didReadValueFromSavedQuery($value);
    $this->validateControlValue($value);
    return $this;
  }

  protected function didReadValueFromSavedQuery($value) {
    return $value;
  }

  public function getValue() {
    return $this->value;
  }

  protected function getValueForControl() {
    return $this->value;
  }

  protected function getDefaultValue() {
    return null;
  }

  public function getValueForQuery($value) {
    return $value;
  }


/* -(  Rendering Controls  )------------------------------------------------- */


  protected function newControl() {
    throw new PhutilMethodNotImplementedException();
  }


  protected function renderControl() {
    // TODO: We should `setError($this->getShortError())` here, but it looks
    // terrible in the form layout.

    return $this->newControl()
      ->setValue($this->getValueForControl())
      ->setName($this->getKey())
      ->setLabel($this->getLabel());
  }

  public function appendToForm(AphrontFormView $form) {
    $form->appendControl($this->renderControl());
    return $this;
  }


/* -(  Utility Methods )----------------------------------------------------- */


  /**
   * Read a list of items from the request, in either array format or string
   * format:
   *
   *   list[]=item1&list[]=item2
   *   list=item1,item2
   *
   * This provides flexibility when constructing URIs, especially from external
   * sources.
   *
   * @param AphrontRequest  Request to read strings from.
   * @param string          Key to read in the request.
   * @return list<string>   List of values.
   * @task utility
   */
  protected function getListFromRequest(
    AphrontRequest $request,
    $key) {

    $list = $request->getArr($key, null);
    if ($list === null) {
      $list = $request->getStrList($key);
    }

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

    return $list;
  }
}
