// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jrt;


class Scheduler {
    private static final int TICK  = 100;
    private static final int SLOTS = 512;
    private static final int MASK  = 511;
    private static final int SHIFT = 9;

    private Task[] slots    = new Task[SLOTS + 1];
    private int[]  counts   = new int[SLOTS + 1];
    private Queue  queue    = new Queue(TICK);
    private int    currIter = 0;
    private int    currSlot = 0;
    private long   nextTick;

    private static boolean isActive(Task task) {
        return (task.next() != null);
    }

    private void linkIn(Task task) {
        Task head = slots[task.slot()];
        if (head == null) {
            task.next(task);
            task.prev(task);
            slots[task.slot()] = task;
        } else {
            task.next(head);
            task.prev(head.prev());
            head.prev().next(task);
            head.prev(task);
        }
        ++counts[task.slot()];
    }

    private void linkOut(Task task) {
        Task head = slots[task.slot()];
        if (task.next() == task) {
            slots[task.slot()] = null;
        } else {
            task.prev().next(task.next());
            task.next().prev(task.prev());
            if (head == task) {
                slots[task.slot()] = task.next();
            }
        }
        task.next(null);
        task.prev(null);
        --counts[task.slot()];
    }

    public Scheduler(long now) {
        nextTick = now + TICK;
    }

    public synchronized void schedule(Task task, double seconds) {
        if (task.isKilled()) {
            return;
        }
        if (seconds < 0.0) {
            throw new IllegalArgumentException("cannot schedule a Task in the past");
        }
        int ticks = 2 + (int) Math.ceil(seconds * (1000.0 / TICK));
        if (isActive(task)) {
            linkOut(task);
        }
        task.slot((ticks + currSlot) & MASK);
        task.iter(currIter + ((ticks + currSlot) >> SHIFT));
        linkIn(task);
    }

    public synchronized void scheduleNow(Task task) {
        if (task.isKilled()) {
            return;
        }
        if (isActive(task)) {
            linkOut(task);
        }
        task.slot(SLOTS);
        task.iter(0);
        linkIn(task);
    }

    public synchronized boolean unschedule(Task task) {
        if (isActive(task)) {
            linkOut(task);
            return true;
        }
        return false;
    }

    public synchronized boolean kill(Task task) {
        task.setKilled();
        if (isActive(task)) {
            linkOut(task);
            return true;
        }
        return false;
    }

    private void queueTasks(int slot, int iter) {
        int cnt = counts[slot];
        Task task = slots[slot];
        for (int i = 0; i < cnt; i++) {
            Task next = task.next();
            if (task.iter() == iter) {
                linkOut(task);
                queue.enqueue(task);
            }
            task = next;
        }
    }

    public void checkTasks(long now) {
        if (slots[SLOTS] == null && now < nextTick) {
            return;
        }
        synchronized (this) {
            queueTasks(SLOTS, 0);
            for (int i = 0; now >= nextTick; i++, nextTick += TICK) {
                if (i < 3) {
                    if (++currSlot >= SLOTS) {
                        currSlot = 0;
                        currIter++;
                    }
                    queueTasks(currSlot, currIter);
                }
            }
        }
        while (!queue.isEmpty()) {
            Task task = (Task) queue.dequeue();
            task.perform();
        }
    }
}
