<?php
//作成者：https://note.com/tekito_note
//もし不具合があった場合、ご連絡ください。

// 設定
$uploadDir = 'images/'; // 画像アップロードディレクトリ
$miniDir = 'images/mini/';//サムネイルディレクトリ
$thumbnailWidth = 250;//サムネイルのサイズ(px)
$fontFile = 'Mplus1-Regular.ttf'; //使用するフォントファイル
$resizeOption = '1'; // 画像サイズ制限オプション（1 = 有効, 0 = 無効）
$maxDimension = 1000; // 最大サイズ
$defaultWatermark = 'watermark.png';  // 透かし画像（複数使う場合は配列で指定: ['a.png', 'b.png']）
$watermarkScale = 0.2;//透かし画像の倍率(対横幅)
$fileNamingMode   = 1;// ファイル名モード: 1 = 元ファイル名 2 = ランダムな英数字（12文字） 3 = 日時+連番_ユーザーID
$maxFileCount       = 20;               // 一度にアップロードできる最大ファイル数
$maxFileSizeMB      = 20;  // 1ファイルあたりの最大サイズ（MB）
$convertToWebpOption = '0'; // WebP変換オプション（1 = 有効, 0 = 無効）

$allowedExtensions  = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$allowedWatermarks  = (array) $defaultWatermark;
$fileNames = [];

// ディレクトリがない場合作成
foreach ([$uploadDir, $miniDir] as $dir) {
    if (!file_exists($dir) && !mkdir($dir, 0755, true)) {
        throw new Exception("{$dir} の作成に失敗しました");
    }
}

// 設定値を返すエンドポイント
if (isset($_GET['getConfig'])) {
    header('Content-Type: application/json');
    echo json_encode([
        'convertToWebpOption' => $convertToWebpOption,
        'resizeOption'        => $resizeOption,
        'maxDimension'        => $maxDimension,
        'fileNamingMode'      => $fileNamingMode,
        'maxFileCount'        => $maxFileCount,
        'maxFileSizeMB'       => $maxFileSizeMB,
        'thumbnailWidth'      => $thumbnailWidth,
    ]);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    header('Content-Type: application/json');
    $renames = [];
    try {
        if (isset($_FILES['files']) && count($_FILES['files']['name']) > 0) {
            if (count($_FILES['files']['name']) > $maxFileCount) {
                http_response_code(400);
                echo json_encode(['error' => "一度にアップロードできるファイル数は{$maxFileCount}枚までです"]);
                exit;
            }

            $memoryLimit = getMemoryLimitBytes();

            foreach ($_FILES['files']['tmp_name'] as $index => $fileTmpPath) {
                try {
                    if (memory_get_usage() > $memoryLimit * 0.8) {
                        throw new Exception("メモリ不足のため処理を中断しました");
                    }

                    if ($_FILES['files']['error'][$index] !== UPLOAD_ERR_OK) continue;

                    if ($_FILES['files']['size'][$index] > $maxFileSizeMB * 1024 * 1024) {
                        throw new Exception("ファイルサイズが上限を超えています");
                    }

                    $fileExtension = strtolower(pathinfo($_FILES['files']['name'][$index], PATHINFO_EXTENSION));
                    if (!in_array($fileExtension, $allowedExtensions)) continue;

                    $savedExt = ($convertToWebpOption === '1' && $fileExtension !== 'webp') ? 'webp' : $fileExtension;

                    // クライアントが選択時に決めたファイル名をベース名として使用する
                    // （本文タグと保存ファイル名を一致させるため。安全な文字のみ許可）
                    $clientFileName = $_POST['fileNames'][$index] ?? $_FILES['files']['name'][$index];
                    $basePart = pathinfo(mb_convert_encoding($clientFileName, 'UTF-8', 'AUTO'), PATHINFO_FILENAME);
                    $baseName = str_replace([' ', '.'], '_', preg_replace('/[^\p{L}\p{N}\s\-\_]/u', '', $basePart));
                    if ($baseName === '') {
                        $baseName = substr(bin2hex(random_bytes(6)), 0, 12);
                    }

                    // move_uploaded_file は元の拡張子で保存（WebP変換は加工後に行う）
                    $tempFileName = $baseName . '.' . $fileExtension;
                    $dest_path    = $uploadDir . $tempFileName;
                    for ($counter = 1; file_exists($dest_path); $counter++) {
                        $tempFileName = $baseName . '_' . $counter . '.' . $fileExtension;
                        $dest_path    = $uploadDir . $tempFileName;
                    }
                    // WebP変換後の最終ファイル名
                    $newFileName = pathinfo($tempFileName, PATHINFO_FILENAME) . '.' . $savedExt;

                    if (!move_uploaded_file($fileTmpPath, $dest_path)) continue;

                    // 実際に画像として読めるか検証
                    if (@getimagesize($dest_path) === false) {
                        unlink($dest_path);
                        throw new Exception("無効な画像ファイルです");
                    }

                    $originalImagePath = $uploadDir . 'temp_original_' . $index . '.' . $fileExtension;
                    copy($dest_path, $originalImagePath);

                    removeICCProfile($dest_path);
                    if (in_array($fileExtension, ['jpg', 'jpeg'])) fixOrientation($dest_path);
                    if ($resizeOption === '1') resizeImage($dest_path, $maxDimension);

                    // 加工処理
                    $needsProcessing = false;
                    if (($_POST['insertText'] ?? '') === 'yes') {
                        addTextAreaToImage($dest_path, $_POST['customText']);
                        $needsProcessing = true;
                    }
                    if (($_POST['applyWatermark'] ?? '') === 'yes') {
                        $watermarkFile = basename($_POST['watermarkFile'] ?? $defaultWatermark);
                        if (!in_array($watermarkFile, $allowedWatermarks)) $watermarkFile = $defaultWatermark;
                        applyWatermark($dest_path, $watermarkFile, $_POST['watermarkPosition'] ?? 'center');
                        $needsProcessing = true;
                    }

                    // サムネイル作成（WebP変換前に元形式で処理する）
                    if (($_POST['createThumbnail'] ?? '') === 'yes') {
                        $thumbSource      = ($needsProcessing && ($_POST['applyThumbnail'] ?? '0') === '1') ? $dest_path : $originalImagePath;
                        $thumbnailPath    = createThumbnail($thumbSource, $miniDir, $fileExtension);
                        // WebP変換は拡張子が元形式のままの状態で行う（rename前に実施）
                        if ($convertToWebpOption === '1' && $fileExtension !== 'webp') {
                            $thumbnailPath = convertImageToWebp($thumbnailPath, $miniDir);
                        }
                        $thumbnailNewPath = $miniDir . pathinfo($newFileName, PATHINFO_FILENAME) . '.' . $savedExt;
                        if ($thumbnailPath !== $thumbnailNewPath) {
                            rename($thumbnailPath, $thumbnailNewPath);
                        }
                    }

                    // WebP変換（すべての加工後）
                    if ($convertToWebpOption === '1' && $fileExtension !== 'webp') {
                        $dest_path = convertImageToWebp($dest_path, $uploadDir);
                    }

                    unlink($originalImagePath);

                    $actualName   = basename($dest_path);
                    $expectedName = $_POST['displayNames'][$index] ?? $actualName;
                    if ($expectedName !== $actualName) {
                        $renames[$expectedName] = $actualName;
                    }
                    $fileNames[] = $actualName;

                } catch (Exception $e) {
                    error_log("Error processing file: " . $e->getMessage());
                    http_response_code(500);
                    echo json_encode(['error' => '画像の処理中にエラーが発生しました']);
                    exit;
                }
            }
            echo json_encode(['files' => $fileNames, 'renames' => $renames]);
        } else {
            echo json_encode(['files' => [], 'renames' => []]);
        }
    } catch (Exception $e) {
        error_log("Error in main process: " . $e->getMessage());
        http_response_code(500);
        echo json_encode(['error' => 'サーバーエラーが発生しました']);
    }
}

// ── ヘルパー関数 ──────────────────────────────────────────────

// memory_limit設定値をバイト数に変換（-1=無制限はPHP_INT_MAXを返す）
function getMemoryLimitBytes(): int {
    $val = ini_get('memory_limit');
    if ($val === '-1') return PHP_INT_MAX;
    $unit = strtolower(substr($val, -1));
    $num  = (int) $val;
    switch ($unit) {
        case 'g': return $num * 1024 * 1024 * 1024;
        case 'm': return $num * 1024 * 1024;
        case 'k': return $num * 1024;
        default:  return $num;
    }
}

function imageCreateFromExt(string $filePath, string $ext) {
    // 不正なICCプロファイル等によるlibpngの警告がレスポンスに混入し
    // JSONを破壊することがあるため、@で抑制する
    switch ($ext) {
        case 'jpg':
        case 'jpeg': return @imagecreatefromjpeg($filePath);
        case 'png':  return @imagecreatefrompng($filePath);
        case 'gif':  return @imagecreatefromgif($filePath);
        case 'webp': return @imagecreatefromwebp($filePath);
        default:     return false;
    }
}

function imageSaveByExt($image, string $filePath, string $ext): void {
    switch ($ext) {
        case 'jpg':
        case 'jpeg': imagejpeg($image, $filePath); break;
        case 'png':  imagepng($image, $filePath);  break;
        case 'gif':  imagegif($image, $filePath);  break;
        case 'webp': imagewebp($image, $filePath); break;
    }
}

// JPEG の Orientation を物理回転に変換
function fixOrientation(string $filePath): void {
    if (!function_exists('exif_read_data')) return;
    $exif = @exif_read_data($filePath);
    $orientation = $exif['Orientation'] ?? 1;
    if ($orientation === 1) return;

    $image = @imagecreatefromjpeg($filePath);
    if (!$image) return;

    // flip系は先にインプレース処理してから $image を回転対象にする
    if (in_array($orientation, [2, 4, 5, 7])) {
        imageflip($image, in_array($orientation, [2, 5, 7]) ? IMG_FLIP_HORIZONTAL : IMG_FLIP_VERTICAL);
    }
    switch ($orientation) {
        case 3:
        case 4:  $degrees = 180; break;
        case 5:
        case 6:  $degrees = -90; break;
        case 7:
        case 8:  $degrees = 90;  break;
        default: $degrees = 0;
    }
    $result = $degrees !== 0 ? imagerotate($image, $degrees, 0) : $image;
    imagejpeg($result, $filePath);
    if ($result !== $image) imagedestroy($result);
    imagedestroy($image);
}

// ICCプロファイル除去
function removeICCProfile(string $filePath): void {
    $ext   = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $image = @imageCreateFromExt($filePath, $ext);
    if ($image === false) return;
    imagealphablending($image, false);
    imagesavealpha($image, true);
    imageSaveByExt($image, $filePath, $ext);
    imagedestroy($image);
}

// リサイズ
function resizeImage(string $filePath, int $maxDimension): void {
    [$width, $height] = getimagesize($filePath);
    if ($width <= $maxDimension && $height <= $maxDimension) return;

    if ($width > $height) {
        $newWidth  = $maxDimension;
        $newHeight = (int) ($maxDimension * $height / $width);
    } else {
        $newHeight = $maxDimension;
        $newWidth  = (int) ($maxDimension * $width / $height);
    }

    $ext    = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $source = imageCreateFromExt($filePath, $ext);
    if (!$source) return;

    $resized = imagecreatetruecolor($newWidth, $newHeight);
    imagealphablending($resized, false);
    imagesavealpha($resized, true);
    imagecopyresampled($resized, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
    imageSaveByExt($resized, $filePath, $ext);
    imagedestroy($source);
    imagedestroy($resized);
}

// サムネイル作成
function createThumbnail(string $filePath, string $miniDir, string $fileExtension): string {
    global $thumbnailWidth;

    $source = imageCreateFromExt($filePath, $fileExtension);
    $srcW   = imagesx($source);
    $srcH   = imagesy($source);
    $thumbH = (int) ($srcH * $thumbnailWidth / $srcW);

    $thumb = imagecreatetruecolor($thumbnailWidth, $thumbH);
    imagealphablending($thumb, false);
    imagesavealpha($thumb, true);
    imagecopyresampled($thumb, $source, 0, 0, 0, 0, $thumbnailWidth, $thumbH, $srcW, $srcH);

    $thumbnailPath = $miniDir . pathinfo($filePath, PATHINFO_FILENAME) . '.' . pathinfo($filePath, PATHINFO_EXTENSION);
    imageSaveByExt($thumb, $thumbnailPath, $fileExtension);
    imagedestroy($source);
    imagedestroy($thumb);

    return $thumbnailPath;
}

// 画像をWebPに変換して保存（元ファイルは削除）。変換後のパスを返す
function convertImageToWebp(string $filePath, string $dir): string {
    $ext      = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $webpPath = $dir . pathinfo($filePath, PATHINFO_FILENAME) . '.webp';
    $image    = imageCreateFromExt($filePath, $ext);
    if ($image) {
        // パレット画像（透過PNG/GIF等）のままimagewebpに渡すと警告が出力され
        // レスポンスのJSONが壊れるため、事前にトゥルーカラーへ変換する
        if (!imageistruecolor($image)) {
            imagepalettetotruecolor($image);
        }
        imagewebp($image, $webpPath);
        imagedestroy($image);
        unlink($filePath);
    }
    return $webpPath;
}

// テキスト挿入
function addTextAreaToImage(string $filePath, string $text): void {
    global $fontFile;

    $text  = preg_replace('/[[:cntrl:]]/', '', $text);
    $ext   = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $image = imageCreateFromExt($filePath, $ext);
    if (!$image) return;

    $w         = imagesx($image);
    $h         = imagesy($image);
    $fontSize  = $h * 0.02;
    $textColor = imagecolorallocatealpha($image, 0, 0, 0, 0);
    $outline   = imagecolorallocatealpha($image, 255, 255, 255, 0);
    $text      = mb_convert_encoding($text, 'UTF-8', 'auto');
    $bbox      = imagettfbbox($fontSize, 0, $fontFile, $text);
    $x         = $w - ($bbox[2] - $bbox[0]) - 20;
    $y         = $h - 20;

    foreach ([[-1,-1],[1,-1],[-1,1],[1,1]] as [$ox, $oy]) {
        imagettftext($image, $fontSize, 0, $x + $ox, $y + $oy, $outline, $fontFile, $text);
    }
    imagettftext($image, $fontSize, 0, $x, $y, $textColor, $fontFile, $text);
    imageSaveByExt($image, $filePath, $ext);
    imagedestroy($image);
}

// 透かし合成
function applyWatermark(string $filePath, string $watermarkPath, string $position): void {
    global $watermarkScale;

    if (!$watermarkPath || !file_exists($watermarkPath)) {
        error_log("警告: 透かし画像 '{$watermarkPath}' が見つかりません");
        return;
    }
    $watermark = @imagecreatefrompng($watermarkPath);
    if (!$watermark) {
        error_log("警告: 透かし画像 '{$watermarkPath}' を読み込めません");
        return;
    }

    $ext   = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
    $image = imageCreateFromExt($filePath, $ext);
    if (!$image) { imagedestroy($watermark); return; }

    [$imgW, $imgH] = getimagesize($filePath);
    [$wmW,  $wmH]  = getimagesize($watermarkPath);
    $newWmW = (int) floor($imgW * $watermarkScale);
    $newWmH = (int) floor($wmH / $wmW * $newWmW);

    $resized = imagecreatetruecolor($newWmW, $newWmH);
    imagealphablending($resized, false);
    imagesavealpha($resized, true);
    imagecopyresampled($resized, $watermark, 0, 0, 0, 0, $newWmW, $newWmH, $wmW, $wmH);
    imagedestroy($watermark);

    imagealphablending($image, true);
    imagesavealpha($image, true);

    if ($position === 'repeat') {
        for ($x = 0; $x < $imgW; $x += $newWmW) {
            for ($y = 0; $y < $imgH; $y += $newWmH) {
                imagecopy($image, $resized, $x, $y, 0, 0, $newWmW, $newWmH);
            }
        }
    } else {
        switch ($position) {
            case 'center':
                $x = (int) floor(($imgW - $newWmW) / 2);
                $y = (int) floor(($imgH - $newWmH) / 2);
                break;
            case 'top-right':
                $x = (int) floor($imgW - $newWmW);
                $y = 0;
                break;
            case 'bottom-left':
                $x = 0;
                $y = (int) floor($imgH - $newWmH);
                break;
            case 'bottom-right':
                $x = (int) floor($imgW - $newWmW);
                $y = (int) floor($imgH - $newWmH);
                break;
            default:
                $x = 0;
                $y = 0;
        }
        imagecopy($image, $resized, $x, $y, 0, 0, $newWmW, $newWmH);
    }

    imageSaveByExt($image, $filePath, $ext);
    imagedestroy($image);
    imagedestroy($resized);
}
