package dev.photosync.platform.impl;

import com.mojang.blaze3d.platform.NativeImage;
import dev.photosync.mcapi.capture.ScreenshotService;
import lombok.extern.slf4j.Slf4j;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;

/**
 * Takes screenshots on the mod's own terms.
 *
 * <p>Deliberately not routed through vanilla's {@code Screenshot.grab}: that one
 * names the file itself, writes a chat message, and -- since we intercept its
 * write to notice the player's own F2 presses -- would make auto-captures
 * indistinguishable from manual ones. Grabbing the frame and writing it here
 * keeps the two origins apart and gives the caller the path it asked for.
 */
@Slf4j
public final class CaptureAdapter implements ScreenshotService {

    private static final DateTimeFormatter STAMP =
            DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss", Locale.ROOT);

    @Override
    public Path directory() {
        Path directory = Minecraft.getInstance().gameDirectory.toPath().resolve("screenshots");
        try {
            Files.createDirectories(directory);
        } catch (IOException e) {
            throw new UncheckedIOException("Cannot create the screenshot directory " + directory, e);
        }
        return directory;
    }

    @Override
    public CompletableFuture<Path> capture(String fileNameSuffix) {
        CompletableFuture<Path> written = new CompletableFuture<>();
        Minecraft game = Minecraft.getInstance();
        // The framebuffer can only be read on the render thread; the PNG encode
        // that follows must not happen there, so it hops to the IO pool.
        //
        // 1.21.5 turned the readback asynchronous: takeScreenshot no longer
        // returns the frame, it hands it to a callback once the GPU fence
        // clears, possibly frames later. The name is still claimed up front so
        // that captures land in the order they were asked for.
        Runnable grab = () -> {
            try {
                Path file = reserveFile(fileNameSuffix);
                Screenshot.takeScreenshot(game.getMainRenderTarget(),
                        frame -> Util.ioPool().execute(() -> write(frame, file, written)));
            } catch (IOException | RuntimeException e) {
                written.completeExceptionally(e);
            }
        };
        if (game.isSameThread()) {
            grab.run();
        } else {
            game.execute(grab);
        }
        return written;
    }

    private void write(NativeImage frame, Path file, CompletableFuture<Path> written) {
        try (NativeImage owned = frame) {
            owned.writeToFile(file);
            written.complete(file);
        } catch (IOException | RuntimeException e) {
            try {
                Files.deleteIfExists(file);
            } catch (IOException cleanup) {
                log.warn("Left an empty screenshot behind at {}", file, cleanup);
            }
            written.completeExceptionally(e);
        }
    }

    /**
     * Vanilla's naming scheme plus the configured suffix: two captures in the
     * same second get {@code _1}, {@code _2}, and so on.
     *
     * <p>The name is claimed by creating the file empty rather than by testing
     * for absence, because the write happens later on another thread -- two
     * captures a moment apart would otherwise agree on a name and one would
     * overwrite the other.
     */
    private synchronized Path reserveFile(String fileNameSuffix) throws IOException {
        Path directory = directory();
        String stamp = LocalDateTime.now().format(STAMP);
        String suffix = fileNameSuffix == null ? "" : fileNameSuffix;
        for (int attempt = 0; ; attempt++) {
            String name = attempt == 0
                    ? stamp + suffix + ".png"
                    : stamp + "_" + attempt + suffix + ".png";
            try {
                return Files.createFile(directory.resolve(name));
            } catch (FileAlreadyExistsException taken) {
                // Somebody -- us a second ago, or vanilla's own F2 -- got there first.
            }
        }
    }
}
