/*
 * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.graalvm.visualvm.core.scheduler;

import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Scheduling pipe maintains a list of {@linkplain ScheduledTask} instances
 * for a certain scheduling interval
 * <p>It allows addition and removal of the tasks and takes care of creating
 * and releasing appropriate scheduled executors</p>
 * 
 * @author Jaroslav Bachorik <jaroslav.bachorik@sun.com>
 */
final class SchedulingPipe {
    private static final Logger LOGGER = Logger.getLogger(SchedulingPipe.class.getName());

    final private Object pipeLock = new Object();
    final private ReadWriteLock tasksLock = new ReentrantReadWriteLock();

    // @GuardedBy pipeLock
    private ScheduledFuture<?> pipeFuture = null;

    final private static ScheduledExecutorService schedulerService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
    final private static ExecutorService dispatcher = Executors.newCachedThreadPool();
    
    // @GuardedBy tasksLock
    final private Set<WeakReference<DefaultScheduledTask>> tasks = new HashSet<>();

    private Quantum interval;

    SchedulingPipe(Quantum interval) {
        this.interval = interval;
    }

    void addTask(DefaultScheduledTask task) {
        tasksLock.writeLock().lock();
        try {
            if (tasks.isEmpty()) {
                startPipe();
            }
            tasks.add(new WeakReference<>(task));
        } finally {
            tasksLock.writeLock().unlock();
        }
    }

    private void startPipe() {
        synchronized (pipeLock) {
            pipeFuture = schedulerService.scheduleAtFixedRate(new Runnable() {

                public void run() {
                    tasksLock.writeLock().lock();
                    try {
                        final long timeStamp = System.currentTimeMillis();
                        for (Iterator<WeakReference<DefaultScheduledTask>> iter = tasks.iterator(); iter.hasNext();) {
                            WeakReference<DefaultScheduledTask> ref = iter.next();
                            final DefaultScheduledTask t = ref.get();
                            if (t != null) {
                                dispatcher.submit(new Runnable() {

                                    public void run() {
                                        try {
                                            t.onSchedule(timeStamp);
                                        } catch (Throwable e) {
                                            LOGGER.log(Level.SEVERE, null, e);
                                        }
                                    }
                                });
                            } else {
                                iter.remove();
                            }
                        }
                        if (tasks.isEmpty()) {
                            synchronized (pipeLock) {
                                pipeFuture.cancel(false);
                                pipeFuture = null;
                            }
                        }
                    } finally {
                        tasksLock.writeLock().unlock();
                    }
                }
            }, interval.interval, interval.interval, interval.unit);
        }
    }

    void removeTask(DefaultScheduledTask task) {
        tasksLock.writeLock().lock();
        try {
            for(Iterator<WeakReference<DefaultScheduledTask>> iter = tasks.iterator();iter.hasNext();) {
                WeakReference<DefaultScheduledTask> ref = iter.next();
                DefaultScheduledTask t = ref.get();
                if (t == null || t.equals(task)) {
                    iter.remove();
                }
            }
        } finally {
            tasksLock.writeLock().unlock();
        }
        if (tasks.isEmpty()) {
            synchronized(pipeLock) {
                pipeFuture.cancel(false);
                pipeFuture = null;
            }
        }
    }
}
