<?php

namespace IZON\Thumber\Services\Impl;

use IZON\IO\Image;
use IZON\Thumber\Configuration\Configuration;
use IZON\Thumber\Exceptions\ThumberException;
use IZON\Thumber\Image\IImagePoint;
use IZON\Thumber\Image\IImageSize;
use IZON\Thumber\Image\IImageUrlBuilder;
use IZON\Thumber\Image\ImageNameBuilder;
use IZON\Thumber\Image\ImagePoint;
use IZON\Thumber\Image\ImageSize;
use IZON\Thumber\ImageCache\ImageCache;
use IZON\Thumber\ImageConfiguration\IImageConfiguration;
use IZON\Thumber\ImageConfiguration\ImageConfiguration;
use IZON\Thumber\ImageConverter\IImageConverterFactory;
use IZON\Thumber\ImageResizer\IImageResizer;
use IZON\Thumber\ImageResizer\ImageResizer;
use IZON\Thumber\Services\ThumbnailService;
use IZON\Thumber\Thumber;
use IZON\Thumber\Utils\CropPositionConvertor;
use function IZON\String\endsWith;
use function IZON\String\startsWith;

/**
 * servis pro zpracovani obrazku, mozna i dalsiho multimedialniho obsahu z galerie
 */
class ThumbnailServiceImpl implements ThumbnailService {
    /**
     *
     * @var IImageResizer
     */
    protected $imageResizer;
    /**
     *
     * @var ImageCache
     */
    protected $imageCache;
    /**
     *
     * @var IImageConverterFactory
     */
    protected $imageConverterFactory;
    /**
     *
     * @var ?string
     */
    protected $format = null;
    protected $rootDir = '';
    /**
     *
     * @var array
     */
    protected $defaultConfig = [
        'type' => IImageResizer::CONTAIN,
        'width' => 0,
        'height' => 0,
        'quality' => 90,
        'upsize' => false
    ];
    protected $defaultCropConfig = [
        'resize' => true,
        'width' => 'center',
        'height' => 'center',
    ];
    
    /**
     * Default behavior is lazy.
     * Converts image on demand.
     * @var bool
     */
    protected $lazy = true;
    
    /**
     *
     * @var IImageUrlBuilder
     */
    protected $imageUrlBuilder;

    /**
     * 
     * @param IImageResizer $imageResizer
     * @param ImageCache $imageCache
     * @param IImageConverterFactory $imageConverterFactory
     */
    function __construct(IImageResizer $imageResizer, ImageCache $imageCache, IImageConverterFactory $imageConverterFactory) {
        $this->imageResizer = $imageResizer;
        $this->imageCache = $imageCache;
        $this->imageConverterFactory = $imageConverterFactory;
    }

    /**
     * 
     * @param string|null $format
     */
    function setFormat(?string $format) {
        $this->format = $format;
    }

    public function setRootDir(string $path) {
        if(!file_exists($path)) {
            throw new ThumberException("Web root path '$path' not exists.");
        }
        $this->rootDir = $path;
    }
    
    /**
     * Setter for ImageUrlBuilder.
     * @param IImageUrlBuilder $builder
     */
    public function setImageUrlBuilder(IImageUrlBuilder $builder) {
        $this->imageUrlBuilder = $builder;
    }
    
    /**
     * Set lazy creating images on demand.
     * @param bool $value
     */
    public function setLazy(bool $value) {
        $this->lazy = $value;
    }

    public function getThumber() {
        return new Thumber($this);
    }

    /**
     * 
     * @param Image $sourceImage
     * @param array $parameters parametry pro resizovani
     */
    public function getImageURL(Image $sourceImage, array $parameters = []) {
        $config = new Configuration($this->defaultConfig);
        $config->extend($parameters);

        $width = $config->get('width');
        $height = $config->get('height');
        $type = $config->get('type');
        $quality = $config->get('quality');
        $upsize = $config->get('upsize');

        $originaSize = ImageSize::create($sourceImage->getWidth(), $sourceImage->getHeight());
        if($width <= 0 && $height <= 0) {
            $containerSize = $originaSize;
        } else if($width <= 0) {
            $containerSize = ImageSize::createFromHeight($height, $originaSize->getAspectRatio());
        } else if($height <= 0) {
            $containerSize = ImageSize::createFromWidth($width, $originaSize->getAspectRatio());
        } else {
            $containerSize = ImageSize::create($width, $height);
        }

        $newSize = $this->imageResizer->resize($type)->getSize($originaSize, $containerSize, $upsize);

        $cropPoint = null;
        $cropSize = null;
        if($type == ImageResizer::CROP) {
            if(!$config->isNestedConfig('crop') && $config->has('crop')) {
                throw new ThumberException('not setted crop parameters');
            }
            if($config->has('crop')) {
                /* @var $cropConfig Configuration */
                $cropConfig = $config->get('crop');
                $cropConfig->insertDefault($this->defaultCropConfig);
            } else {
                $cropConfig = new Configuration($this->defaultCropConfig);
            }
            if($cropConfig->get('resize') == false) {
                throw new ThumberException('resize false is not supported');
            }

            $cropSize = $newSize;
            $newSize = $this->imageResizer
                ->resize(ImageResizer::COVER)
                ->getSize($originaSize, $newSize, $upsize);
            $widthCropPercent = CropPositionConvertor::convert($cropConfig->get('width'));
            $heightCropPercent = CropPositionConvertor::convert($cropConfig->get('height'), 'height');
            $cropPoint = new ImagePoint(
                (int) floor($widthCropPercent * ($newSize->getWidth() - $cropSize->getWidth())),
                (int) floor($heightCropPercent * ($newSize->getHeight() - $cropSize->getHeight()))
            );
        }
        
        $pathParts = pathinfo($sourceImage->getFsPath());

        $filePath = $pathParts['dirname'];
        if(startsWith($filePath, $this->rootDir)) {
            $len = mb_strlen($this->rootDir);
            if(!endsWith($this->rootDir, '/')) {
                $len += 1;
            }
            $filePath = mb_substr($filePath, $len);
        }

        $nameBuilder = new ImageNameBuilder($pathParts['filename'], $filePath, $sourceImage->getExtension());
        $nameBuilder->setSize($newSize);
        $nameBuilder->setQuality($quality);
        $nameBuilder->setType($type);
        if(!empty($this->format)) {
            $nameBuilder->setFormat($this->format);
        }
        if($type == ImageResizer::CROP) {
            if($cropPoint instanceof IImagePoint) {
                $nameBuilder->setCropPoint($cropPoint);
            }
            if($cropSize instanceof IImageSize) {
                $nameBuilder->setCropSize($cropSize);
            }
        }
        $newName = $nameBuilder->build();

        if($this->imageCache->has($newName)) {
            $path = $this->imageCache->getPath($newName);
            return $path . '?' . filemtime($path);
        }
        
        # Create image configuration object
        $imageConfiguration = new ImageConfiguration();
        $imageConfiguration
                ->setImage($sourceImage)
                ->setImageSize($newSize)
                ->setQuality($config->get('quality'))
                ->setType($config->get('type'))
                ->setName($newName);
        if($cropPoint instanceof IImagePoint && $cropSize instanceof IImageSize) {
            $imageConfiguration
                    ->setCropPoint($cropPoint)
                    ->setCropSize($cropSize);
        }
         
        # If lazy, then only URL to controller is returned
        if($this->lazy || ($config->has('lazy') && $config->get('lazy'))) {
            return $this->imageUrlBuilder->buildUrl($imageConfiguration);           
        }
        
        return $this->createThumbnailPath($imageConfiguration);

    }
    
    /**
     * Creates image from ImageConfiguration and returns its path in cache.
     * @param IImageConfiguration $config
     * @return string
     */
    public function createThumbnailPath(IImageConfiguration $config) : string {
        
        $imageConverter = $this->imageConverterFactory->createImageConverter($config->getImage());
        $imageConverter->setSize($config->getImageSize());
        $imageConverter->setQuality($config->getQuality());
        if($config->getType() == ImageResizer::CROP) {
            if($config->getCropPoint() instanceof IImagePoint && $config->getCropSize() instanceof IImageSize) {
                $imageConverter->crop($config->getCropSize(), $config->getCropPoint());
            }
        }
        if(!empty($this->format)) {
            $imageConverter->setFormat($this->format);
        }
        $newImage = $imageConverter->convert($config->getName());
        $this->imageCache->set($config->getName(), $newImage, true);
        $path = $this->imageCache->getPath($config->getName());
        return $path . '?' . filemtime($path);
    }

}

