package nz.ac.massey.javaecs.examples;

import java.io.FileInputStream;
import java.util.concurrent.Semaphore;

import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Control;
import nz.ac.massey.javaecs.examples.Components.Input;
import nz.ac.massey.javaecs.examples.Components.Render;
import nz.ac.massey.javaecs.examples.Components.Sprite;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
import nz.ac.massey.javaecs.examples.Systems.ControlSystem;
import nz.ac.massey.javaecs.examples.Systems.PlayerSystem;
import nz.ac.massey.javaecs.examples.Systems.RenderSystem;

/**
 * Hello world!
 */
public class App {
    static App thisApp;
    public App() {
        thisApp = this;
    }
    static Engine gameEngine;
    public static Stage worldStage;
    public static Group worldGroup;
    static Integer syncManager = 0;
    static Semaphore rendererFinished = new Semaphore(0);
    /**
     * Says hello to the world.
     * @param args The arguments of the program.
     */
    public static void main(String[] args) throws Exception{
        /************************************
        **        Initialise engine        **
        *************************************/
        gameEngine = new Engine(16384);

        /************************************
        **    Register game components     **
        *************************************/
        gameEngine.registerComponent(Control.class);
        gameEngine.registerComponent(Render.class);
        gameEngine.registerComponent(Sprite.class);
        gameEngine.registerComponent(Vec2D.class);
        gameEngine.registerComponent(Input.class);


        /************************************
        ** Register and initialise systems **
        *************************************/
        ControlSystem controlSystem = new ControlSystem(gameEngine);
        gameEngine.registerSystem(ControlSystem.class, controlSystem);

        RenderSystem renderSystem = new RenderSystem(gameEngine, 5.);
        gameEngine.registerSystem(RenderSystem.class, renderSystem);

        PlayerSystem playerSystem = new PlayerSystem(gameEngine);
        gameEngine.registerSystem(PlayerSystem.class, playerSystem);



        /************************************
        **       Create known entities     **
        *************************************/
        // Input controller - gets player input
        Entity inputEntity = gameEngine.createEntity();
        gameEngine.addComponent(inputEntity, Input.class, new Input());

        Entity playerEntity = gameEngine.createEntity();
        gameEngine.addComponent(playerEntity, Vec2D.class, new Vec2D(25,25));
        gameEngine.addComponent(playerEntity, Render.class, new Render(Sprite.class));
        gameEngine.addComponent(playerEntity, Control.class, new Render(Control.class));
        // Load the sprite
        System.err.println(System.getProperty("user.dir"));
        Image image = new Image(new FileInputStream("sprite.png"));
        gameEngine.addComponent(playerEntity, Sprite.class, new Sprite(image, 50, 50));
        // Push rendering into its own thread (to meet the design principle of a JavaFX program)
        Thread renderThread = new Thread(new RenderStarter());
        renderThread.start();

        
        // Use a semaphore to block until the renderer is set-up
        rendererFinished.acquire();
        
        /************************************
        **    Run init() on the systems    **
        *************************************/

        controlSystem.init(worldStage);
        playerSystem.init(inputEntity);
        renderSystem.init(worldGroup);

        double dt = 0.001;
        double frameRate = 1.0 / 144; // 1 second divided by target number of frames
        
        boolean exit = false;
        long startTime = System.nanoTime();

        while (!exit){
            // Run system updates
            controlSystem.update(dt);
            playerSystem.update(dt);
            renderSystem.update(dt);

            dt = (System.nanoTime() - startTime) / 1e9; // convert nanoseconds to seconds
            startTime = System.nanoTime();
            if (dt < frameRate){
                Thread.sleep((int)((frameRate-dt)*1000));
                dt = (System.nanoTime() - startTime) / 1e9;
                startTime = System.nanoTime();
            }
        } 
        renderThread.join(); // Wait for any render task to exit     
    }
}
