package nz.ac.massey.javaecs.examples;

import java.sql.Time;
import java.util.Random;

import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import nz.ac.massey.javaecs.*;
import nz.ac.massey.javaecs.examples.Components.*;
import nz.ac.massey.javaecs.examples.Systems.*;

/**
 * Physics simulation app
 */
public final class App {
    private App() {
    }

    /**
     * Simple physics simulation.
     * @param args The arguments of the program.
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Engine gameEngine = new Engine(16384);

        gameEngine.registerComponent(Gravity.class);
        gameEngine.registerComponent(RidgidBody.class);
        gameEngine.registerComponent(Vec2D.class);
        gameEngine.registerComponent(Collider.class);
        gameEngine.registerComponent(LogUpdate.class);
        gameEngine.registerComponent(Render.class);
        gameEngine.registerComponent(BoxRender.class);
        gameEngine.registerComponent(TimedExistence.class);
        gameEngine.registerComponent(Reporter.class);
        gameEngine.registerComponent(TextRender.class);

        PhysicsSystem physicsSystem = new PhysicsSystem(gameEngine);
        gameEngine.registerSystem(PhysicsSystem.class, physicsSystem);
        

        CollisionSystem collisionSystem = new CollisionSystem(gameEngine);
        gameEngine.registerSystem(CollisionSystem.class, collisionSystem);

        LogUpdateSystem logUpdateSystem = new LogUpdateSystem(gameEngine);
        gameEngine.registerSystem(LogUpdateSystem.class, logUpdateSystem);

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

        ExistenceSystem existenceSystem =  new ExistenceSystem(gameEngine);
        gameEngine.registerSystem(ExistenceSystem.class, existenceSystem);

        ReportSystem reportSystem =  new ReportSystem(gameEngine);
        gameEngine.registerSystem(ReportSystem.class, reportSystem);

        // Define a play area 100 * 100, make sure to use worldspace not screenspace (top left is 0, 100, not 0,0)
        // Bottom boundary
        Entity newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., -50.,0));
        gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
        gameEngine.addComponent(newEnt, Collider.class, new Collider(200., 50.)); // 200 along, 50 up
        gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
        gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(200, 50, 64, 64, 64));
        // Left boundary
        newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., -50.,0));
        gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
        gameEngine.addComponent(newEnt, Collider.class, new Collider(50., 200.));
        gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
        gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(50, 200, 64, 64, 64));
        // Top boundary
        newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., 100.,0));
        gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
        gameEngine.addComponent(newEnt, Collider.class, new Collider(200., 50.));
        gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
        gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(200, 50, 64, 64, 64));
        // Right boundary
        newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(100., -50.,0));
        gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
        gameEngine.addComponent(newEnt, Collider.class, new Collider(50., 200.));
        gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
        gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(50, 200, 64, 64, 64));
        // One object
        newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(62., 90.,0));
        gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(1., 0., 0., 0., 0.f, 0., 1.));
        gameEngine.addComponent(newEnt, Collider.class, new Collider(10., 10.));
        gameEngine.addComponent(newEnt, Gravity.class, new Gravity(0, -9.80665, 45., 45.)); // Terminal velocity is 45 m/s
        gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
        gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(10, 10, 255, 64, 64));

        newEnt = gameEngine.createEntity();
        gameEngine.addComponent(newEnt, TextRender.class, new TextRender("", 24, Color.WHITE));
        gameEngine.addComponent(newEnt, Render.class, new Render(TextRender.class));
        gameEngine.addComponent(newEnt, Reporter.class, new Reporter());
        gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(5,5,0));
        // Other objects
        Random rand = new Random();
        for (int i = 0; i < 20; i++) {
            newEnt = gameEngine.createEntity();
            gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(10 + rand.nextDouble()* 80, 10+ rand.nextDouble()* 80,0));
            gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(-2.5 + rand.nextDouble()* 5., 0., 0., 0., 0.f, 0., rand.nextDouble() * 20.));
            double size = 3 + rand.nextDouble() * 7;
            gameEngine.addComponent(newEnt, Collider.class, new Collider(size, size));
            gameEngine.addComponent(newEnt, Gravity.class, new Gravity(0, -9.80665, 45., 45.)); // Terminal velocity is 45 m/s
            gameEngine.addComponent(newEnt, LogUpdate.class, null); // Add to object to print pos of
            gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
            gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(size, size, 30 + rand.nextInt(225), 30 + rand.nextInt(225), 30 + rand.nextInt(225)));
            gameEngine.addComponent(newEnt, TimedExistence.class, new TimedExistence(5 + rand.nextDouble() * 90));
        }
        
        //gameEngine.addComponent(newEnt, LogUpdate.class, null);


        physicsSystem.init();
        collisionSystem.init();
        logUpdateSystem.init();
        renderSystem.init();
        existenceSystem.init();
        reportSystem.init();

        double dt = 0.001;
        double frameRate = 1.0 / 144; // 1 second divided by target number of frames
        double idleTime = 0.0; // the amount of time spent sleeping
        
        boolean exit = false;
        long loopStart = System.currentTimeMillis();
        long startTime = System.nanoTime();
        while (!exit){

            collisionSystem.update();
            physicsSystem.update(dt);
            existenceSystem.update(dt);
            reportSystem.update();
            //logUpdateSystem.update();
            // The render system should be uncoupled from the physics and collision
            renderSystem.update();

            dt = (System.nanoTime() - startTime) / 1e9; // convert nanoseconds to seconds
            startTime = System.nanoTime();
            if (dt < frameRate){
                idleTime = frameRate-dt;
                Thread.sleep((int)((frameRate-dt)*1000));
                dt = (System.nanoTime() - startTime) / 1e9;
                startTime = System.nanoTime();
            }
            dt = 0.001;
        }
    }
}
