<?php

namespace Mnv\Core\Uploads;

use Mnv\Core\Config;
use Mnv\Core\Filesystem\Filesystem;
use Mnv\Core\Filesystem\FilesystemFactory;
use Mnv\Core\Locale\I18N;
use Symfony\Component\HttpFoundation\File\UploadedFile as SymfonyUploadedFile;

/**
 * Class Uploader
 * @package Mnv\Core\Uploads
 */
class Uploader extends Filesystem
{

    const IMAGETYPE_GIF = 1;
    const IMAGETYPE_JPEG = 2;
    const IMAGETYPE_PNG = 3;
    const IMAGETYPE_WEBP = 4;

    protected $config = [];
    protected $options = [];

    /** @var array  */
    public array $response = [];

    /** @var SymfonyUploadedFile|mixed  */
    protected SymfonyUploadedFile $file;

    /** @var ImageGenerator|ImagineGenerator */
    protected $generator;

    /** @var array  */
    private array $uploadable = [];

//    protected $error_messages = array(
//        1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
//        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
//        3 => 'The uploaded file was only partially uploaded',
//        4 => 'No file was uploaded',
//        6 => 'Missing a temporary folder',
//        7 => 'Failed to write file to disk',
//        8 => 'A PHP extension stopped the file upload',
//        'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini', // Загруженный файл превышает параметр post_max_size или upload_max_filesize в php.ini
//        'max_file_size' => 'File is too big',
//        'min_file_size' => 'File is too small',
//        'accept_file_types'     => 'Filetype not allowed',
//        'max_number_of_files'   => 'Maximum number of files exceeded',
//        'max_width'     => 'Image exceeds maximum width',
//        'min_width'     => 'Image requires a minimum width',
//        'max_height'    => 'Image exceeds maximum height',
//        'min_height'    => 'Image requires a minimum height',
//        'abort'         => 'File upload aborted',
//        'image_resize'  => 'Failed to resize image'
//    );

    public function __construct($options, $realPath, $path, $managerId)
    {

        $this->options = include __DIR__ . '/config/config.inc.php';
        if (!empty($options)) {
            $this->options = $options + $this->options;
        }

        $this->options['max_file_size'] = Config::getValue('max_file_size') * 1024 * 1024;
        $this->options['max_up_size']   = Config::getValue('max_up_size') * 1024 * 1024;

        $this->realPath = $realPath;
        $this->path     = $path;

        // TODO: доработать `$this->request->file()`
        if (request()->hasFile('file')) {
            $this->file = request()->files->get('file', '');

//            print_r($this->file);
            $this->uploadable['name']      = $this->file->getClientOriginalName();
            $this->uploadable['tmp_name']  = $this->file->getPathname();
            $this->uploadable['mimeType']  = $this->file->getClientMimeType();
            $this->uploadable['size']      = $this->file->getSize();
            $this->uploadable['extension'] = $this->file->getClientOriginalExtension();
            $this->uploadable['error']     = $this->file->getError();
        }

        if (Config::getValue('image_generation') == 'imagine') {
            $this->generator = FilesystemFactory::imagineGenerator($this->realPath, $this->path, $managerId);
        } else {
            $this->generator = FilesystemFactory::imageGenerator($this->realPath, $this->path, $managerId);
        }

    }

    /**
     * Валидация загружаемого файла
     * @return bool
     */
    public function validate(): bool
    {
        if (!empty($this->uploadable['error'])) {
            $this->response = array('status' => 500, 'message' => $this->uploadable['error'], 'error' => $this->uploadable['error'], 'type' => 'error');
            return false;
        }

        $content_length = $this->fix_integer_overflow((int)$this->get_server_var('CONTENT_LENGTH'));
        $post_max_size = $this->get_config_bytes(\ini_get('post_max_size'));
        $upload_max_filesize = $this->get_config_bytes(\ini_get('upload_max_filesize'));

        if (($post_max_size && ($content_length > $post_max_size)) || ($upload_max_filesize && ($content_length > $upload_max_filesize))) {
            $this->response = array('status' => 500, 'message' => lang('fileManager:errors:21'), 'error' => 'upload_max_filesize', 'type' => 'error');
            return false;
        }

        if (!empty($this->file)) {

            if (!$this->uploadable['size']) {
                /** Загружаемый файл пустой, либо к нему невозможно получить доступ. */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:13'), 'error' => 'file_size_empty', 'type' => 'error');
                return false;
            }
            /** Загруженный файл превышает параметр MAX_FILE_SIZE, указанный в HTML-форме */
            if ($this->options['max_file_size'] && $this->uploadable['size'] > ($this->options['max_file_size'])) {
                /** Слишком большой размер файла */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:26'), 'error' => 'max_file_size', 'type' => 'error');
                return false;
            }

            /** Файл слишком мал */
            if ($this->options['min_file_size'] && $this->uploadable['size'] < $this->options['min_file_size']) {
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:25'), 'error' => 'min_file_size', 'type' => 'error');
                return false;
            }

            if (($this->options['max_width'] || $this->options['max_height'] || $this->options['min_width'] || $this->options['min_height']) && $this->is_valid_image_file()) {

                /** Слишком большое изображение */
                if ($this->options['max_up_size'] && $this->uploadable['size'] > ($this->options['max_up_size'])) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:15'), 'error' => 'max_up_size', 'type' => 'error');
                    return false;
                }
                list($img_width, $img_height) = $this->get_image_size();
                /** Изображение превышает максимальную ширину */
                if ($this->options['max_width'] && $img_width > $this->options['max_width']) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:21') . ': ' . $this->options['max_width'] . 'px', 'error' => 'max_width', 'type' => 'error');
                    return false;
                }
                /** Изображение превышает максимальную высоту */
                if ($this->options['max_height'] && $img_height > $this->options['max_height']) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:22') . ': ' . $this->options['max_height'] . 'px', 'error' => 'max_height', 'type' => 'error');
                    return false;
                }
                /** Для изображения требуется минимальная ширина */
                if ($this->options['min_width'] && $img_width < $this->options['min_width']) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:23'), 'error' => 'min_width', 'type' => 'error');
                    return false;
                }
                /** Для изображения требуется минимальная высота */
                if ($this->options['min_height'] && $img_height < $this->options['min_height']) {
                    $this->response = array('status' => 500, 'message' => lang('fileManager:errors:24'), 'error' => 'min_height', 'type' => 'error');
                    return false;
                }
            }

            if ($this->path == '') {
                /** Нельзя загружать файлы в корневую директорию */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:18'), 'error' => 'root_path_error', 'type' => 'error');
                return false;
            }

//            if (preg_match('/[^0-9a-zA-Z\-_\. ]/i', $this->uploadable['name'])) {
//                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:5'), 'error' => 'fileName_characters', 'type' => 'error');
//                return false;
//            }

            /** если нет такой директории то создаем */
            $this->ensureDirectoryExists($this->realPath);

            if ($this->exists($this->realPath . $this->uploadable['name'])) {
                /** Файл с данным именем уже существует на сервере */
                $this->response = array('status' => 500, 'message' => lang('fileManager:errors:16'), 'error' => 'duplicate_file', 'type' => 'error');
                return false;
            }
        } else {
            /** Файл не удалось загрузить */
            $this->response = array('status' => 500, 'message' => lang('fileManager:errors:27'), 'error' => 'no_file_was_uploaded', 'type' => 'error');
            return false;
        }

        return true;
    }

    /**
     * Загрузка файла
     * @return bool
     */
    public function upload(): bool
    {
//        if (!in_array($this->uploadable['extension'], $this->options['ext_img'], true)) {
        if (!preg_match($this->options['accept_file_types'], $this->uploadable['name'])) {
            $this->file->move($this->realPath, $this->uploadable['name']);
            $uploadedId = $this->generator->saveFile($this->uploadable);
        }
        else {
            $uploadedId = $this->generator->init($this->file)->save();
        }

        if (!$uploadedId) {
            /** Невозможно загрузить файл на сервер. */
            $this->response = array('status' => 500, 'message' => I18N::locale("Невозможно загрузить файл на сервер", "Rasm qo'shildi!", "Image added!"), 'error' => 'unable_upload_file', 'type' => 'error');
//            $this->response = array('status' => 500, 'message' => lang('fileManager:errors:17'), 'error' => 'unable_upload_file', 'type' => 'error');
            return false;
        }

        /** Файл загружен. */
        $link = $this->getFileUrl($uploadedId);
        $this->response = array('status' => 200, 'message' => I18N::locale("Файл загружен.", "Rasm qo'shildi!", "Image added!"), 'error' => '', 'fileId' => $uploadedId, 'link' => $link, 'type' => 'success');
//        $this->response = array('status' => 200, 'message' => lang('fileManager:messages:1'), 'error' => '', 'fileId' => $uploadedId, 'link' => $link, 'type' => 'success');

        return true;
    }

    /**
     * Метод для получения картинки при загрузки в контент (tinymce)
     *
     * @param $fileId
     * @return string|null
     */
    public function getFileUrl($fileId): ?string
    {
        if ($file = connect()->table('files')->where('fileId', $fileId)->get('array')) {
            $image = ImageSizes::init()->get(null, $file);
            return $image['original'] ?? '';
        }

        return null;
    }

    /**
     * Валидация изображения
     * @return bool
     */
    protected function is_valid_image_file(): bool
    {
        if (!preg_match('/\.(gif|jpe?g|png|webp)$/i', $this->file->getClientOriginalName())) {
            return false;
        }

        return !!$this->image_type();
    }

    protected function image_type()
    {
        $fp = fopen($this->file, 'r');
        $data = fread($fp, 4);
        fclose($fp);
        // GIF: 47 49 46 38
        if ($data === 'GIF8') {
            return self::IMAGETYPE_GIF;
        }
        // JPG: FF D8 FF
        if (bin2hex(substr($data, 0, 3)) === 'ffd8ff') {
            return self::IMAGETYPE_JPEG;
        }
        // PNG: 89 50 4E 47
        if (bin2hex(@$data[0]).substr($data, 1, 4) === '89PNG') {
            return self::IMAGETYPE_PNG;
        }
        if ($data === 'RIFF') {
            return self::IMAGETYPE_WEBP;
        }
        return false;
    }

    protected function get_image_size()
    {
        if ($this->options['image_library']) {

            if (extension_loaded('imagick')) {
                $image = new \Imagick();
                try {
                    if (@$image->pingImage($this->file)) {
                        $dimensions = array($image->getImageWidth(), $image->getImageHeight());
                        $image->destroy();

                        return $dimensions;
                    }

                    return false;

                } catch (\Exception $e) {
                    error_log($e->getMessage());
                }
            }

            if ($this->options['image_library'] === 2) {
                $cmd = $this->options['identify_bin'];
                $cmd .= ' -ping '.escapeshellarg($this->file);
                exec($cmd, $output, $error);
                if (!$error && !empty($output)) {
                    // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000
                    $infos = preg_split('/\s+/', substr($output[0], strlen($this->file)));
                    return preg_split('/x/', $infos[2]);
                }

                return false;
            }
        }

        if (!function_exists('getimagesize')) {
            error_log('Function not found: getimagesize');

            return false;
        }

        return @getimagesize($this->file);
    }

    public function get_config_bytes($val): float
    {
        $val = trim($val);
        $last = strtolower($val[strlen($val)-1]);
        $val = (int)$val;
        switch ($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
        return $this->fix_integer_overflow($val);
    }

    // Исправлено переполнение 32-разрядных целых чисел со знаком,
    // работает для размеров до 2 ^ 32-1 байт (4 гигабайта - 1):
    protected function fix_integer_overflow($size): float
    {
        if ($size < 0) {
            $size += 2.0 * (PHP_INT_MAX + 1);
        }

        return $size;
    }

    /**
     * @param $id
     * @return false|mixed
     */
    protected function get_server_var($id)
    {
        if (isset($_SERVER[$id])) {
            return @$_SERVER[$id];
        }

        return false;
    }


}