<?php

use KupShop\AdminBundle\Util\SystemImageUtils;
use KupShop\I18nBundle\Translations\PhotosTranslation;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\FileUtil;
use KupShop\KupShopBundle\Util\StringUtil;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpKernel\EventListener\AbstractSessionListener;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ImageBase
{
    public function run()
    {
        $id = getVal('id');
        $type_id = getVal('size');

        $image = $this->getImage($id, $type_id);

        $image = $this->createThumbnail($image);

        $this->outputImage($image);
    }

    public function returnError($message)
    {
        wpj_debug($message);
        throw new \UnexpectedValueException(join(': ', $message));
    }

    public function getImage($id, $type_id, $lang = null, ?string $version = null)
    {
        global $cfg;

        $type = $type_id;
        $settings = getImageSettings($type);

        if ($version && isset($settings['sizes'][$version])) {
            $settings = array_merge($settings, $settings['sizes'][$version]);
        }

        $settings['size'] ??= reset($settings['sizes'])['size'];

        $photo_type = getVal('type', $settings, $type);
        $fileData = $this->getFileData($id, $photo_type, $lang, $version);
        $path = $fileData['path'];

        if (!empty($fileData['data'])) {
            $settings['data'] = $fileData['data'];
        }

        if ($id === '0' || empty($path)) {
            $path = '../../'.$this->getPlaceholderFile($lang);
        }

        $ext = findModule(Modules::COMPONENTS) ? 'webp' : 'jpg';
        if (isset($settings['svg']) && $settings['svg'] && pathinfo($path, PATHINFO_EXTENSION) == 'svg') {
            $ext = 'svg';
        }

        // Set quality
        if (!$settings['quality']) {
            $settings['quality'] = $ext == 'webp' ? 82 : 77;

            if ($settings['size'][0] < 100 || $settings['size'][1] < 100 || $type_id == 0) {
                $settings['quality'] = $ext == 'webp' ? 90 : 85;
            }
        }

        // Set sharpening
        if (is_null($settings['sharpen'])) {
            $settings['sharpen'] = $type_id != 0 ? 0.7 : 0;
        }

        $path_thumbnail = getImagePath($id, $type_id, $ext, $lang, $version);
        $path_original = $cfg['Path']['photos'].$path;

        return ['original' => $path_original, 'thumbnail' => $path_thumbnail, 'settings' => $settings];
    }

    public function scaleImage($image)
    {
        // DEBUG information
        wpj_debug(['scaleImage', $image]);

        $path_original = $image['original'];
        $path_thumbnail = $image['thumbnail'];
        $settings = $image['settings'];

        if (pathinfo($path_thumbnail, PATHINFO_EXTENSION) == 'svg') {
            $thumbnail_path_info = pathinfo($path_thumbnail);
            if (!is_dir($thumbnail_path_info['dirname'])) {
                wpj_debug('Create OUTPUT directory: '.$thumbnail_path_info['dirname']);
                if (!$this->createDir($thumbnail_path_info['dirname'], 0777)) {
                    wpj_debug('Couldnt create OUTPUT directory');
                }
            }

            copy(realpath($path_original), realpath('.').'/'.$path_thumbnail);

            return $image;
        }

        $thumbnail_path_info = pathinfo($path_thumbnail);
        $original_path_info = pathinfo($path_original);
        $original_extension = strtolower($original_path_info['extension']);

        if (!findModule(\Modules::COMPONENTS)) {
            // Force PNG source images as JPEG if PNG is not allowed
            if ($original_extension == 'png' && !$settings['png']) {
                $original_extension = 'jpeg';
            }
        }

        // Initialize image
        try {
            $thumbnail = new \Imagick(realpath($path_original));
            $original_size = array_values($thumbnail->getImageGeometry());
            if (!$thumbnail->valid() || !$original_size[0] || !$original_size[1]) {
                throw new \ImagickException('Not valid image');
            }
        } catch (\ImagickException $e) {
            // Mark photo as invalid if there is photo ID
            if ($id_photo = $settings['data']['id_photo'] ?? null) {
                sqlQueryBuilder()->update('photos')
                    ->set('data', 'JSON_MERGE_PATCH(COALESCE(data, "{}"), \'{"invalid":true}\')')
                    ->where(\Query\Operator::equals(['id' => $id_photo]))
                    ->execute();
            }

            throw new NotFoundHttpException('Invalid image data', $e);
        }

        // Remove invalid flag if image loaded successfully
        if ($settings['data']['invalid'] ?? null) {
            sqlQueryBuilder()->update('photos')
                ->set('data', 'JSON_REMOVE(data, "$.invalid")')
                ->where(\Query\Operator::equals(['id' => $settings['data']['id_photo']]))
                ->execute();
        }

        // Set background
        if ($settings['background']) {
            $hexBackground = $settings['background'];
            if (is_int($settings['background'])) {
                $hexBackground = '#'.dechex($settings['background']);
            }
            $thumbnail->setImageBackgroundColor($hexBackground);
        }

        $formatsWithTransparentBg = ['png', 'webp', 'avif', 'gif'];
        if (in_array($original_extension, $formatsWithTransparentBg)) {
            $hexBackground ??= '#FFFFFF';
            // Set background color in RGBA format
            // Transparent but with correct background color if image is later converted to non-transparent format
            $thumbnail->setImageBackgroundColor(new \ImagickPixel($hexBackground.'00'));
        } elseif (in_array(strtolower($thumbnail->getImageFormat()), $formatsWithTransparentBg)) {
            $thumbnail = $thumbnail->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
        }

        if ($settings['upscale'] || $original_size[0] > $settings['size'][0] || $original_size[1] > $settings['size'][1]) {
            if ($original_extension == 'gif') {
                $thumbnail = $thumbnail->coalesceImages();
                do {
                    $thumbnail = $this->cropImage($settings, $thumbnail);
                } while ($thumbnail->nextImage());
                $thumbnail = $thumbnail->deconstructImages();
            } else {
                $thumbnail = $this->cropImage($settings, $thumbnail);
            }
        }

        $thumbnail_size = array_values($thumbnail->getImageGeometry());

        if ($settings['removeBackground'] ?? false) {
            if ($thumbnail->getImageColorspace() != \Imagick::COLORSPACE_SRGB) {
                $thumbnail->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
            }
            [$bR, $bG, $bB] = sscanf('0x'.dechex($settings['removeBackground']), '0x%02x%02x%02x');

            $r = $bR / 255;
            $g = $bG / 255;
            $b = $bB / 255;

            $thumbnail->colorMatrixImage([
                $r, 0.0, 0.0, 0.0, 0.0,
                0.0, $g, 0.0, 0.0, 0.0,
                0.0, 0.0, $b, 0.0, 0.0,
                0.0, 0.0, 0.0, 1.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 1.0,
            ]);
        }

        // Sharpen small images
        $scaleFactor = min($original_size[0] / $settings['size'][0], $original_size[1] / $settings['size'][1]);
        if ($settings['sharpen'] > 0 && $scaleFactor > 2) {
            if ($thumbnail->getImageColorspace() != \Imagick::COLORSPACE_SRGB) {
                $thumbnail->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
            }

            $thumbnail->sharpenImage(1, $settings['sharpen']);
        }

        // aplikovat contrast jen na opravdu male fotky - u vetsich fotek to delalo problemy
        if (is_null($settings['contrast'])) {
            $thumbnailMaxSize = max($settings['size'][0], $settings['size'][1]);
            $settings['contrast'] = $thumbnailMaxSize <= 300;
        }

        if ($settings['contrast']) {
            $thumbnail->contrastImage(1);
        }

        // Apply watermark
        if ($settings['watermark']) {
            if (is_bool($settings['watermark'])) {
                if (findModule(Modules::COMPONENTS)) {
                    $settings['watermark'] = ServiceContainer::getService(SystemImageUtils::class)->getImageSrc('watermark', 'png');
                    if (!file_exists($settings['watermark'])) {
                        throw new \Exception('Watermark image does not exist');
                    }
                } else {
                    $settings['watermark'] = 'templates/images/watermark.png';
                }
            }

            $watermark = new \Imagick(realpath($settings['watermark']));

            // resize the watermark
            $watermark->scaleImage($thumbnail_size[0], $thumbnail_size[1], true);
            $wm_size = array_values($watermark->getImageGeometry());

            // calculate the position
            $wm_pos = [($thumbnail_size[0] - $wm_size[0]) / 2, $thumbnail_size[1] - $wm_size[1]];

            $thumbnail->compositeImage($watermark, \Imagick::COMPOSITE_OVER, $wm_pos[0], $wm_pos[1]);
        }

        // Create output folder if necesary
        if (!is_dir($thumbnail_path_info['dirname'])) {
            wpj_debug('Create OUTPUT directory: '.$thumbnail_path_info['dirname']);

            if (!$this->createDir($thumbnail_path_info['dirname'], 0777)) {
                wpj_debug('Couldnt create OUTPUT directory');
            }
        }
        if (!chmod($thumbnail_path_info['dirname'], 0777)) {
            wpj_debug('Could not change OUTPUT directory rights');
        }

        // Save image using proper image function
        if (findModule(Modules::COMPONENTS)) {
            switch ($original_extension) {
                case 'jpg':
                case 'jpeg':
                case 'gif':
                    $thumbnail->setImageFormat('webp');
                    $thumbnail->setOption('webp:lossless', 'false');
                    $thumbnail->setOption('webp:use-sharp-yuv', '1');
                    $thumbnail->setImageCompressionQuality($settings['quality']);
                    break;
                case 'png':
                    $thumbnail->setImageFormat('webp');
                    $thumbnail->setOption('webp:lossless', 'true');
                    break;
                default:
                    wpj_debug('ERROR!! Unsupported OUTPUT image type!');
            }
        } else {
            switch ($original_extension) {
                case 'jpg':
                case 'jpeg':
                    $thumbnail->setImageFormat('jpeg');
                    $thumbnail->setImageCompressionQuality($settings['quality']);
                    break;
                case 'gif':
                    $thumbnail->setImageFormat('gif');
                    break;
                case 'png':
                    $thumbnail->setImageFormat('png');
                    break;
                default:
                    wpj_debug('ERROR!! Unsupported OUTPUT image type!');
            }
        }
        $thumbnail->stripImage();
        $path_format = $path_thumbnail;
        $imageFormat = strtolower($thumbnail->getImageFormat());
        if (!in_array($imageFormat, ['jpeg', 'svg', 'heic'])) {
            $path_format .= ".{$imageFormat}";
        }
        $thumbnail->writeImages(realpath('.').'/'.$path_format, true);
        if ($path_format != $path_thumbnail) {
            rename($path_format, $path_thumbnail);
        }

        // Change attributes, rwx+ugo
        chmod($path_thumbnail, 0777);
        wpj_debug('<b>Image "'.$path_thumbnail.'" OK</b>');

        $thumbnail->destroy();

        return $image;
    }

    public function createThumbnail($image)
    {
        $fileUtil = new FileUtil();

        if (!file_exists($image['original'])) {
            $this->returnError(['Source file does not exist', $image['original']]);
        }

        $skipCache = getVal('skip_cache');
        if (!$fileUtil->isFileNewer($image['thumbnail'], $image['original'], $time_thumbnail) || $skipCache) {
            if ($skipCache && getVal('wpj_debug')) {
                $image['thumbnail'] .= '.tmp';
            }

            $this->scaleImage($image);
            $time_thumbnail = time();
        }

        $image['thumbnail'] .= "?{$time_thumbnail}";

        return $image;
    }

    public function outputImage($image)
    {
        if (getVal('wpj_debug')) {
            exit("<style>body{background-color: darkgray;}</style><img src='/{$image['thumbnail']}'>
              <img src='/{$image['original']}'>");
        }

        $src = parse_url($image['thumbnail'], PHP_URL_PATH);

        if (!file_exists($src)) {
            $this->returnError(['Scaled image does not exists. Error in scaling?', $image]);
        }

        // Pokud mám zapnutý modul CDN, můžu vrátit rovnou data, protože se to zacachuje a nebude už příště zdržovat
        if (findModule(\Modules::PROXY_CACHE)) {
            $response = new BinaryFileResponse($src);
            $response->headers->set(AbstractSessionListener::NO_AUTO_CACHE_CONTROL_HEADER, 'true');
            $response->setPublic()->setMaxAge(3600 * 24 * 30);

            return $response;
        }

        // Jinak zbaběle vracím redirect, aby browser načetl thumbnail a nemusel jsem ho tahat přes PHP
        redirection(StringUtil::absoluteUrl($image['thumbnail']));
    }

    /**
     * @param array $image Image descriptor created using getImage method
     *
     * @return array Array with "width" and "height" keys containing dimension in pixels
     */
    public function getOutputSize($image)
    {
        $thumbnail = new \Imagick(realpath($image['thumbnail']));

        return $thumbnail->getImageGeometry();
    }

    /**
     * @param $thumbnail \Imagick
     *
     * @return \Imagick
     */
    public function cropImage(&$settings, $thumbnail)
    {
        // Apply image cropping/sizing
        if (is_bool($settings['crop'])) {
            $settings['crop'] = $settings['crop'] ? 'crop' : 'fit';
        }

        switch ($settings['crop']) {
            // Just stretch image to fit thumbnail size, fill remaining with background. Returns exact thumbnail size.
            case 'fit':
                // Resize
                $thumbnail->resizeImage($settings['size'][0], $settings['size'][1], \Imagick::FILTER_CATROM, 1, true);

                // Center in the middle of canvas
                $size = array_values($thumbnail->getImageGeometry());
                $size[0] = ($settings['size'][0] - $size[0]) / 2;
                $size[1] = ($settings['size'][1] - $size[1]) / 2;
                $thumbnail->extentImage($settings['size'][0], $settings['size'][1], -$size[0], -$size[1]);
                break;

                // Scale image to fit thumbnail size and crop remaining thumbnail edges. Modifies thumbnail size.
            case 'scale':
                $thumbnail->resizeImage($settings['size'][0], $settings['size'][1], \Imagick::FILTER_CATROM, 1, true);
                break;

                // Scale image to fill whole thumbnail and crop to get exact thumbnail size
            case 'crop':
            default:
                $origSize = array_values($thumbnail->getImageGeometry());
                $yWhenResizedToX = $origSize[1] * ($settings['size'][0] / $origSize[0]);
                $settings['crop'] = ($yWhenResizedToX < $settings['size'][1]) ? 'width' : 'height';
                // Scale image to fill whole thumbnail width/height and crop to get exact thumbnail size
                // no break
            case 'width':
            case 'height':
                if ($settings['crop'] == 'height') {
                    $thumbnail->resizeImage($settings['size'][0], 0, \Imagick::FILTER_CATROM, 1, false);
                } else {
                    $thumbnail->resizeImage(0, $settings['size'][1], \Imagick::FILTER_CATROM, 1, false);
                }

                $centerPoint = [($settings['data']['center_point']['x'] ?? 50) / 100, ($settings['data']['center_point']['y'] ?? 50) / 100];

                $size = array_values($thumbnail->getImageGeometry());

                $offset = [0, 0];

                $calcOffset = function ($axis) use ($settings, $centerPoint, $size) {
                    if ($settings['size'][$axis] > $size[$axis]) {
                        return ($settings['size'][0] - $size[0]) / 2;
                    }

                    $offset = ($settings['size'][$axis] - 2 * $centerPoint[$axis] * $size[$axis]) / 2;

                    return min(max($settings['size'][$axis] - $size[$axis], $offset), 0);
                };

                if ($settings['crop'] == 'height') {
                    $offset[1] = $calcOffset(1);
                } else {
                    $offset[0] = $calcOffset(0);
                }

                $thumbnail->extentImage($settings['size'][0], $settings['size'][1], -$offset[0], -$offset[1]);
                break;
        }

        return $thumbnail;
    }

    /**
     * @param null $lang
     *
     * @return array
     */
    protected function getFileData($id, $photo_type, $lang = null, ?string $version = null)
    {
        $file = null;
        $folder = null;

        switch ($photo_type) {
            case 'return_delivery':
                $file = returnSQLResult('SELECT photo FROM return_delivery WHERE id=:id', ['id' => $id]);

                $folder = '../return_delivery/';
                break;
            case 'section':
                $file = returnSQLResult('SELECT photo FROM sections WHERE id=:id', ['id' => $id]);

                $folder = '../section/';
                break;

            case 'articles_authors':
                $file = returnSQLResult('SELECT photo FROM articles_authors WHERE id=:id', ['id' => $id]);

                $folder = '../articles_authors/';
                break;

            case 'producer':
                $file = returnSQLResult('SELECT photo FROM producers WHERE id=:id', ['id' => $id]);

                $folder = '../producer/';
                break;

            case 'delivery':
                $delivery = DeliveryType::getDeliveries(true)[$id] ?? false;

                if (!$delivery) {
                    $this->returnError(['Unknown delivery', $id]);
                }
                $file = $delivery->getPhotoPath();

                $folder = '';
                break;

            case 'payment':
                $payment = DeliveryType::getPayments(true)[$id] ?? false;

                if (!$payment) {
                    $this->returnError(['Unknown payment', $id]);
                }
                $file = $payment['class']->getPhotoPath($payment['photo_name'] ?? null);

                $folder = '';
                break;

            default:
                if (is_null($lang)) {
                    $lang = Contexts::get(LanguageContext::class)->getDefaultId();
                }

                $SQL = sqlQueryBuilder()
                    ->select('ph.source, ph.image_2, ph.image_tablet, ph.image_mobile, ph.data')
                    ->from('photos', 'ph')
                    ->andWhere(\Query\Operator::equals(['ph.id' => $id]))
                    ->andWhere(\Query\Translation::coalesceTranslatedFields(PhotosTranslation::class, null, $lang))
                    ->execute();

                if ($id !== '0') {
                    if (!sqlNumRows($SQL)) {
                        $this->returnError(['Image ID does not exists', $id]);
                    }

                    $photo = sqlFetchAssoc($SQL);

                    $defaultKey = $key = 'image_2';
                    if ($version) {
                        $key = 'image_'.$version;
                    }

                    // fallback to default photo if specific version does not exists
                    if (empty($photo[$key])) {
                        $key = $defaultKey;
                    }

                    $file = $photo[$key];
                    $folder = $photo['source'];

                    $additionalData['data'] = json_decode($photo['data'] ?? '', true);
                    $additionalData['data']['id_photo'] = $id;
                    break;
                }
        }

        if (empty($file)) {
            return ['path' => ''];
        }

        return array_merge(['path' => $folder.$file], $additionalData ?? []);
    }

    /**
     * @param null $lang
     *
     * @return string
     */
    public function getFilePath($id, $photo_type, $lang = null, ?string $version = null)
    {
        return $this->getFileData($id, $photo_type, $lang, $version)['path'];
    }

    public function createDir($dir, $rights)
    {
        wpj_debug('wpj_create_dir('.$dir.','.$rights.')');
        $return = 0;

        if (!is_dir($dir)) {
            if (strchr($dir, '/')) {
                $return = $this->createDir(substr($dir, 0, strlen($dir) - strlen(strrchr($dir, '/'))), $rights);
            }

            $return = $return | mkdir($dir, $rights);
            chmod($dir, 0777);
        }

        return $return;
    }

    private function getPlaceholderFile(?string $lang = null): string
    {
        global $cfg;

        $defaultImagePath = ServiceContainer::getService(SystemImageUtils::class)->getSystemImagePlaceholderPath();
        if (!is_array($cfg['Photo']['placeholder'] ?? null)) {
            return getVal('placeholder', $cfg['Photo'], $defaultImagePath);
        }

        if (!$lang) {
            $lang = Contexts::get(LanguageContext::class)->getActiveId();
        }

        return $cfg['Photo']['placeholder']['lang'][$lang] ?? $cfg['Photo']['placeholder']['default'] ?? $defaultImagePath;
    }
}

if (empty($subclass)) {
    class Image extends ImageBase
    {
    }
}
