<?php
/**
 * @author Jaap Jansma <jaap.jansma@civicoop.org>
 * @license AGPL-3.0
 */

namespace Civi\DataProcessor\FilterHandler;

use Civi\DataProcessor\DataFlow\InMemoryDataFlow;
use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\DataSpecification\FieldSpecification;
use Civi\DataProcessor\Exception\DataSourceNotFoundException;
use Civi\DataProcessor\Exception\FieldNotFoundException;
use Civi\DataProcessor\Exception\InvalidConfigurationException;
use CiviCRM_API3_Exception;
use CRM_Core_Exception;
use CRM_Core_Form;
use CRM_Dataprocessor_ExtensionUtil as E;
use CRM_Dataprocessor_Utils_DataSourceFields;
use Exception;

abstract class AbstractFieldFilterHandler extends AbstractFilterHandler {

  /**
   * @var \Civi\DataProcessor\DataSpecification\FieldSpecification
   */
  protected $fieldSpecification;

  /**
   * @var \Civi\DataProcessor\DataSpecification\FieldSpecification
   */
  protected $inputFieldSpecification;

  /**
   * @var \Civi\DataProcessor\Source\SourceInterface
   */
  protected $dataSource;

  /**
   * @var \Civi\DataProcessor\DataFlow\SqlDataFlow\WhereClauseInterface
   */
  protected $whereClause;


  /**
   * @var \Civi\DataProcessor\DataFlow\InMemoryDataFlow\SimpleFilter
   */
  protected $filterClass;

  /**
   * Initialize the filter
   *
   * @throws \Civi\DataProcessor\Exception\DataSourceNotFoundException
   * @throws \Civi\DataProcessor\Exception\InvalidConfigurationException
   * @throws \Civi\DataProcessor\Exception\FieldNotFoundException
   */
  protected function doInitialization() {
    if (!isset($this->configuration['datasource']) || !isset($this->configuration['field'])) {
      throw new InvalidConfigurationException(E::ts("Filter %1 requires a field to filter on. None given.", array(1=>$this->title)));
    }
    $this->initializeField($this->configuration['datasource'], $this->configuration['field']);
  }

  /**
   * @param $datasource_name
   * @param $field_name
   *
   * @throws \Civi\DataProcessor\Exception\DataSourceNotFoundException
   * @throws \Civi\DataProcessor\Exception\FieldNotFoundException
   */
  protected function initializeField($datasource_name, $field_name) {
    $this->dataSource = $this->data_processor->getDataSourceByName($datasource_name);
    if (!$this->dataSource) {
      throw new DataSourceNotFoundException(E::ts("Filter %1 requires data source '%2' which could not be found. Did you rename or delete the data source?", array(1=>$this->title, 2=>$datasource_name)));
    }
    $this->inputFieldSpecification = $this->dataSource->getAvailableFilterFields()->getFieldSpecificationByAlias($field_name);
    if (!$this->inputFieldSpecification) {
      $this->inputFieldSpecification = $this->dataSource->getAvailableFilterFields()->getFieldSpecificationByName($field_name);
    }
    if (!$this->inputFieldSpecification) {
      throw new FieldNotFoundException(E::ts("Filter %1 requires a field with the name '%2' in the data source '%3'. Did you change the data source type?", array(
        1 => $this->title,
        2 => $field_name,
        3 => $datasource_name
      )));
    }
    $this->fieldSpecification = clone $this->inputFieldSpecification;
    $this->fieldSpecification->alias = $this->alias;
    $this->fieldSpecification->title = $this->title;
  }


  /**
   * @return \Civi\DataProcessor\DataSpecification\FieldSpecification
   */
  public function getFieldSpecification(): FieldSpecification {
    return $this->fieldSpecification;
  }

  /**
   * Resets the filter
   *
   * @return void
   * @throws \Exception
   */
  public function resetFilter() {
    if (!$this->isInitialized()) {
      return;
    }
    $dataFlow  = $this->dataSource->ensureField($this->fieldSpecification);
    if ($dataFlow instanceof SqlDataFlow && isset($this->whereClause) && $this->filterCollection) {
      $this->filterCollection->removeWhere($this->whereClause);
      unset($this->whereClause);
    } elseif ($dataFlow instanceof InMemoryDataFlow && $this->filterClass && $this->filterCollection) {
      $this->filterCollection->removeFilter($this->filterClass);
      unset($this->filterClass);
    }
  }

  /**
   * @param array $filterParams
   *   The filter settings
   * @throws \Exception
   */
  public function setFilter($filterParams) {
    $this->resetFilter();
    $dataFlow  = $this->dataSource->ensureField($this->inputFieldSpecification);
    if ($dataFlow instanceof SqlDataFlow) {
      $value = $filterParams['value'];
      if (!is_array($value)) {
        $value = explode(",", $value);
      }
      $tableAlias = $this->getTableAlias($dataFlow);
      $this->whereClause = new SqlDataFlow\SimpleWhereClause($tableAlias, $this->inputFieldSpecification->getName(), $filterParams['op'], $value, $this->inputFieldSpecification->type);
      $this->filterCollection->addWhere($this->whereClause);
    } elseif ($dataFlow instanceof InMemoryDataFlow) {
      $this->filterClass = new InMemoryDataFlow\SimpleFilter($this->inputFieldSpecification->getName(), $filterParams['op'], $filterParams['value']);
      $this->filterCollection->addFilter($this->filterClass);
    }
  }

  /**
   * When this filter type has additional configuration you can add
   * the fields on the form with this function.
   *
   * @param \CRM_Core_Form $form
   * @param array $filter
   */
  public function buildConfigurationForm(CRM_Core_Form $form, $filter=array()) {
    $fieldSelect = [];
    try {
      $fieldSelect = CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFilterFieldsInDataSources($filter['data_processor_id']);
    } catch (Exception $e) {
    }

    try {
      $form->add('select', 'field', $this->getFieldTitle(), $fieldSelect, TRUE, [
        'style' => 'min-width:250px',
        'class' => 'crm-select2 huge',
        'placeholder' => E::ts('- select -'),
      ]);
    } catch (CRM_Core_Exception $e) {
    }

    if (isset($filter['configuration'])) {
      $configuration = $filter['configuration'];
      $defaults = array();
      if (isset($configuration['field']) && isset($configuration['datasource'])) {
        try {
          $defaults['field'] = CRM_Dataprocessor_Utils_DataSourceFields::getSelectedFieldValue($filter['data_processor_id'], $configuration['datasource'], $configuration['field']);
        } catch (CiviCRM_API3_Exception $e) {
        }
      }
      $form->setDefaults($defaults);
    }
  }

  /**
   * Process the submitted values and create a configuration array
   *
   * @param $submittedValues
   * @return array
   */
  public function processConfiguration($submittedValues): array {
    [$datasource, $field] = explode('::', $submittedValues['field'], 2);
    $configuration['field'] = $field;
    $configuration['datasource'] = $datasource;
    return $configuration;
  }

  protected function getFieldTitle(): string {
    return E::ts('Field');
  }

}
