<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\Export\Formatter;

use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FormatterInterface;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;

// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff

/**
 * Create a CSV List for the export.
 */
class SpreadsheetListFormatter implements FormatterInterface
{
    protected $exportAlias;

    protected $exportData;

    /**
     * @var ExportManager
     */
    protected $exportManager;

    protected $formatterData;

    /**
     * This variable cache the labels internally.
     *
     * @var string[]
     */
    protected $labelsCache;

    protected $result;

    /**
     * @var TranslatorInterface
     */
    protected $translator;

    public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager)
    {
        $this->translator = $translatorInterface;
        $this->exportManager = $exportManager;
    }

    /**
     * build a form, which will be used to collect data required for the execution
     * of this formatter.
     *
     * @uses appendAggregatorForm
     *
     * @param string $exportAlias
     */
    public function buildForm(
        FormBuilderInterface $builder,
        $exportAlias,
        array $aggregatorAliases,
    ): void {
        $builder
            ->add('format', ChoiceType::class, [
                'choices' => [
                    'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods',
                    'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx',
                ],
                'placeholder' => 'Choose the format',
            ])
            ->add('numerotation', ChoiceType::class, [
                'choices' => [
                    'yes' => true,
                    'no' => false,
                ],
                'expanded' => true,
                'multiple' => false,
                'label' => 'Add a number on first column',
            ]);
    }

    public function getFormDefaultData(array $aggregatorAliases): array
    {
        return ['numerotation' => true, 'format' => 'xlsx'];
    }

    public function getName()
    {
        return 'Spreadsheet list formatter (.xlsx, .ods)';
    }

    /**
     * Generate a response from the data collected on differents ExportElementInterface.
     *
     * @param mixed[] $result          The result, as given by the ExportInterface
     * @param mixed[] $formatterData   collected from the current form
     * @param string  $exportAlias     the id of the current export
     * @param array   $filtersData     an array containing the filters data. The key are the filters id, and the value are the data
     * @param array   $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data
     *
     * @return Response The response to be shown
     */
    public function getResponse(
        $result,
        $formatterData,
        $exportAlias,
        array $exportData,
        array $filtersData,
        array $aggregatorsData,
    ): \Symfony\Component\HttpFoundation\Response {
        $this->result = $result;
        $this->exportAlias = $exportAlias;
        $this->exportData = $exportData;
        $this->formatterData = $formatterData;

        $spreadsheet = new Spreadsheet();
        $worksheet = $spreadsheet->getActiveSheet();

        $this->prepareHeaders($worksheet);

        $i = 1;

        foreach ($result as $row) {
            if (true === $this->formatterData['numerotation']) {
                $worksheet->setCellValue('A'.($i + 1), (string) $i);
            }

            $a = $this->formatterData['numerotation'] ? 'B' : 'A';

            foreach ($row as $key => $value) {
                $row = $a.($i + 1);

                $formattedValue = $this->getLabel($key, $value);

                if ($formattedValue instanceof \DateTimeInterface) {
                    $worksheet->setCellValue($row, Date::PHPToExcel($formattedValue));

                    if ('000000' === $formattedValue->format('His')) {
                        $worksheet->getStyle($row)
                            ->getNumberFormat()
                            ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
                    } else {
                        $worksheet->getStyle($row)
                            ->getNumberFormat()
                            ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
                    }
                } else {
                    $worksheet->setCellValue($row, $formattedValue);
                }
                ++$a;
            }

            ++$i;
        }

        switch ($this->formatterData['format']) {
            case 'ods':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods');
                $contentType = 'application/vnd.oasis.opendocument.spreadsheet';

                break;

            case 'xlsx':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
                $contentType = 'application/vnd.openxmlformats-officedocument.'
                    .'spreadsheetml.sheet';

                break;

            case 'csv':
                $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv');
                $contentType = 'text/csv';

                break;

            default:
                // this should not happen
                // throw an exception to ensure that the error is catched
                throw new \OutOfBoundsException('The format '.$this->formatterData['format'].' is not supported');
        }

        $response = new Response();
        $response->headers->set('content-type', $contentType);

        $tempfile = \tempnam(\sys_get_temp_dir(), '');
        $writer->save($tempfile);

        $f = \fopen($tempfile, 'rb');
        $response->setContent(\stream_get_contents($f));
        fclose($f);

        // remove the temp file from disk
        \unlink($tempfile);

        return $response;
    }

    public function getType()
    {
        return FormatterInterface::TYPE_LIST;
    }

    /**
     * Give the label corresponding to the given key and value.
     *
     * @param string $key
     * @param string $value
     *
     * @return string
     *
     * @throws \LogicException if the label is not found
     */
    protected function getLabel($key, $value)
    {
        if (null === $this->labelsCache) {
            $this->prepareCacheLabels();
        }

        if (!\array_key_exists($key, $this->labelsCache)) {
            throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache))));
        }

        return $this->labelsCache[$key]($value);
    }

    /**
     * Prepare the label cache which will be used by getLabel. This function
     * should be called only once in the generation lifecycle.
     */
    protected function prepareCacheLabels()
    {
        $export = $this->exportManager->getExport($this->exportAlias);
        $keys = $export->getQueryKeys($this->exportData);

        foreach ($keys as $key) {
            // get an array with all values for this key if possible
            $values = \array_map(static function ($v) use ($key) {
                if (!\array_key_exists($key, $v)) {
                    throw new \RuntimeException(sprintf('This key does not exists: %s. Available keys are %s', $key, \implode(', ', \array_keys($v))));
                }

                return $v[$key];
            }, $this->result);
            // store the label in the labelsCache property
            $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData);
        }
    }

    /**
     * add the headers to the csv file.
     */
    protected function prepareHeaders(Worksheet $worksheet)
    {
        $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData);
        // we want to keep the order of the first row. So we will iterate on the first row of the results
        $first_row = \count($this->result) > 0 ? $this->result[0] : [];
        $header_line = [];

        if (true === $this->formatterData['numerotation']) {
            $header_line[] = $this->translator->trans('Number');
        }

        foreach ($first_row as $key => $value) {
            $header_line[] = $this->translator->trans(
                $this->getLabel($key, '_header')
            );
        }

        if (\count($header_line) > 0) {
            $worksheet->fromArray($header_line, null, 'A1');
        }
    }
}
