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

namespace Civi\DataProcessor\FilterHandler;

use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\DataSpecification\FieldSpecification;
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_Utils_Type;
use Exception;

class ContactHasActivityInPeriodFilter extends AbstractFieldInPeriodFilter {

  protected $statusOptions = [];

  protected $typeOptions = [];

  protected $contactRecordTypeOptions = [];

  public function __construct() {
    parent::__construct();
  }

  /**
   * 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']);
  }

  /**
   * Returns true when this filter has additional configuration
   *
   * @return bool
   */
  public function hasConfiguration(): bool {
    return true;
  }

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

  /**
   * When this filter type has configuration specify the template file name
   * for the configuration form.
   *
   * @return false|string
   */
  public function getConfigurationTemplateFileName():? string {
    return "CRM/Dataprocessor/Form/Filter/Configuration/ContactHasActivityInPeriodFilter.tpl";
  }

  protected function getOperatorOptions(FieldSpecification $fieldSpec): array {
    return array(
      'IN' => E::ts('Has activity in period'),
      'NOT IN' => E::ts('Does not have an activity in period'),
    );
  }

  /**
   * Add addition filter fields to the API specs.
   *
   * @param array $specs
   *
   * @return array
   */
  public function enhanceApi3FieldSpec(array $specs): array {
    $fieldSpec = $this->getFieldSpecification();
    $alias = $fieldSpec->alias;
    $specs["{$alias}_status_ids"] = [
      'name' => "{$alias}_status_ids",
      'options' => $this->getStatusOptions(),
      'title' => $fieldSpec->title .': '.E::ts('Activity Status'),
      'api.filter' => TRUE,
      'api.return' => FALSE,
      'type' => CRM_Utils_Type::T_INT,
    ];
    $specs["{$alias}_type_ids"] = [
      'name' => "{$alias}_type_ids",
      'options' => $this->getTypeOptions(),
      'title' => $fieldSpec->title .': '.E::ts('Activity Types'),
      'api.filter' => TRUE,
      'api.return' => FALSE,
      'type' => CRM_Utils_Type::T_INT,
    ];
    $specs["{$alias}_record_type_ids"] = [
      'name' => "{$alias}_record_type_ids",
      'options' => $this->getContactRecordTypeOptions(),
      'title' => $fieldSpec->title .': '.E::ts('Activity Contact Record Type'),
      'api.filter' => TRUE,
      'api.return' => FALSE,
      'type' => CRM_Utils_Type::T_INT,
    ];
    $specs["{$alias}_campaign_ids"] = [
      'name' => "{$alias}_campaign_ids",
      'title' => $fieldSpec->title .': '.E::ts('Campaign IDs'),
      'api.filter' => TRUE,
      'api.return' => FALSE,
      'type' => CRM_Utils_Type::T_INT,
    ];
    return $specs;
  }


  /**
   * Add the elements to the filter form.
   *
   * @param \CRM_Core_Form $form
   * @param array $defaultFilterValue
   * @param string $size
   *   Possible values: full or compact
   * @return array
   *   Return variables belonging to this filter.
   */
  public function addToFilterForm(CRM_Core_Form $form, $defaultFilterValue, $size='full'): array {
    $filter = parent::addToFilterForm($form, $defaultFilterValue, $size);
    $fieldSpec = $this->getFieldSpecification();
    $alias = $fieldSpec->alias;
    $sizeClass = $this->getSizeClass($size);
    $minWidth = $this->getMinWidth($size);

    try {
      $form->add('select', "{$alias}_status_ids", null, $this->getStatusOptions(), false, [
        'style' => $minWidth,
        'class' => 'crm-select2 '.$sizeClass,
        'multiple' => TRUE,
        'placeholder' => E::ts('- Any status -'),
      ]);
      $form->add('select', "{$alias}_type_ids", null, $this->getTypeOptions(), false, [
        'style' => $minWidth,
        'class' => 'crm-select2 '.$sizeClass,
        'multiple' => TRUE,
        'placeholder' => E::ts('- Any type -'),
      ]);

      $form->add('select', "{$alias}_record_type_ids", null, $this->getContactRecordTypeOptions(), false, [
        'style' => $minWidth,
        'class' => 'crm-select2 '.$sizeClass,
        'multiple' => TRUE,
        'placeholder' => E::ts('- Any contact record -'),
      ]);

      $form->addEntityRef("{$alias}_campaign_ids", E::ts('Campaign'), [
        'entity' => 'Campaign',
        'select' => ['minimumInputLength' => 0],
        'class' => $sizeClass,
        'placeholder' => E::ts('- Any campaign -'),
        'multiple' => TRUE,
      ]);
    } catch (CRM_Core_Exception $e) {
    }

    $defaults = [];
    if (isset($defaultFilterValue['status_ids'])) {
      $defaults[$alias.'_status_ids'] = $defaultFilterValue['status_ids'];
    }
    if (isset($defaultFilterValue['type_ids'])) {
      $defaults[$alias.'_type_ids'] = $defaultFilterValue['type_ids'];
    }
    if (isset($defaultFilterValue['record_type_ids'])) {
      $defaults[$alias.'_record_type_ids'] = $defaultFilterValue['record_type_ids'];
    } else {
      $defaults[$alias.'_record_type_ids'] = [3]; // Targets.
    }
    if (isset($defaultFilterValue['campaign_ids'])) {
      $defaults[$alias.'_campaign_ids'] = $defaultFilterValue['campaign_ids'];
    }
    if (count($defaults)) {
      $form->setDefaults($defaults);
    }
    return $filter;
  }

  protected function getStatusOptions(): array {
    if (empty($this->statusOptions)) {
      $this->statusOptions = [];
      try {
        $activityStatusApi = civicrm_api3('OptionValue', 'get', [
          'option_group_id' => 'activity_status',
          'options' => ['limit' => 0],
        ]);
        foreach ($activityStatusApi['values'] as $option) {
          $this->statusOptions[$option['value']] = $option['label'];
        }
      } catch (CiviCRM_API3_Exception $e) {
      }
    }
    return $this->statusOptions;
  }

  protected function getTypeOptions(): array {
    if (empty($this->typeOptions)) {
      $this->typeOptions = [];
      try {
        $activityTypeApi = civicrm_api3('OptionValue', 'get', [
          'option_group_id' => 'activity_type',
          'options' => ['limit' => 0],
        ]);
        foreach ($activityTypeApi['values'] as $option) {
          $this->typeOptions[$option['value']] = $option['label'];
        }
      } catch (CiviCRM_API3_Exception $e) {
      }
    }
    return $this->typeOptions;
  }

  protected function getContactRecordTypeOptions(): array {
    if (empty($this->contactRecordTypeOptions)) {
      $this->contactRecordTypeOptions = [];
      try {
        $activityContactsApi = civicrm_api3('OptionValue', 'get', [
          'option_group_id' => 'activity_contacts',
          'options' => ['limit' => 0],
        ]);
        foreach ($activityContactsApi['values'] as $option) {
          $this->contactRecordTypeOptions[$option['value']] = $option['label'];
        }
      } catch (CiviCRM_API3_Exception $e) {
      }
    }
    return $this->contactRecordTypeOptions;
  }

  /**
   * File name of the template to add this filter to the criteria form.
   *
   * @return string
   */
  public function getTemplateFileName(): string {
    return "CRM/Dataprocessor/Form/Filter/ContactHasActivityInPeriodFilter.tpl";
  }

  /**
   * Process the submitted values to a filter value
   * Which could then be processed by applyFilter function
   *
   * @param $submittedValues
   * @return array
   */
  public function processSubmittedValues($submittedValues): array {
    $return = parent::processSubmittedValues($submittedValues);
    $filterSpec = $this->getFieldSpecification();
    $alias = $filterSpec->alias;
    if (isset($submittedValues[$alias.'_status_ids'])) {
      $return['status_ids'] = $submittedValues[$alias . '_status_ids'];
    }
    if (isset($submittedValues[$alias.'_type_ids'])) {
      $return['type_ids'] = $submittedValues[$alias . '_type_ids'];
    }
    if (isset($submittedValues[$alias.'_record_type_ids'])) {
      $return['record_type_ids'] = $submittedValues[$alias . '_record_type_ids'];
    }
    if (isset($submittedValues[$alias.'_campaign_ids']) && !empty($submittedValues[$alias.'_campaign_ids'])) {
      $return['campaign_ids'] = explode(',', $submittedValues[$alias . '_campaign_ids']);
    }
    return $return;
  }

  /**
   * Extend the filter params from the submitted values.
   *
   * @param array $submittedValues
   * @param array $filterParams
   *
   * @return array
   */
  public function extendFilterParamsFromSubmittedValues(array $submittedValues, array $filterParams): array {
    $filterParams['status_ids'] = [];
    if (isset($submittedValues['status_ids'])) {
      $filterParams['status_ids'] = $submittedValues['status_ids'];
    }
    $filterParams['type_ids'] = [];
    if (isset($submittedValues['type_ids'])) {
      $filterParams['type_ids'] = $submittedValues['type_ids'];
    }
    if (isset($submittedValues['record_type_ids'])) {
      $filterParams['record_type_ids'] = $submittedValues['record_type_ids'];
    }
    if (isset($submittedValues['campaign_ids'])) {
      $filterParams['campaign_ids'] = $submittedValues['campaign_ids'];
    }
    return $filterParams;
  }

  /**
   * @param array $filterParams
   *   The filter settings
   */
  public function setFilter($filterParams) {
    try {
      $this->resetFilter();
      $dataFlow  = $this->dataSource->ensureField($this->inputFieldSpecification);
      if ($dataFlow instanceof SqlDataFlow) {
        $tableAlias = $this->getTableAlias($dataFlow);
        $fieldName = $this->inputFieldSpecification->getName();
        $fieldAlias = $this->inputFieldSpecification->alias;

        $select = "SELECT `contact_id`";
        $from = "
          FROM `civicrm_activity` `activity_$fieldAlias`
          INNER JOIN `civicrm_activity_contact` `activity_contact_$fieldAlias` ON `activity_$fieldAlias`.`id` = `activity_contact_$fieldAlias`.`activity_id`
        ";
        $where = "WHERE 1";
        if (isset($filterParams['status_ids']) && is_array($filterParams['status_ids']) && count($filterParams['status_ids'])) {
          $where .= " AND `activity_$fieldAlias`.`status_id` IN (" . implode(",", $filterParams['status_ids']) . ")";
        }
        if (isset($filterParams['type_ids']) && is_array($filterParams['type_ids']) && count($filterParams['type_ids'])) {
          $where .= " AND `activity_$fieldAlias`.`activity_type_id` IN (" . implode(",", $filterParams['type_ids']) . ")";
        }
        if (isset($filterParams['record_type_ids']) && is_array($filterParams['record_type_ids']) && count($filterParams['record_type_ids'])) {
          $where .= " AND `activity_contact_$fieldAlias`.`record_type_id` IN (" . implode(",", $filterParams['record_type_ids']) . ")";
        }
        if (isset($filterParams['campaign_ids']) && is_array($filterParams['campaign_ids']) && count($filterParams['campaign_ids'])) {
          $where .= " AND `activity_$fieldAlias`.`campaign_id` IN (" . implode(",", $filterParams['campaign_ids']) . ")";
        }
        $where .= " AND " . $this->getDateSqlStatement('activity_'.$fieldAlias, 'activity_date_time', $filterParams);

        $subQueryStatement = $this->getSubQueryStatement($select, $from, $where, $filterParams);
        $this->whereClause = new SqlDataFlow\PureSqlStatementClause("`$tableAlias`.`$fieldName` {$filterParams['op']} ($subQueryStatement)", FALSE);
        $this->filterCollection->addWhere($this->whereClause);
      }
    } catch (Exception $e) {
    }
  }

  /**
   * Returns the subquery statement.
   *
   * This function can be overridden in child classes to alter the subquery.
   *
   * @param string $select
   * @param string $from
   * @param string $where
   * @param array $filterParams
   *
   * @return string
   */
  protected function getSubQueryStatement(string $select, string $from, string $where, array $filterParams): string {
    return trim($select) . " " . trim($from) . " " . trim($where);
  }

}
