<?php
/**
 * Copyright (C) 2023  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\FilterCollection;

use Civi\DataProcessor\DataFlow\AbstractDataFlow;
use Civi\DataProcessor\DataFlow\InMemoryDataFlow;
use Civi\DataProcessor\DataFlow\InMemoryDataFlow\AndFilter;
use Civi\DataProcessor\DataFlow\InMemoryDataFlow\FilterInterface;
use Civi\DataProcessor\DataFlow\InMemoryDataFlow\OrFilter;
use Civi\DataProcessor\DataFlow\InvalidFlowException;
use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\DataFlow\SqlDataFlow\AndClause;
use Civi\DataProcessor\DataFlow\SqlDataFlow\OrClause;
use Civi\DataProcessor\DataFlow\SqlDataFlow\WhereClauseInterface;
use Civi\DataProcessor\FilterHandler\AbstractFilterHandler;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;

class FilterCollection {

  /**
   * @var String
   */
  protected $title;

  /**
   * The conditions in this filter collection.
   * E.g. AND / OR
   *
   * @var String
   */
  protected $condition;

  /**
   * The condition for linking this connection to the previous condition.
   * E.g. AND / OR
   *
   * @var String
   */
  protected $linkCondition;

  /**
   * @var int
   */
  protected $collectionNumber;

  /**
   * @var \Civi\DataProcessor\FilterHandler\AbstractFilterHandler[]
   */
  protected $filterHandlers;

  /**
   * @var \Civi\DataProcessor\ProcessorType\AbstractProcessorType
   */
  protected $data_processor;

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

  /**
   * @var \Civi\DataProcessor\DataFlow\InMemoryDataFlow\FilterInterface
   */
  protected $filterClause;

  /**groupCondition
   * @var string
   */
  protected $description = '';

  /**
   * @var bool
   */
  protected $isCollapsed = false;

  public function __construct($collectionNumber=0, $linkCondition = 'AND', $condition='AND', $title='', $description='', bool $isCollapsed = false) {
    $this->collectionNumber = $collectionNumber;
    $this->linkCondition = $linkCondition;
    $this->condition = $condition;
    $this->title = $title;
    $this->description = $description;
    $this->isCollapsed = $isCollapsed;
  }

  public function getDescription(): ?string {
    return $this->description;
  }

  public function getFilterCollectionNumber(): int {
    return $this->collectionNumber;
  }

  public function getTitle(): string {
    return $this->title;
  }

  public function setDataProcessor(AbstractProcessorType $dataProcessor) {
    $this->data_processor = $dataProcessor;
  }

  /**
   * Initialize a data flow. And add existing filters.
   *
   * @param \Civi\DataProcessor\DataFlow\AbstractDataFlow $dataFlow
   * @return void
   */
  public function initializeDataFlow(AbstractDataFlow $dataFlow): void {
    if (!empty($this->whereClause) && $dataFlow instanceof SqlDataFlow) {
      $dataFlow->addWhereClause($this->whereClause);
    }
    if (!empty($this->filterClause) && $dataFlow instanceof InMemoryDataFlow) {
      $dataFlow->addFilter($this->filterClause);
    }
  }

  public function isCollapsed(): bool {
    foreach($this->filterHandlers as $filterHandler) {
      if ($filterHandler->isRequired()) {
        return false;
      }
    }
    return $this->isCollapsed;
  }

  /**
   * @param \Civi\DataProcessor\FilterHandler\AbstractFilterHandler $filterHandler
   */
  public function addFilterHandler(AbstractFilterHandler $filterHandler) {
    $this->filterHandlers[] = $filterHandler;
  }

  /**
   * @return \Civi\DataProcessor\FilterHandler\AbstractFilterHandler[]
   */
  public function getFilterHandlers(): array {
    if (!$this->filterHandlers || !is_array($this->filterHandlers)) {
      $this->filterHandlers = array();
    }
    return $this->filterHandlers;
  }

  /**
   * @throws \Exception
   */
  public function addFilter(FilterInterface $filter) {
    if (empty($this->filterClause)) {
      if ($this->condition == 'AND') {
        $this->filterClause = new AndFilter();
      } elseif ($this->condition == 'OR') {
        $this->filterClause = new OrFilter();
      }
      $dataFlow = $this->data_processor->getDataFlow();
      if ($dataFlow instanceof InMemoryDataFlow) {
        $dataFlow->addFilter($this->filterClause);
      }
    }
    $this->filterClause->addFilter($filter);
  }

  /**
   * @throws \Exception
   */
  public function removeFilter(FilterInterface $filter) {
    if ($this->filterClause instanceof AndFilter) {
      $this->filterClause->removeFilter($filter);
    } elseif ($this->filterClause instanceof OrFilter) {
      $this->filterClause->removeFilter($filter);
    }
  }

  public function addWhere(WhereClauseInterface $where) {
    if (empty($this->whereClause)) {
      $isOr = FALSE;
      if ($this->linkCondition == 'OR') {
        $isOr = TRUE;
      }
      if ($this->condition == 'AND') {
        $this->whereClause = new AndClause();
      } elseif ($this->condition == 'OR') {
        $this->whereClause = new OrClause();
      }
      $this->whereClause->setIsOR($isOr);
    }
    $this->whereClause->addWhereClause($where);
    try {
      $this->data_processor->getDataFlow()
        ->addWhereClause($this->whereClause);
    } catch (InvalidFlowException $e) {
    }
  }

  public function removeWhere(WhereClauseInterface $where) {
    if ($this->whereClause instanceof AndClause) {
      $this->whereClause->removeWhereClause($where);
    } elseif ($this->whereClause instanceof OrClause) {
      $this->whereClause->removeWhereClause($where);
    }
    if ($this->data_processor && $this->whereClause && !$this->whereClause->hasWhereClauses()) {
      $this->data_processor->getDataFlow()->removeWhereClause($this->whereClause);
    }
  }

  /**
   * Returns whether we should use the addWhere and removeWhere or addFilter or removeFilter functions.
   *
   * @return bool
   * @throws \Civi\DataProcessor\DataFlow\InvalidFlowException
   */
  public function useWhere(): bool {
    if ($this->data_processor->getDataFlow() instanceof SqlDataFlow) {
      return true;
    }
    return false;
  }
}
