<?php

/**
 * This is a helper class for the unit test.
 * This class emulates an action provider class as we cannot be sure
 * whether the action provider extension is installed on the test environment.
 * However we want to test the form processor and it is integrated with an action_provider service
 * so we have to emulate that service.
 *
 * This class is a stub and implements only the methods getActions and getActionByName.
 * It does contain one stub action which is an add to group action.
 *
 * We have almost recreated the action-provider extension in this file. And that only for
 * the puprose of unit testing ;-)
 */
class UnitTestActionProvider {

  private $availableActions;

  public function __construct() {
    $this->availableActions = array(
      'AddToGroup' => new UnitTestActionProvider_StubAction_AddToGroup(),
    );
  }

  /**
   * Returns all available actions
   */
  public function getActions() {
    return $this->availableActions;
  }

  /**
   * Returns an action by its name.
   *
   * @return \Civi\ActionProvider\Action\AbstractAction|null when action is not found.
   */
  public function getActionByName($name) {
    if (isset($this->availableActions[$name])) {
      return $this->availableActions[$name];
    }
    return null;
  }

  /**
   * Returns a new ParameterBag
   *
   * This function exists so we can encapsulate the creation of a ParameterBag to the provider.
   *
   * @return ParameterBagInterface
   */
  public function createParameterBag() {
    return new UnitTestActionProviderParameterBag();
  }
}

/**
 * This is a stub class which emulates the action provider container.
 */
class UnitTestActionProviderContainer {

  /**
   * @var Provider
   */
  protected $defaultProvider;

  public function __construct() {
    $this->defaultProvider = new UnitTestActionProvider();
  }

  /**
   * return Provider
   */
  public function getDefaultProvider() {
    return $this->defaultProvider;
  }

  /**
   * Returns the provider object the name of the context.
   *
   * @param string $context
   * @return Provider
   */
  public function getProviderByContext($context) {
    return $this->defaultProvider;
  }

  /**
   * Returns whether the container has already a particulair context.
   *
   * @param string $context
   * @return bool
   */
  public function hasContext($context) {
    return true;
  }

}

/**
 * This is the stub class for the action.
 */
class UnitTestActionProvider_StubAction_AddToGroup {

  private $configuration;

  private $defaultConfiguration;

  public function __construct() {
    $this->defaultConfiguration = new UnitTestActionProvider_ParameterBag();
  }

  /**
   * Returns the specification of the configuration options for the actual action.
   *
   * @return SpecificationBag
   */
  public function getConfigurationSpecification() {
    return new UnitTestActionProvider_SpecificationBag(array(
      new UnitTestActionProvider_Specification('group_id', 'Integer', 'Select group', true, null, 'Group', null, FALSE),
    ));
  }

  /**
   * Returns the specification of the parameters of the actual action.
   *
   * @return SpecificationBag
   */
  public function getParameterSpecification() {
    return new UnitTestActionProvider_SpecificationBag(array(
      new UnitTestActionProvider_Specification('contact_id', 'Integer', 'Contact ID', true)
    ));
  }

  /**
   * Returns the specification of the output parameters of this action.
   *
   * This function could be overriden by child classes.
   *
   * @return SpecificationBag
   */
  public function getOutputSpecification() {
    return new UnitTestActionProvider_SpecificationBag(array(
      new UnitTestActionProvider_Specification('contact_id', 'Integer', 'Contact ID', true)
    ));
  }

  public function setConfiguration($configuration) {
    $this->configuration = $configuration;
  }

  public function getConfiguration() {
    return $this->configuration;
  }

  /**
   * @return ParameterBag
   */
  public function getDefaultConfiguration() {
    return $this->defaultConfiguration;
  }

  public function getTitle() {
    return 'Stub Action';
  }

  public function execute($parameters) {
    civicrm_api3('GroupContact', 'create', array(
      'contact_id' => $parameters->getParameter('contact_id'),
      'group_id' => $this->configuration->getParameter('group_id'),
    ));
  }

  /**
   * Converts the object to an array.
   *
   * @return array
   */
  public function toArray() {
    $return['parameter_spec'] = $this->getParameterSpecification()->toArray();
    $return['configuration_spec'] = $this->getConfigurationSpecification()->toArray();
    $return['output_spec'] = $this->getOutputSpecification()->toArray();
    $return['default_configuration'] = $this->getDefaultConfiguration()->toArray();
    $return['name'] = 'AddToGroup';
    $return['title'] = $this->getTitle();
    return $return;
  }

}

class UnitTestActionProvider_ParameterBag implements \IteratorAggregate {

  protected $parameters = array();

  /**
   * Get the parameter.
   */
  public function getParameter($name) {
    if (isset($this->parameters[$name])) {
      return $this->parameters[$name];
    }
    return null;
  }
  /**
   * Tests whether the parameter with the name exists.
   */
  public function doesParameterExists($name) {
    if (isset($this->parameters[$name])) {
      return true;
    }
    return false;
  }

  /**
   * Sets parameter.
   */
  public function setParameter($name, $value) {
    $this->parameters[$name] = $value;
  }

  public function getIterator() {
    return new \ArrayIterator($this->parameters);
  }

  /**
   * Converts the object to an array.
   *
   * @return array
   */
  public function toArray() {
    return $this->parameters;
  }

}

class UnitTestActionProvider_SpecificationBag implements \IteratorAggregate  {

  protected $parameterSpecifications = array();

  public function __construct($specifcations = array()) {
    foreach($specifcations as $spec) {
      $this->parameterSpecifications[$spec->getName()] = $spec;
    }
  }

  /**
   * @param string $name
   *   The name of the parameter.
   * @return Specification|null
   */
  public function getSpecificationByName($name) {
    foreach($this->parameterSpecifications as $key => $spec) {
      if ($spec->getName() == $name) {
        return $this->parameterSpecifications[$key];
      }
    }
    return null;
  }

  public function getIterator() {
    return new \ArrayIterator($this->parameterSpecifications);
  }

  /**
   * Converts the object to an array.
   *
   * @return array
   */
  public function toArray() {
    $return = array();
    foreach($this->parameterSpecifications as $spec) {
      $return[] = $spec->toArray();
    }
    return $return;
  }

}


class UnitTestActionProvider_Specification {

   /**
   * @var mixed
   */
  protected $defaultValue;
  /**
   * @var string
   */
  protected $name;
  /**
   * @var string
   */
  protected $title;
  /**
   * @var string
   */
  protected $description;
  /**
   * @var bool
   */
  protected $required = FALSE;
  /**
   * @var array
   */
  protected $options = array();
  /**
   * @var bool
   */
  protected $multiple = FALSE;
  /**
   * @var string
   */
  protected $dataType;
  /**
   * @var string
   */
  protected $fkEntity;

    /**
   * @param $name
   * @param $dataType
   */
  public function __construct($name, $dataType = 'String', $title='', $required = false, $defaultValue = null, $fkEntity = null, $options = array(), $multiple = false) {
    $this->setName($name);
    $this->setDataType($dataType);
    $this->setTitle($title);
    $this->setRequired($required);
    $this->setDefaultValue($defaultValue);
    $this->setFkEntity($fkEntity);
    $this->setOptions($options);
    $this->setMultiple($multiple);
  }

  /**
   * @return mixed
   */
  public function getDefaultValue() {
    return $this->defaultValue;
  }

  /**
   * @param mixed $defaultValue
   *
   * @return $this
   */
  public function setDefaultValue($defaultValue) {
    $this->defaultValue = $defaultValue;
    return $this;
  }

  /**
   * @return string
   */
  public function getName() {
    return $this->name;
  }

  /**
   * @param string $name
   *
   * @return $this
   */
  public function setName($name) {
    $this->name = $name;
    return $this;
  }

  /**
   * @return string
   */
  public function getTitle() {
    return $this->title;
  }

  /**
   * @param string $title
   *
   * @return $this
   */
  public function setTitle($title) {
    $this->title = $title;
    return $this;
  }

  /**
   * @return string
   */
  public function getDescription() {
    return $this->description;
  }

  /**
   * @param string $description
   *
   * @return $this
   */
  public function setDescription($description) {
    $this->description = $description;
    return $this;
  }

  /**
   * @return bool
   */
  public function isRequired() {
    return $this->required;
  }

  /**
   * @param bool $required
   *
   * @return $this
   */
  public function setRequired($required) {
    $this->required = $required;
    return $this;
  }

  /**
   * @return string
   */
  public function getDataType() {
    return $this->dataType;
  }

  /**
   * @param $dataType
   *
   * @return $this
   * @throws \Exception
   */
  public function setDataType($dataType) {
    if (!in_array($dataType, $this->getValidDataTypes())) {
      throw new \Exception(sprintf('Invalid data type "%s', $dataType));
    }
    $this->dataType = $dataType;
    return $this;
  }

    /**
   * Add valid types that are not not part of \CRM_Utils_Type::dataTypes
   *
   * @return array
   */
  private function getValidDataTypes() {
    $extraTypes =  array('Boolean', 'Text', 'Float');
    $extraTypes = array_combine($extraTypes, $extraTypes);
    return array_merge(\CRM_Utils_Type::dataTypes(), $extraTypes);
  }

   /**
   * @return array
   */
  public function getOptions() {
    return $this->options;
  }

  /**
   * @param array $options
   *
   * @return $this
   */
  public function setOptions($options) {
    $this->options = $options;
    return $this;
  }

  /**
   * @param $option
   */
  public function addOption($option) {
    $this->options[] = $option;
  }

  /**
   * @return bool
   */
  public function isMultiple() {
    return $this->multiple;
  }

  /**
   * @param bool $multiple
   *
   * @return $this
   */
  public function setMultiple($multiple) {
    $this->multiple = $multiple;
    return $this;
  }

  /**
   * @return string
   */
  public function getFkEntity() {
    return $this->fkEntity;
  }

  /**
   * @param string $fkEntity
   *
   * @return $this
   */
  public function setFkEntity($fkEntity) {
    $this->fkEntity = $fkEntity;
    return $this;
  }

  public function toArray() {
    $ret = array();
    foreach (get_object_vars($this) as $key => $val) {
      $key = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $key));
      $ret[$key] = $val;
    }
    return $ret;
  }

}
