<?php

/*
 * This file is part of the Kimai time-tracking app.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Form\Type;

use App\Form\Model\DateRange;
use App\Utils\LocaleSettings;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Custom form field type to select a date range
 */
class DateRangeType extends AbstractType
{
    public const DATE_SPACER = ' - ';

    private $localeSettings;

    public function __construct(LocaleSettings $localeSettings)
    {
        $this->localeSettings = $localeSettings;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $pickerFormat = $this->localeSettings->getDatePickerFormat();
        $dateFormat = $this->localeSettings->getDateFormat();

        $resolver->setDefaults([
            'timezone' => date_default_timezone_get(),
            'label' => 'label.daterange',
            'format' => $dateFormat,
            'separator' => self::DATE_SPACER,
            'format_picker' => $pickerFormat,
            'allow_empty' => true,
        ]);
    }

    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $view->vars['attr'] = array_merge($view->vars['attr'], [
            'autocomplete' => 'off',
            'placeholder' => strtoupper($options['format_picker']) . $options['separator'] . strtoupper($options['format_picker']),
            'data-format' => $options['format_picker'],
            'data-separator' => $options['separator'],
        ]);
    }

    /**
     * A better way would be to use the Intl NumberFormatter, but if that is not available
     * and the Symfony polyfill is used, this method would not work properly.
     *
     * @param string $string
     * @return string
     */
    protected function convertArabicPersian($string)
    {
        return strtr(
            $string,
            [
                '۰' => '0',
                '۱' => '1',
                '۲' => '2',
                '۳' => '3',
                '۴' => '4',
                '۵' => '5',
                '۶' => '6',
                '۷' => '7',
                '۸' => '8',
                '۹' => '9',
                '٠' => '0',
                '١' => '1',
                '٢' => '2',
                '٣' => '3',
                '٤' => '4',
                '٥' => '5',
                '٦' => '6',
                '٧' => '7',
                '٨' => '8',
                '٩' => '9'
            ]
        );
    }

    protected function formatToPattern(string $format, string $separator)
    {
        $format = preg_quote($format, '/');

        $pattern = str_replace('d', '[0-9]{2}', $format);
        $pattern = str_replace('m', '[0-9]{2}', $pattern);
        $pattern = str_replace('Y', '[0-9]{4}', $pattern);

        return '/^' . $pattern . $separator . $pattern . '$/';
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $formatDate = $options['format'];
        $separator = $options['separator'];
        $allowEmpty = $options['allow_empty'];
        $timezone = new \DateTimeZone($options['timezone']);
        $pattern = $this->formatToPattern($formatDate, $separator);

        $builder->addModelTransformer(new CallbackTransformer(
            function ($range) use ($formatDate, $separator) {
                if (null === $range) {
                    return '';
                }

                if (!($range instanceof DateRange)) {
                    throw new \InvalidArgumentException('Invalid DateRange given');
                }

                if (null === $range->getBegin()) {
                    return '';
                }

                $display = $range->getBegin()->format($formatDate);
                if (null !== $range->getEnd()) {
                    $display .= $separator . $range->getEnd()->format($formatDate);
                }

                return $display;
            },
            function ($dates) use ($formatDate, $pattern, $separator, $allowEmpty, $timezone) {
                $range = new DateRange();

                if (empty($dates) && $allowEmpty) {
                    return $range;
                }

                $dates = $this->convertArabicPersian($dates);

                if (preg_match($pattern, $dates) !== 1) {
                    throw new TransformationFailedException('Invalid date range given');
                }

                $values = explode($separator, $dates);

                if (\count($values) !== 2) {
                    throw new TransformationFailedException('Invalid date range given');
                }

                $begin = \DateTime::createFromFormat($formatDate, $values[0], $timezone);
                if ($begin === false) {
                    throw new TransformationFailedException('Invalid begin date given');
                }
                $range->setBegin($begin);

                $end = \DateTime::createFromFormat($formatDate, $values[1], $timezone);
                if ($end === false) {
                    throw new TransformationFailedException('Invalid end date given');
                }
                $range->setEnd($end);

                if ($begin->getTimestamp() > $end->getTimestamp()) {
                    throw new TransformationFailedException('Begin date must be before end date');
                }

                return $range;
            }
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return TextType::class;
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'daterange';
    }
}
