<?php

namespace Gregwar\Image\Adapter;

use Gregwar\Image\Image;
use Gregwar\Image\ImageColor;

class GD extends Common
{
    public static $gdTypes = array(
        'jpeg' => \IMG_JPG,
        'gif' => \IMG_GIF,
        'png' => \IMG_PNG,
        'webp'  => \IMG_WEBP
    );

    protected function loadResource($resource)
    {
        parent::loadResource($resource);
        imagesavealpha($this->resource, true);
    }

    /**
     * Gets the width and the height for writing some text.
     */
    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');
        }
    }

    /**
     * {@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.
     *
     * @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);

        // keep transparency
        // http://stackoverflow.com/questions/279236/how-do-i-resize-pngs-with-transparency-in-php
//        imagecopy($destination, $this->resource, 0, 0, $x, $y, $this->width(), $this->height());
        imagecopyresampled($destination, $this->resource, 0, 0, (int) $x, (int) $y, $this->width(), $this->height(), $this->width(), $this->height());

        imagedestroy($this->resource);
        $this->resource = $destination;

        return $this;
    }


    public function cropCircle($x, $y, $diameter, $border_width = 0, $border_color = 'ffffff')
    {
        // http://stackoverflow.com/questions/999251/crop-or-mask-an-image-into-a-circle?rq=1


        // factor to antialiase, tradeoff between quality and memory consumption
        $antialias_factor = 1;

        if ($diameter < 800) {
            $antialias_factor = 2;
        }

        if ($diameter < 500) {
            $antialias_factor = 2.5;
        }
        if ($diameter < 200) {
            $antialias_factor = 3.5;
        }


        // scale up to and afterwards down to reduce antialising effect
        $this->forceResize($this->width() * $antialias_factor, $this->height() * $antialias_factor);

        $x = $x * $antialias_factor;
        $y = $y * $antialias_factor;
        $diameter = $diameter * $antialias_factor;

        $b_width = (int)$border_width * $antialias_factor;

        $this->crop($x, $y, $diameter, $diameter);

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

        $src = $this->resource;
        $newpic = imagecreatetruecolor($w, $h);
        imagealphablending($newpic, false);
        $transparent = imagecolorallocatealpha($newpic, 0, 0, 0, 127);

        $r = $w / 2;

        try {
            $colorRGBA = ImageColor::parse($border_color);
            // taken from gdAllocate()
            // could be a separate helper function
            $col_b = ($colorRGBA) & 0xff;
            $colorRGBA >>= 8;
            $col_g = ($colorRGBA) & 0xff;
            $colorRGBA >>= 8;
            $col_r = ($colorRGBA) & 0xff;
            $colorRGBA >>= 8;
            $col_a = ($colorRGBA) & 0xff;
        } catch (\InvalidArgumentException $e) {
            $b_width = 0;
            $col_a = 127;
            $col_r = $col_g = $col_b = 0;
        }
        $b_color = imagecolorallocatealpha($newpic, $col_r, $col_g, $col_b, $col_a);

        // stitch out the circle
        for ($x = 0; $x < $w; $x++) {
            for ($y = 0; $y < $h; $y++) {
                $c = imagecolorat($src, $x, $y);
                $_x = $x - $w / 2;
                $_y = $y - $h / 2;

                $x2 = ($_x * $_x);
                $y2 = ($_y * $_y);
                $r2 = ($r * $r);
                $rb2 = (($r - $b_width) * ($r - $b_width));

                if (($x2 + $y2) < $r2) {
                    imagesetpixel($newpic, $x, $y, $c);
                    // draw border
                    if (((int)$b_width > 0) && (($x2 + $y2) >= $rb2)) {
                        imagesetpixel($newpic, $x, $y, $b_color);
                    }
                } else {
                    imagesetpixel($newpic, $x, $y, $transparent);
                }
            }
        }
        imagesavealpha($newpic, true);
        $this->resource = $newpic;


        // scale down
        $this->forceResize($this->width() / $antialias_factor, $this->height() / $antialias_factor);
//        header("Content-type: image/png");
//        imagepng($this->resource);
//        mh_exit();
//        mh_exit();
        return $this;
    }

    public function cropEllipse($x, $y, $width, $height)
    {
        // http://stackoverflow.com/questions/2223437/crop-image-into-circle-and-add-border

        header("Content-type: image/png");
        $antialias_factor = 3.25;


        // issue:
        // fakebg can not be converted into transparent?
        $this->forceResize($this->width() * $antialias_factor, $this->height() * $antialias_factor, "ff00ff");


        $center_x = ($x + $width / 2) * $antialias_factor;
        $center_z = ($y + $height / 2) * $antialias_factor;

        $mask = imagecreatetruecolor($this->width(), $this->height());

        $bg = imagecolorallocate($mask, 255, 0, 255);
        imagefill($mask, 0, 0, $bg);

        $e = imagecolorallocate($mask, 0, 0, 0);
        imagefilledellipse($mask, $center_x, $center_z, $width * $antialias_factor, $height * $antialias_factor, $e);

        imagecolortransparent($mask, $e);


        imagecopymerge($this->resource, $mask, 0, 0, 0, 0, $this->width(), $this->height(), 100);
        $bg_t = imagecolorallocate($this->resource, 255, 0, 255);
        imagecolortransparent($this->resource, $bg_t);
//        imagedestroy($mask);


        $realWidth = $this->width() / $antialias_factor;
        $realHeight = $this->height() / $antialias_factor;
        $srcWidth = $this->width();
        $srcHeight = $this->height();

        // scale down

        $target = imagecreatetruecolor($realWidth, $realHeight);

        imagecopyresampled($target, $this->resource, 0, 0, 0, 0, $realWidth, $realHeight, $srcWidth, $srcHeight);

//        $this->resource = $target;
//        imagepng($mask);
        $this->forceResize($this->width() / $antialias_factor, $this->height() / $antialias_factor, "transparent");


        imagepng($this->resource);
        mh_exit();

        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, $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);
    }

    protected function createImage($width, $height)
    {
        $this->resource = imagecreatetruecolor($width, $height);
    }

    protected function createImageFromData($data)
    {
        $this->resource = @imagecreatefromstring($data);
    }

    /**
     * Converts the image to true color.
     */
    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 saveJpeg($file, $quality)
    {
        imagejpeg($this->resource, $file, $quality);

        return $this;
    }

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

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

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

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

    /**
     * Does this adapter supports type ?
     */
    protected function supports($type)
    {
        return imagetypes() & self::$gdTypes[$type];
    }

    protected function getColor($x, $y)
    {
        return imagecolorat($this->resource, $x, $y);
    }

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

        return $this;
    }
}
