<?php
/**
 * Copyright (C) 2022  Jaap Jansma (jaap.jansma@civicoop.org)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

namespace Civi\DataProcessor\FilterHandler;

use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\DataSpecification\FieldSpecification;
use CiviCRM_API3_Exception;
use CRM_Core_Exception;
use CRM_Core_Form;
use CRM_Dataprocessor_ExtensionUtil as E;
use CRM_Dataprocessor_Utils_Form;
use CRM_Utils_Type;
use Exception;

class ContactHasContributionInPeriod extends AbstractFieldInPeriodFilter {

  protected $financialTypeOptions = [];

  protected $statusOptions = [];

  /**
   * 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/ContactHasContributionInPeriod.tpl";
  }

  protected function getOperatorOptions(FieldSpecification $fieldSpec): array {
    return array(
      'IN' => E::ts('Has contribution in period'),
      'NOT IN' => E::ts('Does not have contribution 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->getControbitionStatusOptions(),
      'title' => $fieldSpec->title .': '.E::ts('Contribution Status'),
      'api.filter' => TRUE,
      'api.return' => FALSE,
      'type' => CRM_Utils_Type::T_INT,
    ];
    $specs["{$alias}_financial_type_ids"] = [
      'name' => "{$alias}_financial_type_ids",
      'options' => $this->getFinancialTypeOptions(),
      'title' => $fieldSpec->title .': '.E::ts('Financial Types'),
      '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->getControbitionStatusOptions(), false, [
        'style' => $minWidth,
        'class' => 'crm-select2 '.$sizeClass,
        'multiple' => TRUE,
        'placeholder' => E::ts('- Any status -'),
      ]);
      $form->add('select', "{$alias}_financial_type_ids", null, $this->getFinancialTypeOptions(), false, [
        'style' => $minWidth,
        'class' => 'crm-select2 '.$sizeClass,
        'multiple' => TRUE,
        'placeholder' => E::ts('- Any financial type -'),
      ]);

      CRM_Dataprocessor_Utils_Form::addInExcludeEntityRef($form, "{$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['financial_type_ids'])) {
      $defaults[$alias.'_financial_type_ids'] = $defaultFilterValue['financial_type_ids'];
    }
    if (isset($defaultFilterValue['campaign_ids'])) {
      $defaults[$alias.'_campaign_ids'] = $defaultFilterValue['campaign_ids'];
    }

    if (count($defaults)) {
      $form->setDefaults($defaults);
    }

    return $filter;
  }

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

  protected function getFinancialTypeOptions(): array {
    if (empty($this->financialTypeOptions)) {
      $this->financialTypeOptions = [];
      try {
        $financialTypeApi = civicrm_api3('FinancialType', 'get', [
          'options' => ['limit' => 0],
        ]);
        foreach ($financialTypeApi['values'] as $financialType) {
          $this->financialTypeOptions[$financialType['id']] = $financialType['name'];
        }
      } catch (CiviCRM_API3_Exception $e) {
      }
    }
    return $this->financialTypeOptions;
  }

  /**
   * File name of the template to add this filter to the criteria form.
   *
   * @return string
   */
  public function getTemplateFileName(): string {
    return "CRM/Dataprocessor/Form/Filter/ContactHasContributionInPeriodFilter.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.'_financial_type_ids'])) {
      $return['financial_type_ids'] = $submittedValues[$alias . '_financial_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['financial_type_ids'] = [];
    if (isset($submittedValues['financial_type_ids'])) {
      $filterParams['financial_type_ids'] = $submittedValues['financial_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) {
        $includeOp = 'IN';
        $excludeOp = 'NOT IN';
        if ($filterParams['op'] == 'NOT IN') {
          $excludeOp = 'IN';
          $includeOp = 'NOT IN';
        }
        $tableAlias = $this->getTableAlias($dataFlow);
        $fieldName = $this->inputFieldSpecification->getName();
        $fieldAlias = $this->inputFieldSpecification->alias;
        $baseSqlStatement = "
          SELECT `contact_id`
          FROM `civicrm_contribution` `c_$fieldAlias`
          WHERE 1";
        if (isset($filterParams['financial_type_ids']) && is_array($filterParams['financial_type_ids']) && count($filterParams['financial_type_ids'])) {
          $baseSqlStatement .= " AND `c_$fieldAlias`.`financial_type_id` IN (" . implode(",", $filterParams['financial_type_ids']) . ")";
        }
        $baseSqlStatement .= " AND " . $this->getDateSqlStatement('c_'.$fieldAlias, 'receive_date', $filterParams);
        $baseSqlStatement .= " AND `c_$fieldAlias`.`is_test` = 0";
        if (isset($filterParams['status_ids']) && is_array($filterParams['status_ids']) && count($filterParams['status_ids'])) {
          $baseSqlStatement .= " AND `c_$fieldAlias`.`contribution_status_id` IN (" . implode(",", $filterParams['status_ids']) . ")";
        }
        $includeCampaignIds = [];
        $excludeCampaignIds = [];
        if (isset($filterParams['campaign_ids']) && is_array($filterParams['campaign_ids']) && count($filterParams['campaign_ids'])) {
          foreach($filterParams['campaign_ids'] as $campaign_id) {
            if (strpos($campaign_id, 'exclude_') === 0) {
              $excludeCampaignIds[] = substr($campaign_id, strlen('exclude_'));
            } else {
              $includeCampaignIds[] = $campaign_id;
            }
          }
        }

        $sqlStatement = "";
        if (count($includeCampaignIds)) {
          $sqlStatement = "`$tableAlias`.`$fieldName` {$includeOp} (";
          $sqlStatement .= $baseSqlStatement;
          $sqlStatement .= " AND `c_$fieldAlias`.`campaign_id` IN (" . implode(",", $includeCampaignIds) . ")";
          $sqlStatement .= ")";
        }
        if (count($excludeCampaignIds)) {
          if (strlen($sqlStatement)) {
            $sqlStatement .= " AND ";
          }
          $sqlStatement .= "`$tableAlias`.`$fieldName` {$excludeOp} (";
          $sqlStatement .= $baseSqlStatement;
          $sqlStatement .= " AND `c_$fieldAlias`.`campaign_id` IN (" . implode(",", $excludeCampaignIds) . ")";
          $sqlStatement .= ")";
        }
        if (!strlen($sqlStatement)) {
          $sqlStatement = "`$tableAlias`.`$fieldName` {$includeOp} (";
          $sqlStatement .= $baseSqlStatement;
          $sqlStatement .= ")";
        }
        $this->whereClause = new SqlDataFlow\PureSqlStatementClause($sqlStatement, FALSE);
        $this->filterCollection->addWhere($this->whereClause);
      }
    } catch (Exception $e) {
    }
  }

}
