package com.grapeshot.halfnes;

import com.grapeshot.halfnes.ui.ControllerImpl;
import com.grapeshot.halfnes.ui.GUIInterface;
import com.grapeshot.halfnes.ui.OnScreenMenu;
import com.grapeshot.halfnes.video.NesColors;
import java.nio.ByteBuffer;
import java.util.List;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritablePixelFormat;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCombination;
import javafx.scene.paint.Color;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;

/**
 * @author Stephen Chin - [email protected]
 */
public class JavaFXNES extends Application implements GUIInterface {


    // Set the overscan insets to match your config
    // And make sure your framebuffer is set to:
    // * screen.width + overscan.right
    // * screen.height + overscan.bottom
    
    //overscan for PC
    private static final Insets overscan = new Insets(0, 0, 0, 0);
    
    //overscan for Pi screen
    //private static final Insets overscan = new Insets(-59, 160, 150, 0);
    private static final Insets extraOverscan = new Insets(8, 0, 8, 0);

    private NES nes;
    private Canvas gameCanvas;
    private Stage stage;
    private OnScreenMenu menu;

    @Override
    public void start(Stage stage) throws Exception {
        this.stage = stage;
        //Rectangle2D bounds = Screen.getPrimary().getBounds();
        Rectangle2D bounds = new Rectangle2D(0,0,640,480);
        gameCanvas = new Canvas(256, 240);
        stage.addEventHandler(javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST, e -> nes.quit());
        menu = new OnScreenMenu(this);
        //menu.setPadding(extraOverscan);
        menu.setPrefWidth(256);
        menu.setPrefHeight(240);
        Group root = new Group(gameCanvas, menu);
        Scene scene = new Scene(root, bounds.getWidth(), bounds.getHeight(), Color.BLACK);
        stage.setScene(scene);
        //stage.setFullScreen(true);
        stage.setFullScreenExitKeyCombination(KeyCombination.valueOf("F11"));
        stage.addEventHandler(javafx.scene.input.KeyEvent.KEY_PRESSED, e -> {
            if (e.getCode().equals(KeyCode.ESCAPE)) {
                menu.show();
            }
        });
        root.setLayoutX(overscan.getRight() - overscan.getLeft() - extraOverscan.getLeft() * bounds.getWidth() / 256);
        root.setLayoutY(overscan.getBottom() - overscan.getTop() - extraOverscan.getTop() * bounds.getHeight() / 240);
        root.getTransforms().add(new Scale(
            (bounds.getWidth() - (overscan.getRight() - overscan.getLeft())) / (256 - extraOverscan.getLeft() - extraOverscan.getRight()),
            (bounds.getHeight() - (overscan.getBottom() - overscan.getTop())) / (240 - extraOverscan.getTop() - extraOverscan.getBottom())));
        nes = new NES(this);
        ControllerImpl padController1 = new ControllerImpl(scene, 0);
        ControllerImpl padController2 = new ControllerImpl(scene, 1);
        padController1.startEventQueue();
        padController2.startEventQueue();
        nes.setControllers(padController1, padController2);
        final List<String> params = getParameters().getRaw();
        new Thread(() -> {
            if (params.isEmpty()) {
                nes.run();
            } else {
                nes.run(params.get(0));
            }
        }, "Game Thread").start();
    }

    public static void main(String[] args) {
        JInputHelper.setupJInput();
        launch(args);
    }

    @Override
    public NES getNes() {
        return nes;
    }

    @Override
    public void setNES(NES nes) {
        this.nes = nes;
    }

    final byte[] buffer = new byte[256 * 240 * 4];
    final WritablePixelFormat<ByteBuffer> format = WritablePixelFormat.getByteBgraPreInstance();

    private final long[] frametimes = new long[60];
    private int frametimeptr = 0;
    private double fps;

    @Override
    public void setFrame(int[] nespixels, int[] bgcolor, boolean dotcrawl) {
        Platform.runLater(() -> {
            frametimes[frametimeptr] = nes.getFrameTime();
            ++frametimeptr;
            frametimeptr %= frametimes.length;

            if (frametimeptr == 0) {
                long averageframes = 0;
                for (long l : frametimes) {
                    averageframes += l;
                }
                averageframes /= frametimes.length;
                fps = 1E9 / averageframes;
                stage.setTitle(String.format("HalfNES %s, %2.2f fps",
                    //                    + ((nes.frameskip > 0) ? " frameskip " + nes.frameskip : ""),
                    NES.VERSION,
                    //                    nes.getCurrentRomName(),
                    fps));
            }
            PixelWriter writer = gameCanvas.getGraphicsContext2D().getPixelWriter();
            for (int i = 0; i < nespixels.length; i++) {
                byte[] colbytes = NesColors.colbytes[(nespixels[i] & 0x1c0) >> 6][nespixels[i] & 0x3f];
                System.arraycopy(colbytes, 0, buffer, i * 4, 3);
            }
            writer.setPixels(0, 0, 256, 240, format, buffer, 0, 256 * 4);
        });
    }

    @Override
    public void messageBox(String message) {
        System.out.println("message = " + message);
    }

    @Override
    public void run() {
        Platform.runLater(() -> {
            stage.show();
            menu.show();
        });
    }

    @Override
    public void render() {
        // whatever...
    }

    public void loadROMs(String path) {
        menu.loadROMs(path);
    }
}