<?php

namespace pictpostpersonal;

class ImageUtility
{
    /**
     * 画像ファイルをアップロード（必要に応じてリサイズ・変換・EXIF回転）します。
     *
     * @param string   $source_path 元画像ファイルのパス
     * @param string   $dest_path   保存先のパス。末尾が ".<ext>" なら元画像の拡張子を維持し、
     *                              例えば "/path/to/filename.jpg" のように指定されると指定の拡張子に変換します。
     *                              対応フォーマットは jpg, png, gif, webp です。
     * @param int|null $max_width   オプション：リサイズ時の最大幅（ピクセル）
     * @param int|null $max_height  オプション：リサイズ時の最大高（ピクセル）
     *
     * @throws \RuntimeException ファイルが存在しない、画像でない、または処理中にエラー発生時
     */
    static public function createResizeImage(string $source_path, string $dest_path, ?int $max_width = null, ?int $max_height = null, $quality = 100, bool $remove_exif = false): array
    {
        // ソースファイルの存在チェック
        if (!file_exists($source_path)) {
            throw new \RuntimeException("ソースファイルが存在しません: {$source_path}");
        }

        // 画像情報の取得
        $image_info = getimagesize($source_path);
        if ($image_info === false) {
            throw new \RuntimeException("ファイルは画像ではありません: {$source_path}");
        }

        // 画像の幅・高さおよびMIMEタイプを取得
        [$src_width, $src_height] = $image_info;
        $mime = $image_info['mime'];

        // 元画像のタイプ（GD用識別子）を決定
        $source_type = self::getImageTypeFromMime($mime);
        if ($source_type === null) {
            throw new \RuntimeException("サポートされていない画像タイプ: {$mime}");
        }

        // 保存形式の決定
        // 保存先パスの拡張子を抽出。末尾が ".<ext>" なら元画像の拡張子を維持、
        // それ以外の場合は指定された拡張子に変換します。
        $dest_ext = strtolower(pathinfo($dest_path, PATHINFO_EXTENSION));
        if ($dest_ext === '<ext>' || $dest_ext === '') {
            $target_format = $source_type;
            $dest_path = preg_replace('/<ext>$/', self::getExtensionFromType($source_type), $dest_path);
        } else {
            if (!in_array($dest_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'], true)) {
                throw new \RuntimeException("対応していない保存形式: {$dest_ext}");
            }
            $target_format = ($dest_ext === 'jpeg') ? 'jpg' : $dest_ext;
        }

        // 元画像リソースの生成
        $src_image = self::createImageResource($source_path, $source_type);
        if (!$src_image) {
            throw new \RuntimeException("画像リソースの生成に失敗しました: {$source_path}");
        }

        // EXIF情報による回転（JPEGの場合のみ）：
        // ※ 回転はリサイズ前に行う必要があるため、ここで実施します。
        $rotation_applied = false;
        $exif = null;
        if ($source_type === 'jpg') {
            // EXIF情報の読み込み（存在しない場合の警告は抑制）
            $exif = @exif_read_data($source_path);
            if ($exif !== false && !empty($exif['Orientation'])) {
                switch ($exif['Orientation']) {
                    case 3:
                        $src_image = imagerotate($src_image, 180, 0);
                        $rotation_applied = true;
                        break;
                    case 6:
                        $src_image = imagerotate($src_image, -90, 0);
                        $rotation_applied = true;
                        break;
                    case 8:
                        $src_image = imagerotate($src_image, 90, 0);
                        $rotation_applied = true;
                        break;
                        // Orientationが1やその他の場合は何もしない
                }
                if ($rotation_applied) {
                    // 回転後のサイズを再取得
                    $src_width  = imagesx($src_image);
                    $src_height = imagesy($src_image);
                }
            }
        }

        // リサイズサイズの算出（オプションが指定されている場合）
        $new_width  = $src_width;
        $new_height = $src_height;
        if ($max_width !== null || $max_height !== null) {
            // 片方のみ指定の場合、もう片方は元画像サイズと同じ扱いとする
            $max_width  = $max_width != 0 ? $max_width : $src_width;
            $max_height = $max_height != 0 ? $max_height : $src_height;

            if ($src_width > $max_width || $src_height > $max_height) {
                $ratio = min($max_width / $src_width, $max_height / $src_height);
                $new_width  = (int)round($src_width * $ratio);
                $new_height = (int)round($src_height * $ratio);
            }
        }

        // 入力と出力のフォーマットが同じ、かつリサイズが不要で回転も行われていない場合は、
        // 単純にファイルコピーを実施
        if (!$rotation_applied && $new_width === $src_width && $new_height === $src_height && $source_type === $target_format && (!$remove_exif && !$exif)) {
            if (!copy($source_path, $dest_path)) {
                imagedestroy($src_image);
                throw new \RuntimeException("ファイルコピーに失敗しました: {$dest_path}");
            }
            imagedestroy($src_image);
            return ['width' => $new_width, 'height' => $new_height];
        }

        // リサイズが必要な場合、またはフォーマット変換・EXIF回転が発生している場合は、
        // GD を用いて画像リソースの作成・リサイズを行います。
        if ($new_width !== $src_width || $new_height !== $src_height) {
            $dst_image = imagecreatetruecolor($new_width, $new_height);
            if (!$dst_image) {
                imagedestroy($src_image);
                throw new \RuntimeException('リサイズ用画像リソースの生成に失敗しました');
            }
            // PNG, GIF, WEBPの場合は透過情報を保持
            self::preserveTransparency($dst_image, $target_format);

            if (!imagecopyresampled(
                $dst_image,
                $src_image,
                0,
                0,
                0,
                0,
                $new_width,
                $new_height,
                $src_width,
                $src_height
            )) {
                imagedestroy($src_image);
                imagedestroy($dst_image);
                throw new \RuntimeException('画像のリサイズに失敗しました');
            }
        } else {
            // リサイズ不要の場合は、回転や形式変換が必要なため、そのまま使用
            $dst_image = $src_image;
        }

        // 保存処理
        $saved = self::saveImageResource($dst_image, $dest_path, $target_format, $quality);
        imagedestroy($src_image);
        if ($dst_image !== $src_image) {
            imagedestroy($dst_image);
        }

        if (!$saved) {
            throw new \RuntimeException("画像の保存に失敗しました: {$dest_path}");
        }

        // パーミッションを644(rw-:r--:r--)に設定
        chmod($dest_path, 0644);

        return ['width' => $new_width, 'height' => $new_height];
    }

    /**
     * MIMEタイプから対応する画像フォーマットを返す
     *
     * @param string $mime
     * @return string|null jpg, png, gif, webp のいずれか。未対応の場合は null
     */
    static private function getImageTypeFromMime(string $mime): ?string
    {
        switch ($mime) {
            case 'image/jpeg':
                return 'jpg';
            case 'image/png':
                return 'png';
            case 'image/gif':
                return 'gif';
            case 'image/webp':
                return 'webp';
            default:
                return null;
        }
    }

    /**
     * 内部で扱う画像タイプから一般的な拡張子を返す
     *
     * @param string $type
     * @return string
     */
    static private function getExtensionFromType(string $type): string
    {
        return $type === 'jpg' ? 'jpg' : $type;
    }

    /**
     * 画像ファイルから GD イメージリソースを生成する
     *
     * @param string $file_path
     * @param string $type     jpg, png, gif, webp
     * @return \GdImage|false
     */
    static private function createImageResource(string $file_path, string $type)
    {
        switch ($type) {
            case 'jpg':
                return \imagecreatefromjpeg($file_path);
            case 'png':
                return \imagecreatefrompng($file_path);
            case 'gif':
                return \imagecreatefromgif($file_path);
            case 'webp':
                if (function_exists('imagecreatefromwebp')) {
                    return imagecreatefromwebp($file_path);
                }
                return false;
            default:
                return false;
        }
    }

    /**
     * GD イメージリソースを指定のパス・形式で保存する
     *
     * @param \GdImage $image
     * @param string   $dest_path
     * @param string   $format   jpg, png, gif, webp
     * @return bool
     */
    static private function saveImageResource(\GdImage $image, string $dest_path, string $format, $quality = 100): bool
    {
        $result = false;
        switch ($format) {
            case 'jpg':
                $result = \imagejpeg($image, $dest_path, $quality);
                break;
            case 'png':
                $result = \imagepng($image, $dest_path, 6);
                break;
            case 'gif':
                $result = \imagegif($image, $dest_path);
                break;
            case 'webp':
                if (function_exists('imagewebp')) {
                    $result = \imagewebp($image, $dest_path, $quality);
                }
                break;
        }
        return $result;
    }

    /**
     * 透過情報の保持処理。PNG, GIF, WEBP の場合に透過情報を保持するための設定を行う
     *
     * @param \GdImage $image
     * @param string   $format  jpg, png, gif, webp
     */
    static private function preserveTransparency(\GdImage $image, string $format): void
    {
        if (in_array($format, ['png', 'gif', 'webp'], true)) {
            imagealphablending($image, false);
            imagesavealpha($image, true);
            $transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
            imagefilledrectangle($image, 0, 0, imagesx($image), imagesy($image), $transparent);
        }
    }
}
