<?php

namespace Internetgalerie\IgDatapoolFe\ViewHelpers\Form;

use TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper;
use Internetgalerie\IgDatapoolFe\Utility\FormUtility;
use TYPO3\CMS\Extbase\Property\PropertyMapper;
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
use Internetgalerie\IgDatapoolFe\Controller\ActionController;
use Exception;
use Iterator;
use Internetgalerie\IgDatapoolFe\Services\LangService;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
use TYPO3\CMS\Core\Page\PageRenderer;

/**
 * Renders an AJAX Upload Field.
 *
 * @api
 * @author     Markus Baumgartner <mb@stylesheep.ch>, Internetgalerie AG
 * @package    IgDatapoolFE
 * @subpackage ViewHelpers
 */
class UploadViewHelper extends AbstractFormFieldViewHelper implements FieldInterface
{
    use FormFieldTrait;
    /**
     * @var FormUtility
     */
    protected $formUtility;

    /**
     * @var PropertyMapper
     */
    protected $propertyMapper;

    /**
     * @var ReflectionService
     */
    protected $reflectionService;


    // remove if v12 is dropped
    protected PageRenderer $pageRenderer;

    public function injectPageRenderer(PageRenderer $pageRenderer): void
    {
        $this->pageRenderer = $pageRenderer;
    }

    /**
     * @param FormUtility $formUtility
     */
    public function injectFormUtility(FormUtility $formUtility): void
    {
        $this->formUtility = $formUtility;
    }

    /**
     * @param PropertyMapper $propertyMapper
     */
    public function injectPropertyMapper(PropertyMapper $propertyMapper): void
    {
        $this->propertyMapper = $propertyMapper;
    }

    /**
     * @param ReflectionService $reflectionService
     */
    public function injectReflectionService(ReflectionService $reflectionService): void
    {
        $this->reflectionService = $reflectionService;
    }

    /**
     * Initialize the arguments.
     *
     * @return void
     * @api
     */
    public function initializeArguments(): void
    {
        parent::initializeArguments();
        $this->registerArgument('allowedFileExt', 'string', 'List of allowed file types');
        $this->registerArgument('acceptMimeTypes', 'string', 'Mime Types, i.e. images/*. only these will be shown in the upload selector. accept="" attrib. see http://www.iana.org/assignments/media-types/media-types.xhtml.');
        $this->registerArgument('maxFileSize', 'string', 'Maximum file size in 10KB, 10MB, etc..');
        $this->registerArgument('uploadLabel', 'string', 'Label/Text for upload button', false, 'Datei auswählen...');
        $this->registerArgument('uploadClass', 'string', 'CSS class upload button', false, '');
    }

    /**
     * Renders the upload field with possible resource pointer
     *
     * @return string
     * @api
     */
    public function render(): string
    {
        $this->pageRenderer->loadJavaScriptModule('@dp/elements/upload.js');
        $this->formUtility->addJsFooter();
        $type = $this->reflectionService->getPropertyTagValues(ActionController::$currentObjectClass, array_shift(explode('.', (string) $this->arguments['property'])), 'var')[0];
        if (!str_contains((string) $type, 'ObjectStorage')) {
            throw new Exception('You must use ObjectStorage of FileReferences in your model to make the upload work with FAL! @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>');
        }

        //the current File.
        //might be an arary or objectstorage or a fileReferences or NULL.
        $value = $this->getUploadedResource();
        $line ='';

        $hideNew = '';
        if (($value !== null && !($value instanceof Iterator)) || (($value instanceof Iterator) && $value->count()>0)) {
            if ($value instanceof Iterator) {
                // File lists are not implemented yet!
                //throw new \Exception('Upload doesn\'t support StorageObjects/Arrays at the moment. please use object.myfield.0)');
                foreach ($value as $v) {
                    $line .= $this->renderFileLine($value);
                }
                //$value->rewind();
                // $value = $value->current();
            } else {
                //here.
                $line = $this->renderFileLine($value);
            }
            $hideNew = 'style="display:none"';
        }

        $uploader = $this->renderUploader();
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
        /**
         * Options for the AJAX Uploader
         *
         * @see https://github.com/LPology/Simple-Ajax-Uploader
         */
        $options = [];
        $options['url'] = $uriBuilder
                        ->reset()
                        ->setRequest($this->getRequest())
                        ->setUseCacheHash(false)->uriFor('ajaxUpload');
        $options['name'] = 'uploadFile';

        if ($this->hasArgument('maxFileSize')) {
            // in KB
            $options['maxSize'] = GeneralUtility::getBytesFromSizeMeasurement(rtrim((string) $this->arguments['maxFileSize'], 'b')) / 1024;
        }

        if ($this->hasArgument('minImageSize')) {
            // in mp
            $options['minImageSize'] = $this->arguments['minImageSize'];
        }

        if ($this->hasArgument('allowedFileExt')) {
            $options['allowedExtensions'] = GeneralUtility::trimExplode(',', $this->arguments['allowedFileExt'], true);
        }

        if ($this->hasArgument('acceptMimeTypes')) {
            $options['accept'] = $this->arguments['acceptMimeTypes'];
        }

        $labels = [];
        $labels['type_error'] = LangService::ll('validator.file.invalidType', 'ig_datapool_fe');
        $labels['size_error'] = LangService::ll('validator.file.tooBig', 'ig_datapool_fe');

        /**
         * Hidden Fields, incl. options
         */
        $inputFields = $this->renderInputFields($value, $options);

        $hiddenFields = '<input type="hidden" name="' . $this->prefixFieldName('__dpFal[]') . '" value="' . htmlspecialchars($this->arguments['property']) . '" />';

        return '<div class="dp-file" data-labels="' . htmlentities(json_encode($labels)) . '" data-options="' . htmlentities(json_encode($options)) . '">
        <div class="dp-file-line">' . $line . '</div>
        <div class="dp-file-new" ' . $hideNew . '>' . $uploader . '</div>
        <div class="dp-file-progress" style="display:none;"><div class="dp-file-progressBarWrapper"></div><div class="dp-file-percentage">0%</div></div>
        ' . $inputFields . $hiddenFields . '
        </div>';
    }

    public function renderFileLine($value)
    {
        return $this->formUtility->renderFile($value);
    }

    protected function renderUploader()
    {
        return $this->formUtility->renderUploader($this->arguments['uploadLabel'], $this->arguments['uploadClass']) . '<div class="dp-file-status"></div>';
    }

    protected function renderInputFields($currentValue, $options)
    {
        $origFilename = $fileVal = '';

        if ($currentValue instanceof FileReference) {
            // these guys might be 0, if the row hasnt been stored yet!
            if ($currentValue->getUid() != null) {
                $fileVal = htmlspecialchars(htmlspecialchars((string)$currentValue->getOriginalResource()->getUid()));
                $origFilename = (string)$currentValue->getOriginalResource()->getUid();
            } else {
                //in this case we refernce the FalFileObject and add the hash, to avoid tempering with it. origFilename stores the FAL File ref prefixed with file:
                $fileVal = htmlspecialchars(htmlspecialchars((string)$currentValue->getOriginalResource()->getOriginalFile()->getUid()));
                $origFilename = 'file:' . $currentValue->getOriginalResource()->getOriginalFile()->getUid();
            }
        } elseif ($currentValue instanceof ObjectStorage) {
            foreach ($currentValue as $value) {
                if ($value instanceof FileReference) {
                    // these guys might be 0, if the row hasnt been stored yet!
                    if ($value->getUid() != null) {
                        $fileVal = htmlspecialchars(htmlspecialchars((string)$value->getOriginalResource()->getUid()));
                        $origFilename = (string)$value->getOriginalResource()->getUid();
                    } else {
                        //in this case we refernce the FalFileObject and add the hash, to avoid tempering with it. origFilename stores the FAL File ref prefixed with file:
                        $fileVal = htmlspecialchars(htmlspecialchars((string)$value->getOriginalResource()->getOriginalFile()->getUid()));
                        $origFilename = 'file:' . $value->getOriginalResource()->getOriginalFile()->getUid();
                    }
                }
            }
        }
        $inputs = '<input type="hidden" class="curFile" name="' . $this->getName() . '[curFile]" value="' . $fileVal . '" />';
        if ($currentValue instanceof FileReference) {
            $inputs .= '<input type="hidden" class="uid" name="' . $this->getName() . '[uid]" value="' . $currentValue->getUid() . '" />';
            $inputs .= '<input type="hidden" class="remove" name="' . $this->getName() . '[remove]" value="0" />';
        }
        $inputs .= '<input type="hidden" class="origFilename" name="' . $this->getName() . '[origFilename]" value="' . $origFilename . '" />';

        $inputs .= '<input type="hidden" name="' . $this->getName() . '[options]" value="' . htmlspecialchars(htmlspecialchars((string)base64_encode(serialize($options)))) . '" />';

        $this->registerFieldNameForFormTokenGeneration($this->getName());
        return $inputs;
    }

    /**
     * Returns a previously uploaded resource.
     * If errors occurred during property mapping for this property, NULL is returned
     *
     * @return FileReference
     */
    protected function getUploadedResource()
    {
        if ($this->getMappingResultsForProperty()->hasErrors()) {
            return null;
        }
        $resource = $this->getValueAttribute(false);

        // do we get a file resource?
        if ($resource instanceof FileReference) {
            // in case it doesnt exist or there is any problem, return NULL, this is a bit ugly
            try {
                $resource->getOriginalResource()->getOriginalFile()->getContents();
            } catch (Exception) {
                return null;
            }
            return $resource;
        }
        //var_dump( $this->FieldTrait-getValueAttribute(FALSE));
        //echo('b'.get_class($resource ));exit(0);
        //maybe we already converted it before?
        if ($resource instanceof ObjectStorage) {
            // in case it doesnt exist or there is any problem, return NULL, this is a bit ugly
            return $resource;
        }
        return $this->propertyMapper->convert($resource, FileReference::class);
    }
}
