package org.jgroups.tests;

import org.jgroups.util.*;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Bela Ban
 */
public class TimeSchedulerStressTest {
    final TimeScheduler timer;
    final int num_threads;
    final int num_tasks;       // to process per thread
    final long task_duration;  // ms

    final CyclicBarrier barrier;
    final AtomicInteger total_tasks=new AtomicInteger(0);
    final AtomicInteger total_sched=new AtomicInteger(0);
    final AtomicInteger total_task_invocations=new AtomicInteger(0);

    static final int NUM_THREADS_IN_TIMER=5;


    public TimeSchedulerStressTest(TimeScheduler timer, int num_threads, int num_tasks, long task_duration) {
        this.timer=timer;
        this.num_threads=num_threads;
        this.task_duration=task_duration;
        this.num_tasks=num_tasks;
        barrier=new CyclicBarrier(num_threads +1);
    }


    public void start() throws Exception {
        MyThread[] threads=new MyThread[num_threads];
        for(int i=0; i < threads.length; i++) {
            threads[i]=new MyThread();
            threads[i].setName("thread-" + i);
            threads[i].start();
        }

        Util.sleep(1000);
        System.out.println("starting " + threads.length + " threads");
        long start=System.currentTimeMillis();
        barrier.await();

        Reporter reporter=new Reporter();
        reporter.setDaemon(true);
        reporter.start();

        for(MyThread thread: threads)
            thread.join();
        long diff=System.currentTimeMillis() - start;
        timer.stop();
        
        System.out.println("Time: " + diff + " ms for " + total_tasks + " tasks");
        System.out.println("running tasks: " + timer.size() + ", total_sched: " + total_sched + ", completed: " + total_tasks +
        ", total task invocations: " + total_task_invocations);
    }


    class MyThread extends Thread {

        public void run() {
            try {
                barrier.await();
            }
            catch(Exception e) {
                e.printStackTrace();
            }

            MyTask[] tasks=new MyTask[num_tasks];

            for(int i=0; i < num_tasks; i++) {
                tasks[i]=new MyTask();
                timer.schedule(tasks[i], task_duration, TimeUnit.MILLISECONDS);
                total_sched.incrementAndGet();
            }

            for(MyTask task: tasks) {
                task.get();
                total_tasks.incrementAndGet();
            }
        }
    }

    class Reporter extends Thread {

        public void run() {
            while(!timer.isShutdown()) {
                System.out.println("running tasks: " + timer.size() + ", total_sched: " + total_sched + ", completed: " + total_tasks +
                        ", total task invocations: " + total_task_invocations);
                Util.sleep(2000);
            }
        }
    }


    class MyTask implements Runnable {
        final Promise<Boolean> result=new Promise<>();

        public void run() {
            total_task_invocations.incrementAndGet();
            result.setResult(true);
        }

        Boolean get() {
            return result.getResult();
        }
    }


    public static void main(String[] args) throws Exception {
        int num_threads=100;
        int num_tasks=100; // to process per thread
        long task_duration=50; // ms
        TimeScheduler timer=null;


        for(int i=0; i < args.length; i++) {
            if(args[i].equals("-num_threads")) {
                num_threads=Integer.parseInt(args[++i]);
                continue;
            }
            if(args[i].equals("-num_tasks")) {
                num_tasks=Integer.parseInt(args[++i]);
                continue;
            }
            if(args[i].equals("-task_duration")) {
                task_duration=Long.parseLong(args[++i]);
                continue;
            }
            if(args[i].equals("-type")) {
                String tmp=args[++i];
                if(tmp.equals("default")) {
                    timer=new DefaultTimeScheduler(NUM_THREADS_IN_TIMER); // ? That's not what we do in real life !
                }
                else if(tmp.equals("new")) {
                    //timer=new TimeScheduler3(new DefaultThreadFactory("tmp", true, true),
                      //                       NUM_THREADS_IN_TIMER, NUM_THREADS_IN_TIMER, 5000, 20000, "abort");
                    timer=new TimeScheduler3();
                }
                else {
                    help();
                    return;
                }
                continue;
            }

            help();
            return;
        }

        if(timer == null) {
            System.out.println("timer is null, using DefaultTimeScheduler with " + NUM_THREADS_IN_TIMER + " threads");
            timer=new DefaultTimeScheduler(NUM_THREADS_IN_TIMER);
        }

        TimeSchedulerStressTest test=new TimeSchedulerStressTest(timer, num_threads, num_tasks, task_duration);
        test.start();
    }

    static void help() {
        System.out.println("TimeSchedulerStressTest [-type <\"default\" | \"new\">] [-num_threads <num>] [-num_tasks <num>]" +
                " [-task_duration <ms>]");
    }
}
