<?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\FileFormat;

use Civi\DataProcessor\DataSpecification\DataSpecification;
use CRM_Core_Exception;
use CRM_Core_Form;
use CRM_Dataprocessor_ExtensionUtil as E;

class CSVFileFormat implements Fileformat {

  /**
   * @var int|null
   */
  private $filePointer;

  /**
   * @return string
   */
  public function getMimetype(): string {
    return 'text/csv';
  }

  /**
   * @return string
   */
  public function getFileExtension(): string {
    return 'csv';
  }

  /**
   * @return string
   */
  public function getExportFileIcon(): string {
    return '<i class="fa fa-file-excel-o">&nbsp;</i>';
  }


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

  /**
   * When this file format has additional configuration you can add
   * the fields on the form with this function.
   *
   * @param \CRM_Core_Form $form
   * @param array $configuration
   */
  public function buildConfigurationForm(CRM_Core_Form $form, array $configuration = []) {
    try {
      $form->add('text', 'delimiter', E::ts('Delimiter'), [], TRUE);
      $form->add('text', 'enclosure', E::ts('Enclosure'), array(), true);
      $form->add('text', 'escape_char', E::ts('Escape Character'), array(), true);
    } catch (CRM_Core_Exception $e) {
    }
    if ($configuration && isset($configuration['delimiter']) && $configuration['delimiter']) {
      $defaults['delimiter'] = $configuration['delimiter'];
    } else {
      $defaults['delimiter'] = ',';
    }
    if ($configuration && isset($configuration['enclosure']) && $configuration['enclosure']) {
      $defaults['enclosure'] = $configuration['enclosure'];
    } else {
      $defaults['enclosure'] = '"';
    }
    if ($configuration && isset($configuration['escape_char']) && $configuration['escape_char']) {
      $defaults['escape_char'] = $configuration['escape_char'];
    } else {
      $defaults['escape_char'] = '\\';
    }
    $form->setDefaults($defaults);
  }

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

  /**
   * Process the submitted values and create a configuration array
   *
   * @param $submittedValues
   * @param array $configuration
   */
  public function processConfiguration($submittedValues, array &$configuration) {
    $configuration['delimiter'] = $submittedValues['delimiter'];
    $configuration['enclosure'] = $submittedValues['enclosure'];
    $configuration['escape_char'] = $submittedValues['escape_char'];
  }

  /**
   * Create a header
   *
   * @param \Civi\DataProcessor\DataSpecification\DataSpecification $fieldSpecification
   * @param string $filename
   * @param array $configuration
   */
  public function createHeader(DataSpecification $fieldSpecification, string $filename, array $configuration) {
    $file = fopen($filename. '.'.$this->getFileExtension(), 'w');
    fwrite($file, "\xEF\xBB\xBF"); // BOF this will make sure excel opens the file correctly.
    $headerLine = array();
    foreach ($fieldSpecification->getFields() as $field) {
      $headerLine[] = $this->encodeValue($field->title, $configuration['escape_char'], $configuration['enclosure']);
    }
    fwrite($file, implode($configuration['delimiter'], $headerLine)."\r\n");
    fclose($file);
  }

  /**
   * Create a footer
   *
   * @param \Civi\DataProcessor\DataSpecification\DataSpecification $fieldSpecification
   * @param string $filename
   * @param array $configuration
   */
  public function createFooter(DataSpecification $fieldSpecification, string $filename, array $configuration) {
    // Nothing needed here.
  }

  /**
   * Add a record to the file
   *
   * @param \Civi\DataProcessor\DataSpecification\DataSpecification $fieldSpecification
   * @param string $filename
   * @param array $record
   * @param array $configuration
   */
  public function addRecord(DataSpecification $fieldSpecification, string $filename, array $record, array $configuration) {
    if (!$this->filePointer) {
      $this->filePointer = fopen($filename . '.' . $this->getFileExtension(), 'a');
    }
    if ($this->filePointer) {
      $row = [];
      foreach($record as $value) {
        $row[] = $this->encodeValue($value, $configuration['escape_char'], $configuration['enclosure']);
      }
      fwrite($this->filePointer, implode($configuration['delimiter'], $row) . "\r\n");
    }
  }

  /**
   * Close a file
   *
   * @param \Civi\DataProcessor\DataSpecification\DataSpecification $fieldSpecification
   * @param string $filename
   * @param array $configuration
   */
  public function closeFile(DataSpecification $fieldSpecification, string $filename, array $configuration) {
    if ($this->filePointer) {
      fclose($this->filePointer);
    }
  }


  protected function encodeValue($value, $escape, $enclosure): string {
    ///remove any ESCAPED double quotes within string.
    $value = str_replace("$escape$enclosure","$enclosure",$value);
    //then force escape these same double quotes And Any UNESCAPED Ones.
    $value = str_replace("$enclosure","$escape$enclosure",$value);
    //force wrap value in quotes and return
    return "$enclosure$value$enclosure";
  }


}
