<?php

namespace Gregwar\Image\Adapter;

use Gregwar\Image\Image;
use Gregwar\Image\ImageColor;
use Gregwar\Image\Utils\FileUtils;

// Add IMG_AVIF constant for PHP versions below 8.1
defined('IMG_AVIF') or define('IMG_AVIF', 256);

class GD extends Common
{
    /**
     * GD image types supported by this adapter.
     */
    public static $gdTypes = array(
        'jpeg'  => \IMG_JPG,
        'gif'   => \IMG_GIF,
        'png'   => \IMG_PNG,
        'webp'  => \IMG_WEBP,
        'avif'  => \IMG_AVIF
    );

    /**
     * @param resource $resource
     */
    protected function loadResource($resource)
    {
        parent::loadResource($resource);

        imagesavealpha($this->resource, true);
    }

    /**
     * Gets the width and the height for writing some text.
     *
     * @param string $font
     * @param string $text
     * @param int|float $size
     * @param int|float $angle
     *
     * @return array
     */
    public static function TTFBox($font, $text, $size, $angle = 0)
    {
        $box = imagettfbbox($size, $angle, $font, $text);

        return array(
            'width'  => abs($box[2] - $box[0]),
            'height' => abs($box[3] - $box[5]),
        );
    }

    public function __construct()
    {
        parent::__construct();

        if (!(extension_loaded('gd') && function_exists('gd_info'))) {
            throw new \RuntimeException('You need to install GD PHP Extension to use this library');
        }
    }
    
    public function __destruct()
    {
        if ($this->resource) {
            imagedestroy($this->resource);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'GD';
    }

    /**
     * {@inheritdoc}
     */
    public function fillBackground($background = 0xffffff)
    {
        $w = $this->width();
        $h = $this->height();
        $n = imagecreatetruecolor($w, $h);
        imagefill($n, 0, 0, ImageColor::gdAllocate($this->resource, $background));
        imagecopyresampled($n, $this->resource, 0, 0, 0, 0, $w, $h, $w, $h);
        imagedestroy($this->resource);
        $this->resource = $n;

        return $this;
    }

    /**
     * Do the image resize.
     *
     * @param int|string $bg
     * @param int $target_width
     * @param int $target_height
     * @param int $new_width
     * @param int $new_height
     *
     * @return $this
     */
    protected function doResize($bg, int $target_width, int $target_height, int $new_width, int $new_height)
    {
        $width = $this->width();
        $height = $this->height();
        $n = imagecreatetruecolor($target_width, $target_height);

        if ($bg !== 'transparent') {
            imagefill($n, 0, 0, ImageColor::gdAllocate($this->resource, $bg));
        } else {
            imagealphablending($n, false);
            $color = ImageColor::gdAllocate($this->resource, 'transparent');

            imagefill($n, 0, 0, $color);
            imagesavealpha($n, true);
        }

        imagecopyresampled(
            $n,
            $this->resource,
            (int) (($target_width - $new_width) / 2),
            (int) (($target_height - $new_height) / 2),
            0,
            0,
            $new_width,
            $new_height,
            $width,
            $height
        );

        imagedestroy($this->resource);

        $this->resource = $n;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function crop($x, $y, $width, $height)
    {
        $destination = imagecreatetruecolor($width, $height);
        imagealphablending($destination, false);
        imagesavealpha($destination, true);
        imagecopy($destination, $this->resource, 0, 0, (int) $x, (int) $y, $this->width(), $this->height());
        imagedestroy($this->resource);
        $this->resource = $destination;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function negate()
    {
        imagefilter($this->resource, IMG_FILTER_NEGATE);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function brightness($brightness)
    {
        imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $brightness);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function contrast($contrast)
    {
        imagefilter($this->resource, IMG_FILTER_CONTRAST, $contrast);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function grayscale()
    {
        imagefilter($this->resource, IMG_FILTER_GRAYSCALE);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function emboss()
    {
        imagefilter($this->resource, IMG_FILTER_EMBOSS);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function smooth($p)
    {
        imagefilter($this->resource, IMG_FILTER_SMOOTH, $p);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function sharp()
    {
        imagefilter($this->resource, IMG_FILTER_MEAN_REMOVAL);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function edge()
    {
        imagefilter($this->resource, IMG_FILTER_EDGEDETECT);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function colorize($red, $green, $blue)
    {
        imagefilter($this->resource, IMG_FILTER_COLORIZE, $red, $green, $blue);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function sepia()
    {
        imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
        imagefilter($this->resource, IMG_FILTER_COLORIZE, 100, 50, 0);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function gaussianBlur($blurFactor = 1)
    {
        $blurFactor = round($blurFactor); // blurFactor has to be an integer

        $originalWidth = $this->width();
        $originalHeight = $this->height();

        $smallestWidth = ceil($originalWidth * pow(0.5, $blurFactor));
        $smallestHeight = ceil($originalHeight * pow(0.5, $blurFactor));

        // for the first run, the previous image is the original input
        $prevImage = $this->resource;
        $prevWidth = $originalWidth;
        $prevHeight = $originalHeight;

        // scale way down and gradually scale back up, blurring all the way
        for ($i = 0; $i < $blurFactor; ++$i) {
            // determine dimensions of next image
            $nextWidth = $smallestWidth * pow(2, $i);
            $nextHeight = $smallestHeight * pow(2, $i);

            // resize previous image to next size
            $nextImage = imagecreatetruecolor($nextWidth, $nextHeight);
            imagecopyresized($nextImage, $prevImage, 0, 0, 0, 0,
                $nextWidth, $nextHeight, $prevWidth, $prevHeight);

            // apply blur filter
            imagefilter($nextImage, IMG_FILTER_GAUSSIAN_BLUR);

            // now the new image becomes the previous image for the next step
            $prevImage = $nextImage;
            $prevWidth = $nextWidth;
            $prevHeight = $nextHeight;
        }

        // scale back to original size and blur one more time
        imagecopyresized($this->resource, $nextImage,
            0, 0, 0, 0, $originalWidth, $originalHeight, $nextWidth, $nextHeight);
        imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);

        // clean up
        imagedestroy($prevImage);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function merge(Image $other, $x = 0, $y = 0, $width = null, $height = null)
    {
        $other = clone $other;
        $other->init();
        $other->applyOperations();

        imagealphablending($this->resource, true);

        if (null === $width) {
            $width = $other->width();
        }

        if (null === $height) {
            $height = $other->height();
        }

        imagecopyresampled($this->resource, $other->getAdapter()->getResource(), $x, $y, 0, 0, $width, $height, $width, $height);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function rotate($angle, $background = 0xffffff)
    {
        $this->resource = imagerotate($this->resource, $angle, ImageColor::gdAllocate($this->resource, $background));
        imagealphablending($this->resource, true);
        imagesavealpha($this->resource, true);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function fill($color = 0xffffff, $x = 0, $y = 0)
    {
        imagealphablending($this->resource, false);
        imagefill($this->resource, $x, $y, ImageColor::gdAllocate($this->resource, $color));

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function write($font, $text, $x = 0, $y = 0, $size = 12, $angle = 0, $color = 0x000000, $align = 'left')
    {
        imagealphablending($this->resource, true);

        if ($align !== 'left') {
            $sim_size = self::TTFBox($font, $text, $size, $angle);

            if ($align === 'center') {
                $x -= $sim_size['width'] / 2;
            }

            if ($align === 'right') {
                $x -= $sim_size['width'];
            }
        }

        imagettftext($this->resource, $size, $angle, (int) $x, $y, ImageColor::gdAllocate($this->resource, $color), $font, $text);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function rectangle($x1, $y1, $x2, $y2, $color, $filled = false)
    {
        if ($filled) {
            imagefilledrectangle($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));
        } else {
            imagerectangle($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function roundedRectangle($x1, $y1, $x2, $y2, $radius, $color, $filled = false)
    {
        if ($color) {
            $color = ImageColor::gdAllocate($this->resource, $color);
        }

        if ($filled == true) {
            imagefilledrectangle($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y2, $color);
            imagefilledrectangle($this->resource, $x1, $y1 + $radius, $x1 + $radius - 1, $y2 - $radius, $color);
            imagefilledrectangle($this->resource, $x2 - $radius + 1, $y1 + $radius, $x2, $y2 - $radius, $color);

            imagefilledarc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $color, IMG_ARC_PIE);
            imagefilledarc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $color, IMG_ARC_PIE);
            imagefilledarc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $color, IMG_ARC_PIE);
            imagefilledarc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $color, IMG_ARC_PIE);
        } else {
            imageline($this->resource, $x1 + $radius, $y1, $x2 - $radius, $y1, $color);
            imageline($this->resource, $x1 + $radius, $y2, $x2 - $radius, $y2, $color);
            imageline($this->resource, $x1, $y1 + $radius, $x1, $y2 - $radius, $color);
            imageline($this->resource, $x2, $y1 + $radius, $x2, $y2 - $radius, $color);

            imagearc($this->resource, $x1 + $radius, $y1 + $radius, $radius * 2, $radius * 2, 180, 270, $color);
            imagearc($this->resource, $x2 - $radius, $y1 + $radius, $radius * 2, $radius * 2, 270, 360, $color);
            imagearc($this->resource, $x1 + $radius, $y2 - $radius, $radius * 2, $radius * 2, 90, 180, $color);
            imagearc($this->resource, $x2 - $radius, $y2 - $radius, $radius * 2, $radius * 2, 360, 90, $color);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function line($x1, $y1, $x2, $y2, $color = 0x000000)
    {
        imageline($this->resource, $x1, $y1, $x2, $y2, ImageColor::gdAllocate($this->resource, $color));

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function ellipse($cx, $cy, $width, $height, $color = 0x000000, $filled = false)
    {
        if ($filled) {
            imagefilledellipse($this->resource, $cx, $cy, $width, $height, ImageColor::gdAllocate($this->resource, $color));
        } else {
            imageellipse($this->resource, $cx, $cy, $width, $height, ImageColor::gdAllocate($this->resource, $color));
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function circle($cx, $cy, $r, $color = 0x000000, $filled = false)
    {
        return $this->ellipse($cx, $cy, $r, $r, ImageColor::gdAllocate($this->resource, $color), $filled);
    }

    /**
     * {@inheritdoc}
     */
    public function polygon(array $points, $color, $filled = false)
    {
        if ($filled) {
            imagefilledpolygon($this->resource, $points, count($points) / 2, ImageColor::gdAllocate($this->resource, $color));
        } else {
            imagepolygon($this->resource, $points, count($points) / 2, ImageColor::gdAllocate($this->resource, $color));
        }

        return $this;
    }

    /**
     *  {@inheritdoc}
     */
    public function flip($flipVertical, $flipHorizontal)
    {
        if (!$flipVertical && !$flipHorizontal) {
            return $this;
        }

        if (function_exists('imageflip')) {
            if ($flipVertical && $flipHorizontal) {
                $flipMode = \IMG_FLIP_BOTH;
            } elseif ($flipVertical && !$flipHorizontal) {
                $flipMode = \IMG_FLIP_VERTICAL;
            } elseif (!$flipVertical && $flipHorizontal) {
                $flipMode = \IMG_FLIP_HORIZONTAL;
            }

            imageflip($this->resource, $flipMode);
        } else {
            $width = $this->width();
            $height = $this->height();

            $src_x      = 0;
            $src_y      = 0;
            $src_width  = $width;
            $src_height = $height;

            if ($flipVertical) {
                $src_y      = $height - 1;
                $src_height = -$height;
            }

            if ($flipHorizontal) {
                $src_x      = $width - 1;
                $src_width  = -$width;
            }

            $imgdest = imagecreatetruecolor($width, $height);
            imagealphablending($imgdest, false);
            imagesavealpha($imgdest, true);

            if (imagecopyresampled($imgdest, $this->resource, 0, 0, $src_x, $src_y, $width, $height, $src_width, $src_height)) {
                imagedestroy($this->resource);
                $this->resource = $imgdest;
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function width()
    {
        if (null === $this->resource) {
            $this->init();
        }

        return imagesx($this->resource);
    }

    /**
     * {@inheritdoc}
     */
    public function height()
    {
        if (null === $this->resource) {
            $this->init();
        }

        return imagesy($this->resource);
    }

    /**
     * @param int $width
     * @param int $height
     *
     * @return void
     */
    protected function createImage($width, $height)
    {
        $this->resource = imagecreatetruecolor($width, $height);
    }


    /**
     * @param string $data
     *
     * @return void
     */
    protected function createImageFromData($data)
    {
        $this->resource = @imagecreatefromstring($data);
    }

    /**
     * Converts the image to true color.
     *
     * @return void
     */
    protected function convertToTrueColor()
    {
        if (!imageistruecolor($this->resource)) {
            if (function_exists('imagepalettetotruecolor')) {
                // Available in PHP 5.5
                imagepalettetotruecolor($this->resource);
            } else {
                $transparentIndex = imagecolortransparent($this->resource);

                $w = $this->width();
                $h = $this->height();

                $img = imagecreatetruecolor($w, $h);
                imagecopy($img, $this->resource, 0, 0, 0, 0, $w, $h);

                if ($transparentIndex != -1) {
                    $width = $this->width();
                    $height = $this->height();

                    imagealphablending($img, false);
                    imagesavealpha($img, true);

                    for ($x = 0; $x < $width; ++$x) {
                        for ($y = 0; $y < $height; ++$y) {
                            if (imagecolorat($this->resource, $x, $y) == $transparentIndex) {
                                imagesetpixel($img, $x, $y, 127 << 24);
                            }
                        }
                    }
                }

                $this->resource = $img;
            }
        }

        imagesavealpha($this->resource, true);
    }

    /**
     * {@inheritdoc}
     */
    public function saveGif($file)
    {
        $transColor = imagecolorallocatealpha($this->resource, 255, 255, 255, 127);
        imagecolortransparent($this->resource, $transColor);
        imagegif($this->resource, $file);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function savePng($file)
    {
        imagepng($this->resource, $file);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function saveWebp($file, $quality)
    {
        imagewebp($this->resource, $file, $quality);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function saveAvif($file, $quality)
    {
        imageavif($this->resource, $file, $quality);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function saveJpeg($file, $quality)
    {
        imagejpeg($this->resource, $file, $quality);

        return $this;
    }

    /**
     * Try to open the file using jpeg.
     *
     * @param string $file
     *
     * @return void
     */
    protected function openJpeg($file)
    {
        if (FileUtils::safeExists($file) && filesize($file)) {
            $this->resource = @imagecreatefromjpeg($file);
        } else {
            $this->resource = false;
        }
    }

    /**
     * Try to open the file using gif.
     *
     * @param string $file
     *
     * @return void
     */
    protected function openGif($file)
    {
        if (FileUtils::safeExists($file) && filesize($file)) {
            $this->resource = @imagecreatefromgif($file);
        } else {
            $this->resource = false;
        }
    }

    /**
     * Try to open the file using PNG.
     *
     * @param string $file
     *
     * @return void
     */
    protected function openPng($file)
    {
        if (FileUtils::safeExists($file) && filesize($file)) {
            $this->resource = @imagecreatefrompng($file);
        } else {
            $this->resource = false;
        }
    }

    /**
     * Try to open the file using WEBP.
     *
     * @param string $file
     *
     * @return void
     */
    protected function openWebp($file)
    {
        if (FileUtils::safeExists($file) && filesize($file)) {
            $this->resource = @imagecreatefromwebp($file);
        } else {
            $this->resource = false;
        }
    }

    /**
     * Try to open the file using AVIF.
     */
    protected function openAvif($file)
    {
        if (file_exists($file) && filesize($file)) {
            $this->resource = @imagecreatefromavif($file);
        } else {
            $this->resource = false;
        }
    }

    /**
     * Does this adapter supports type ?
     * Returns true if checked type is supported by the PHP/GD build and this adapter.
     * @param string $type
     *
     * @return bool
     */
    protected function supports($type)
    {
        return imagetypes() & self::$gdTypes[$type];
    }

    /**
     * @param int $x
     * @param int $y
     *
     * @return int|false
     */
    protected function getColor($x, $y)
    {
        return imagecolorat($this->resource, $x, $y);
    }

    /**
     * {@inheritdoc}
     */
    public function enableProgressive()
    {
        imageinterlace($this->resource, 1);

        return $this;
    }
}
