<?php

namespace Drupal\webp;

use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\File\Exception\FileException;
use Drupal\Core\Image\ImageFactory;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Imagick;

/**
 * Class Webp.
 *
 * @package Drupal\webp
 */
class Webp {

  use StringTranslationTrait;

  /**
   * The image factory.
   *
   * @var \Drupal\Core\Image\ImageFactory
   */
  protected $imageFactory;

  /**
   * Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * Default image processing quality.
   *
   * @var int
   */
  protected $defaultQuality;

  /**
   * The file system service.
   *
   * @var Drupal\Core\File\FileSystemInterface;
   */
  protected $fileSystem;

  /**
   * Webp constructor.
   *
   * @param \Drupal\Core\Image\ImageFactory $imageFactory
   *   Image factory to be used.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   Logger channel factory.
   * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation
   *   String translation interface.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   Configuration factory.
   * @param Drupal\Core\File\FileSystemInterface $fileSystem
   *   The file system service.
   */
  public function __construct(ImageFactory $imageFactory, LoggerChannelFactoryInterface $loggerFactory, TranslationInterface $stringTranslation, ConfigFactoryInterface $configFactory, FileSystemInterface $fileSystem) {
    $this->imageFactory = $imageFactory;
    $this->logger = $loggerFactory->get('webp');
    $this->setStringTranslation($stringTranslation);
    $this->defaultQuality = $configFactory->get('webp.settings')->get('quality');
    $this->fileSystem = $fileSystem;
  }

  /**
   * Creates a WebP copy of a source image URI.
   *
   * @param string $uri
   *   Image URI.
   * @param int $quality
   *   Image quality factor (optional).
   *
   * @return bool|string
   *   The location of the WebP image if successful, FALSE if not successful.
   */
  public function createWebpCopy($uri, $quality = NULL) {
    $webp = FALSE;

    $toolkit = \Drupal::config('system.image')->get('toolkit', FALSE);
    // Fall back to GD if the installed imagemagick does not support WEBP.
    if (!extension_loaded('imagick')) {
      $toolkit = 'gd';
    }
    elseif (($toolkit == 'imagemagick' || $toolkit == 'imagick') && !in_array('WEBP', Imagick::queryFormats())) {
      $toolkit = 'gd';
    }

    if (is_null($quality)) {
      $quality = $this->defaultQuality;
    }

    if ($toolkit == 'imagemagick' || $toolkit == 'imagick') {
      $webp = $this->createImageMagickImage($uri, $quality, $toolkit);
    }
    else {
      // We assume $toolkit == 'gd'.
      // Generate a GD resource from the source image. You can't pass GD
      // resources created by the $imageFactory as a parameter to another
      // function, so we have to do everything in one function.
      $sourceImage = $this->imageFactory->get($uri, 'gd');
      /** @var \Drupal\system\Plugin\ImageToolkit\GDToolkit $toolkit */
      $toolkit = $sourceImage->getToolkit();
      if (!class_exists(DeprecationHelper::class)) {
        $sourceImage = $toolkit->getResource();
      }
      else {
        $sourceImage = DeprecationHelper::backwardsCompatibleCall(
          \Drupal::VERSION,
          '10.2',
          currentCallable: fn () => $toolkit->getImage(),
          deprecatedCallable: fn () => $toolkit->getResource(),
        );
      }

      // If we can generate a GD resource from the source image, generate the
      // URI of the WebP copy and try to create it.
      if ($sourceImage !== NULL) {
        $destination = $this->getWebpDestination($uri, '@directory@filename.webp');

        imagesavealpha($sourceImage, TRUE);
        imagealphablending($sourceImage, TRUE);
        imagesavealpha($sourceImage, TRUE);
        if (@imagewebp($sourceImage, $destination, $quality)) {
          // Clear cache to have correct value of filesize.
          clearstatcache(TRUE, $destination);
          // In some cases, libgd generates broken images. See
          // https://stackoverflow.com/questions/30078090/imagewebp-php-creates-corrupted-webp-files
          // for more information.
          if (filesize($destination) % 2 == 1) {
            file_put_contents($destination, "\0", FILE_APPEND);
          }

          @imagedestroy($sourceImage);
          $webp = $destination;
        }
        else {
          $error = $this->t('Could not generate WebP image.');
          $this->logger->error($error);
        }
      }

      // If we can't generate a GD resource from the source image, fail safely.
      else {
        $error = $this->t('Could not generate image resource from URI @uri.', [
          '@uri' => $uri,
        ]);
        $this->logger->error($error);
      }
    }
    return $webp;
  }

  /**
   * Deletes all image style derivatives.
   */
  public function deleteImageStyleDerivatives() {
    // Remove the styles directory and generated images.
    try {
      $this->fileSystem->deleteRecursive(\Drupal::config('system.file')->get('default_scheme') . '://styles');
    }
    catch (FileException $e) {
      $this->logger->error($e->getMessage());
      $error = $this->t('Could not delete image style directory while uninstalling WebP. You have to delete it manually.');
      $this->logger->error($error);
    }
  }

  /**
   * Receives the srcset string of an image and returns the webp equivalent.
   *
   * @param string $srcset
   *   Srcset to convert to .webp version
   *
   * @return string
   *   Webp version of srcset
   */
  public function getWebpSrcset($srcset) {
    return preg_replace('/\.(png|jpg|jpeg)(\\?.*?)?(,| |$)/i', '.webp\\2\\3', $srcset);
  }

  /**
   * Creates a WebP copy of a source image URI using imagemagick toolkit.
   *
   * @param string $uri
   *   Image URI.
   * @param int $quality
   *   Image quality factor.
   * @param string $toolkit
   *   The toolkit ID of the image factory to use.
   *
   * @return bool|string
   *   The location of the WebP image if successful, FALSE if not successful.
   */
  private function createImageMagickImage($uri, $quality, $toolkit) {
    $webp = FALSE;

    $ImageMagickImg = $this->imageFactory->get($uri, $toolkit);
    // We'll convert the image into webp.
    $ImageMagickImg->apply('convert', ['extension' => 'webp', 'quality' => $quality]);

    $destination = $this->getWebpDestination($uri, '@directory@filename.webp');
    if ($ImageMagickImg->save($destination)) {
      $webp = $destination;

      $msg = $this->t(
        'Generated WebP image with Image Magick. Quality: @quality Destination: @destination',
        [
          '@quality' => $quality,
          '@destination' => $destination,
        ]
      );
      $this->logger->info($msg);
    }
    else {
      $error = $this->t('Imagemagick issue: Could not generate WebP image.');
      $this->logger->error($error);
    }

    return $webp;
  }

  /**
   * Return source destination for given uri.
   * @param $uri
   * @param $template
   *
   * @return string
   */
  public function getWebpDestination($uri, $template) {
    $path_info = pathinfo($uri);
    $path = $path_info['dirname'];
    // If path is just the stream wrapper scheme, e.g. "public:" or
    // "temporary:" append two slashes, otherwise just one.
    $path .= (substr($path, (strlen($path) - 1)) === ':') ? '//' : '/';
    $destination = strtr($template, [
      '@directory' => $path,
      '@filename' => $path_info['filename'],
      '@extension' => $path_info['extension'],
    ]);

    return $destination;
  }

}
