package cz.jzitnik.screens;

import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.screen.TerminalScreen;
import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.game.ResourceManager;
import cz.jzitnik.states.TerminalState;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import com.googlecode.lanterna.screen.Screen.RefreshType;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

@Slf4j
public abstract class VideoPlayScreen extends Screen {
    @InjectDependency
    private ResourceManager resourceManager;

    @InjectState
    private TerminalState terminalState;

    private final String videoPath;
    protected boolean isRenderedAlready;

    public VideoPlayScreen(String videoPath) {
        this.videoPath = videoPath;
    }

    @Override
    public void fullRender() {
        if (!isRenderedAlready) {
            isRenderedAlready = true;
            render();
        }
    }

    protected void render() {
        File tempVideo = null;
        try (InputStream resource = resourceManager.getResourceAsStream(videoPath)) {

            TerminalScreen screen = terminalState.getTerminalScreen();
            var tg = terminalState.getTextGraphics();

            tempVideo = File.createTempFile("lanterna-video", ".mp4");
            tempVideo.deleteOnExit();
            Files.copy(resource, tempVideo.toPath(), StandardCopyOption.REPLACE_EXISTING);

            try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(tempVideo);
                 Java2DFrameConverter converter = new Java2DFrameConverter()) {

                grabber.start();

                double fps = grabber.getFrameRate();
                long frameDelayNs = (long) (1_000_000_000 / fps);

                long videoStartTime = System.nanoTime();
                long frameIndex = 0;

                // Track previous terminal size
                int prevTermWidth = screen.getTerminalSize().getColumns();
                int prevTermHeight = screen.getTerminalSize().getRows();

                Frame frame;
                while ((frame = grabber.grabImage()) != null) {

                    long now = System.nanoTime();
                    long expectedTime = videoStartTime + frameIndex * frameDelayNs;

                    // Skip frames if we're behind schedule
                    while (now > expectedTime) {
                        frame = grabber.grabImage();
                        if (frame == null) break;
                        frameIndex++;
                        expectedTime = videoStartTime + frameIndex * frameDelayNs;
                        now = System.nanoTime();
                    }

                    if (frame == null) break;

                    BufferedImage img = converter.convert(frame);
                    if (img == null) continue;

                    int termWidth = screen.getTerminalSize().getColumns();
                    int termHeight = screen.getTerminalSize().getRows();
                    int targetWidth = termWidth;
                    int targetHeight = termHeight * 2;

                    BufferedImage scaledImg = img;
                    if (img.getWidth() != targetWidth || img.getHeight() != targetHeight) {
                        scaledImg = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
                        scaledImg.getGraphics().drawImage(img, 0, 0, targetWidth, targetHeight, null);
                    }

                    for (int y = 0; y < termHeight; y++) {
                        for (int x = 0; x < termWidth; x++) {
                            int topPixel = scaledImg.getRGB(x, y * 2);
                            int bottomPixel = scaledImg.getRGB(x, y * 2 + 1);

                            TextColor.RGB bg = toColor(topPixel);
                            TextColor.RGB fg = toColor(bottomPixel);

                            tg.setBackgroundColor(bg);
                            tg.setForegroundColor(fg);
                            tg.setCharacter(x, y, '▄');
                        }
                    }

                    RefreshType refreshType;
                    if (termWidth != prevTermWidth || termHeight != prevTermHeight) {
                        refreshType = RefreshType.COMPLETE;
                        prevTermWidth = termWidth;
                        prevTermHeight = termHeight;
                    } else {
                        refreshType = RefreshType.DELTA;
                    }

                    screen.refresh(refreshType);

                    frameIndex++;
                    long nextFrameTime = videoStartTime + frameIndex * frameDelayNs;
                    long sleepTime = nextFrameTime - System.nanoTime();
                    if (sleepTime > 0) {
                        Thread.sleep(sleepTime / 1_000_000, (int) (sleepTime % 1_000_000));
                    }
                }

            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to play video", e);
        } finally {
            if (tempVideo != null && tempVideo.exists()) {
                if (!tempVideo.delete()) {
                    System.err.println("Warning: failed to delete temp video file " + tempVideo.getAbsolutePath());
                }
            }
        }
    }

    private TextColor.RGB toColor(int rgb) {
        int r = (rgb >> 16) & 0xFF;
        int g = (rgb >> 8) & 0xFF;
        int b = rgb & 0xFF;
        return new TextColor.RGB(r, g, b);
    }
}
