<?php

declare(strict_types=1);
/**
 * +----------------------------------------------------------------------
 * | ThinkAdmin Plugin for ThinkAdmin
 * +----------------------------------------------------------------------
 * | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
 * +----------------------------------------------------------------------
 * | 官方网站: https://thinkadmin.top
 * +----------------------------------------------------------------------
 * | 开源协议 ( https://mit-license.org )
 * | 免责声明 ( https://thinkadmin.top/disclaimer )
 * | 会员特权 ( https://thinkadmin.top/vip-introduce )
 * +----------------------------------------------------------------------
 * | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
 * | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
 * +----------------------------------------------------------------------
 */

namespace think\admin\extend;

use think\admin\Exception;

/**
 * 网站 ICO 文件生成工具.
 * @class FaviconExtend
 */
class FaviconExtend
{
    /**
     * 转换后的 BMP 图像.
     * @var array
     */
    private $images = [];

    /**
     * Constructor - 创建一个新的 ICO 生成器.
     * @param ?string $file 源图像文件的路径
     * @param array $size 图片文件尺寸 [W1,H1]
     * @throws Exception
     */
    public function __construct(?string $file = null, array $size = [])
    {
        $functions = [
            'imagesx',
            'imagesy',
            'getimagesize',
            'imagesavealpha',
            'imagecreatefromstring',
            'imagecreatetruecolor',
            'imagecolortransparent',
            'imagecolorallocatealpha',
            'imagecopyresampled',
            'imagealphablending',
        ];

        foreach ($functions as $function) {
            if (!function_exists($function)) {
                throw new Exception(lang('Required %s function not found.', [$function]));
            }
        }

        if (is_string($file)) {
            $this->addImage($file, $size);
        }
    }

    /**
     * 添加图像到生成器中.
     *
     * @param string $file 图像文件路径
     * @param array $size 图像文件尺寸
     * @throws Exception
     */
    public function addImage(string $file, array $size = []): FaviconExtend
    {
        if (false === ($im = $this->loadImageFile($file))) {
            throw new Exception(lang('Read picture file Failed.'));
        }
        if (empty($size)) {
            $size = [imagesx($im), imagesy($im)];
        }

        [$width, $height] = $size;
        $image = imagecreatetruecolor($width, $height);
        imagecolortransparent($image, imagecolorallocatealpha($image, 0, 0, 0, 127));
        imagealphablending($image, false);
        imagesavealpha($image, true);

        [$sourceWidth, $sourceHeight] = [imagesx($im), imagesy($im)];
        if (imagecopyresampled($image, $im, 0, 0, 0, 0, $width, $height, $sourceWidth, $sourceHeight) === false) {
            throw new Exception(lang('Parse and process picture Failed.'));
        }
        $this->addImageData($image);
        return $this;
    }

    /**
     * 将 ICO 内容写入到文件.
     *
     * @param string $file 写入文件路径
     */
    public function saveIco(string $file): bool
    {
        if (false === ($data = $this->getIcoData())) {
            return false;
        }
        if (false === ($fh = fopen($file, 'w'))) {
            return false;
        }
        if (fwrite($fh, $data) === false) {
            fclose($fh);
            return false;
        }
        fclose($fh);
        return true;
    }

    /**
     * 生成并获取 ICO 图像数据.
     */
    private function getIcoData()
    {
        if (!is_array($this->images) || empty($this->images)) {
            return false;
        }
        [$pixelData, $entrySize] = ['', 16];
        $data = pack('vvv', 0, 1, count($this->images));
        $offset = 6 + ($entrySize * count($this->images));
        foreach ($this->images as $image) {
            $data .= pack('CCCCvvVV', $image['width'], $image['height'], $image['colors'], 0, 1, $image['pixel'], $image['size'], $offset);
            $pixelData .= $image['data'];
            $offset += $image['size'];
        }
        $data .= $pixelData;
        unset($pixelData);
        return $data;
    }

    /**
     * 将 GD 图像转为 BMP 格式.
     * @param mixed $im
     */
    private function addImageData($im)
    {
        [$width, $height] = [imagesx($im), imagesy($im)];
        [$pixelData, $opacityData, $opacityValue] = [[], [], 0];
        for ($y = $height - 1; $y >= 0; --$y) {
            for ($x = 0; $x < $width; ++$x) {
                $color = imagecolorat($im, $x, $y);
                $alpha = ($color & 0x7F000000) >> 24;
                $alpha = (1 - ($alpha / 127)) * 255;
                $color &= 0xFFFFFF;
                $color |= 0xFF000000 & (intval($alpha) << 24);
                $pixelData[] = $color;
                $opacity = ($alpha <= 127) ? 1 : 0;
                $opacityValue = ($opacityValue << 1) | $opacity;
                if ((($x + 1) % 32) == 0) {
                    $opacityData[] = $opacityValue;
                    $opacityValue = 0;
                }
            }
            if (($x % 32) > 0) {
                while (($x++ % 32) > 0) {
                    $opacityValue = $opacityValue << 1;
                }
                $opacityData[] = $opacityValue;
                $opacityValue = 0;
            }
        }

        $imageHeaderSize = 40;
        $colorMaskSize = $width * $height * 4;
        $opacityMaskSize = (ceil($width / 32) * 4) * $height;
        $data = pack('VVVvvVVVVVV', 40, $width, $height * 2, 1, 32, 0, 0, 0, 0, 0, 0);
        foreach ($pixelData as $color) {
            $data .= pack('V', $color);
        }
        foreach ($opacityData as $opacity) {
            $data .= pack('N', $opacity);
        }

        $this->images[] = [
            'data' => $data,
            'size' => $imageHeaderSize + $colorMaskSize + $opacityMaskSize,
            'width' => $width, 'height' => $height,
            'pixel' => 32, 'colors' => 0,
        ];
    }

    /**
     * 读取图片资源.
     * @param string $file 文件路径
     * @return false|\GdImage|resource
     */
    private function loadImageFile(string $file)
    {
        if (getimagesize($file) === false) {
            return false;
        }
        if (false === ($data = file_get_contents($file))) {
            return false;
        }
        if (false === ($image = @imagecreatefromstring($data))) {
            return false;
        }
        unset($data);
        return $image;
    }
}