package jvm;
import java.lang.System; // Import System for nanoTime and printf
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * A Java class to replicate the calculation performed by the Python script,
 * optimized using multithreading.
 */
public class Calculation2 {

    /**
     * Represents a task that calculates a partial sum for a given range of iterations.
     * Implements Callable so it can be executed by an ExecutorService and return a result.
     */
    private static class PartialCalculator implements Callable<Double> {
        private final int startIteration;
        private final int endIteration;
        private final int param1;
        private final int param2;

        /**
         * Constructor for the partial calculator task.
         * @param startIteration The starting iteration (inclusive).
         * @param endIteration The ending iteration (exclusive).
         * @param param1 The first parameter for calculation.
         * @param param2 The second parameter for calculation.
         */
        public PartialCalculator(int startIteration, int endIteration, int param1, int param2) {
            this.startIteration = startIteration;
            this.endIteration = endIteration;
            this.param1 = param1;
            this.param2 = param2;
        }

        /**
         * The computation performed by this task. Calculates the sum for the assigned range.
         * @return The partial sum for the range.
         */
        @Override
        public Double call() {
            double partialSum = 0.0;
            // Loop through the assigned range of iterations
            // Note: loop goes up to < endIteration
            for (int i = startIteration; i < endIteration; i++) {
                // Calculate the first value of j
                double jMinus = (double)i * param1 - param2;
                // Subtract 1.0 / jMinus from the partial sum
                partialSum -= (1.0 / jMinus);

                // Calculate the second value of j
                double jPlus = (double)i * param1 + param2;
                // Add 1.0 / jPlus to the partial sum
                partialSum += (1.0 / jPlus);
            }
            return partialSum;
        }
    }

    /**
     * Performs the iterative calculation using multiple threads.
     *
     * @param iterations The total number of iterations for the loop.
     * @param param1     The first parameter used in the calculation.
     * @param param2     The second parameter used in the calculation.
     * @param numThreads The number of threads to use for the calculation.
     * @return The result of the calculation.
     * @throws InterruptedException If thread execution is interrupted.
     * @throws ExecutionException If computation threw an exception.
     */
    public static double calculateParallel(int iterations, int param1, int param2, int numThreads)
            throws InterruptedException, ExecutionException {

        // Create a fixed-size thread pool
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        // List to hold the Future objects representing the results of each task
        List<Future<Double>> futureResults = new ArrayList<>();

        // Calculate the approximate number of iterations per thread
        int iterationsPerThread = iterations / numThreads;
        int start = 1; // Start iteration from 1

        // Divide the work and submit tasks to the executor
        for (int i = 0; i < numThreads; i++) {
            int end = start + iterationsPerThread;
            // For the last thread, ensure it covers all remaining iterations
            if (i == numThreads - 1) {
                end = iterations + 1; // Go up to iterations (inclusive)
            }
            // Ensure end doesn't exceed the total iterations + 1 boundary
            if (end > iterations + 1) {
                end = iterations + 1;
            }

            // Create and submit the task for the calculated range
            // The loop inside PartialCalculator runs from start (inclusive) to end (exclusive)
            Callable<Double> task = new PartialCalculator(start, end, param1, param2);
            futureResults.add(executor.submit(task));

            // Set the start for the next chunk
            start = end;
        }

        // Initialize the total result (starting from the base 1.0)
        double totalResult = 1.0;
        // Retrieve results from each Future and add to the total result
        for (Future<Double> future : futureResults) {
            // future.get() blocks until the result is available
            totalResult += future.get();
        }

        // Shut down the executor service gracefully
        executor.shutdown();

        // Return the final combined result
        return totalResult;
    }

    /**
     * The main entry point of the program.
     *
     * @param args Command line arguments (not used).
     */
    public static void main(String[] args) {
        // Define the parameters for the calculation
        final int ITERATIONS = 100_000_000;
        final int PARAM1 = 4;
        final int PARAM2 = 1;
        // Determine the number of threads based on available processors
        final int NUM_THREADS = Runtime.getRuntime().availableProcessors();

        System.out.println("Using " + NUM_THREADS + " threads.");

        // Record the start time
        long startTime = System.nanoTime();

        double finalResult = 0;
        try {
            // Perform the parallel calculation and multiply the result by 4.0
            finalResult = calculateParallel(ITERATIONS, PARAM1, PARAM2, NUM_THREADS) * 4.0;

            // Record the end time
            long endTime = System.nanoTime();

            // Calculate the duration
            long durationNanos = endTime - startTime;
            double durationSeconds = durationNanos / 1_000_000_000.0;

            // Print the final result and execution time
            System.out.printf("Result: %.12f%n", finalResult);
            System.out.printf("Execution Time: %.6f seconds%n", durationSeconds);

        } catch (InterruptedException | ExecutionException e) {
            // Handle potential exceptions during parallel execution
            System.err.println("Calculation failed: " + e.getMessage());
            e.printStackTrace();
            // Ensure System.exit is not used in production code without careful consideration
            // System.exit(1);
        }
    }
}
