/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package parallelnbody;

import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Locale;
import java.util.Scanner;

/**
 *
 * @author alefrag
 */
public class ParallelNBody {

    //Set value to simulate <execTime> seconds of the bodies
    static final int execTime = 100;

    static final boolean draw = false;
    
    public static void main(String[] args) throws FileNotFoundException {
         //Make sure we are at the correct format for input PA
        Locale.setDefault(new Locale("en", "US"));
        //Allow for reading from args[0] PA
        String fname = args[0];
        int numThreads = 1; // default
        if (args.length > 1) {
            try {
                numThreads = Integer.parseInt(args[1]);
                if (numThreads < 1) numThreads = 1;
            } catch (NumberFormatException e) {
                numThreads = 1;
            }
        }
        java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool(numThreads);
        FileInputStream is = new FileInputStream(new File(fname));
        System.setIn(is);
        
        Scanner console = new Scanner(System.in);

        final double dt = 0.1;                     // time quantum
        int N = console.nextInt();                 // number of particles
        double radius = console.nextDouble();      // radius of universe

        // turn on animation mode and rescale coordinate system PA
        if (draw) {
            StdDraw.show(0);
            StdDraw.setXscale(-radius, +radius);
            StdDraw.setYscale(-radius, +radius);
        }
        // read in and initialize bodies
        Body[] bodies = new Body[N];               // array of N bodies
        for (int i = 0; i < N; i++) {
            double px = console.nextDouble();
            double py = console.nextDouble();
            double vx = console.nextDouble();
            double vy = console.nextDouble();
            double mass = console.nextDouble();
            int red = console.nextInt();
            int green = console.nextInt();
            int blue = console.nextInt();
            Color color = new Color(red, green, blue);
            bodies[i] = new Body(px, py, vx, vy, mass, color);
        }

        // Store time after IO
        long startTime = System.nanoTime();

        // simulate the universe
        for (double t = 0.0; t < execTime; t = t + dt) {

            Quad quad = new Quad(0, 0, radius * 2);
            BHTree tree = new BHTree(quad);

            // build the Barnes-Hut tree (serial)
            for (int i = 0; i < N; i++) {
                if (bodies[i].in(quad)) {
                    tree.insert(bodies[i]);
                }
            }

            // parallel update: χωρίζουμε τα bodies σε chunks, ένα Runnable ανά thread
            java.util.List<java.util.concurrent.Callable<Void>> tasks = new java.util.ArrayList<>(numThreads);
            final int chunkSize = (N + numThreads - 1) / numThreads; // ceil
            for (int ti = 0; ti < numThreads; ti++) {
                final int start = ti * chunkSize;
                final int end = Math.min(N, start + chunkSize);
                tasks.add(new java.util.concurrent.Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        for (int i = start; i < end; i++) {
                            bodies[i].resetForce();
                            tree.updateForce(bodies[i]); // read-only on tree
                            bodies[i].update(dt);
                        }
                        return null;
                    }
                });
            }

            try {
                // invokeAll θα μπλοκάρει μέχρι να ολοκληρωθούν όλα τα tasks
                executor.invokeAll(tasks);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                break;
            }

            // If draw is enabled update picture
            if (draw) {
                StdDraw.clear(StdDraw.BLACK);
                for (int i = 0; i < N; i++) {
                    bodies[i].draw();
                }
                StdDraw.show(0);
            }
        }

        //Terminating the executor
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException ex) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        //Print execution time and exit
        long endTime = System.nanoTime();
        long totalTime = endTime - startTime;
        System.out.println("Simulation time: " + ((double) totalTime) / 1E9);
        System.exit(0);
    }
    
}
