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

use Civi\DataProcessor\DataFlow\EndOfFlowException;
use Civi\DataProcessor\DataFlow\InvalidFlowException;
use Civi\DataProcessor\DataSpecification\DataSpecification;
use Civi\DataProcessor\DataSpecification\FieldExistsException;
use Civi\DataProcessor\Exception\DataFlowException;
use Civi\DataProcessor\Output\DirectDownloadExportOutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use Civi\DataProcessor\FileFormat\Fileformat;
use CRM_Dataprocessor_ExtensionUtil as E;

/**
 * Abstract class to reuse for spreadsheet exports. Such as CSV and Xlsx.
 */
abstract class CRM_DataprocessorOutputExport_AbstractSpreadsheet extends CRM_DataprocessorOutputExport_AbstractOutputExport implements DirectDownloadExportOutputInterface {

  /**
   * Returns the File Format class for this spreadsheet format.
   *
   * @param array $configuration
   * @return \Civi\DataProcessor\FileFormat\Fileformat
   */
  abstract protected function getFileFormatClass(array $configuration): Fileformat;

  /**
   * Returns the directory name for storing temporary files.
   *
   * @param array $configuration
   * @return String
   */
  public function getDirectory(array $configuration=[]): string {
    return 'dataprocessor_export_'.$this->getFileFormatClass($configuration)->getFileExtension();
  }

  /**
   * Returns the file extension.
   *
   * @param array $configuration
   * @return String
   */
  public function getExtension(array $configuration=[]): string {
    return $this->getFileFormatClass($configuration)->getFileExtension();
  }

  /**
   * Returns the alternate file name.
   *
   * @param array $configuration
   * @return string
   */
  protected function getAlternateFileName(array $configuration=[]):? string {
    if (isset($configuration['altfilename']) && ''!==$configuration['altfilename']) {
      return $configuration['altfilename'];
    }
    return null;
  }

  /**
   * Get the download name of the export file.
   *
   * @param $dataProcessor
   * @param $outputBAO
   *
   * @return string
   */
  public function getDownloadName($dataProcessor, $outputBAO): string {
    $configuration = [];
    if (isset($outputBAO['configuration'])) {
      $configuration = $outputBAO['configuration'];
    }
    $download_name = $this->getAlternateFileName($configuration);
    if (empty($download_name)) {
      $download_name = date('Ymdhis') . '_' . $dataProcessor['name'] . '.' . $this->getExtension($configuration);
    }
    return $download_name;
  }

  /**
   * 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 $output
   */
  public function buildConfigurationForm(CRM_Core_Form $form, $output=array()) {
    parent::buildConfigurationForm($form, $output);
    try {
      $form->add('text', 'altfilename', E::ts('Alternate File Name'), []);
    } catch (CRM_Core_Exception $e) {
    }
    $configuration = array();
    if ($output && isset($output['configuration'])) {
      $configuration = $output['configuration'];
    }
    $defaults['altfilename'] = $this->getAlternateFileName($configuration);
    $form->setDefaults($defaults);
    $this->getFileFormatClass($configuration)->buildConfigurationForm($form, $configuration);
    $form->assign('spreadsheet_file_format_configuration', $this->getFileFormatClass($configuration)->getConfigurationTemplateFileName());
  }

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


  /**
   * Process the submitted values and create a configuration array
   *
   * @param $submittedValues
   * @param array $output
   * @return array
   */
  public function processConfiguration($submittedValues, &$output): array {
    $configuration = parent::processConfiguration($submittedValues, $output);
    $this->getFileFormatClass($configuration)->processConfiguration($submittedValues, $configuration);

    if (isset($submittedValues['altfilename']) && ''!==$submittedValues['altfilename']) {
      $configuration['altfilename'] = CRM_Utils_String::munge($this->removeFileExtensionFromFileName($submittedValues['altfilename']), $configuration) . '.' . $this->getExtension($configuration);
    }
    return $configuration;
  }

  /**
   * This function is called prior to removing an output
   *
   * @param array $output
   * @return void
   */
  public function deleteOutput($output) {
    // Do nothing
  }


  /**
   * Returns the mime type of the export file.
   *
   * @param array $configuration
   * @return string
   */
  public function mimeType(array $configuration=[]): string {
    return $this->getFileFormatClass($configuration)->getMimetype();
  }

  /**
   * Returns the url for the page/form this output will show to the user
   *
   * @param array $output
   * @param array $dataProcessor
   * @return string
   */
  public function getTitleForExport($output, $dataProcessor): string {
    return E::ts('Download as Spreadsheet');
  }

  /**
   * Returns the url for the page/form this output will show to the user
   *
   * @param array $output
   * @param array $dataProcessor
   * @return string|false
   */
  public function getExportFileIcon($output, $dataProcessor):? string {
    $configuration = [];
    if (isset($output['configuration'])) {
      $configuration = $output['configuration'];
    }
    return $this->getFileFormatClass($configuration)->getExportFileIcon();
  }

  protected function createHeader($filename, AbstractProcessorType $dataProcessorClass, $configuration, $dataProcessor, $idField=null, $selectedIds=array(), $formValues=array()) {
    $fields = new DataSpecification();
    try {
      foreach ($dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputHandler) {
        $field = $outputHandler->getOutputFieldSpecification();
        $fields->addFieldSpecification($field->name, $field);
      }
    } catch (InvalidFlowException|FieldExistsException $e) {
    }

    $this->getFileFormatClass($configuration)->createHeader($fields, $filename, $configuration);
  }

  protected function exportRecords(string $filename, AbstractProcessorType $dataProcessor, array $configuration, string $idField=null, array $selectedIds=array(), array $formValues=array()) {
    $fields = new DataSpecification();
    try {
      $fields = $dataProcessor->getDataFlow()->getDataSpecification();
    } catch (InvalidFlowException|FieldExistsException $e) {
    }

    try {
      while($record = $dataProcessor->getDataFlow()->nextRecord()) {
        $rowIsSelected = true;
        if (isset($idField) && is_array($selectedIds) && count($selectedIds)) {
          $rowIsSelected = false;
          $id = $record[$idField]->rawValue;
          if (in_array($id, $selectedIds)) {
            $rowIsSelected = true;
          }
        }
        if ($rowIsSelected) {
          $row = [];
          foreach ($record as $column => $value) {
            $row[$column] = $value->formattedValue;
          }
          $this->getFileFormatClass($configuration)->addRecord($fields, $filename, $row, $configuration);
        }
      }
    } catch (EndOfFlowException|InvalidFlowException|DataFlowException $e) {
      // Do nothing
    }
    $this->getFileFormatClass($configuration)->closeFile($fields, $filename, $configuration);
  }


}
